diff --git a/Makefile b/Makefile index 8debc39d9..713c01683 100644 --- a/Makefile +++ b/Makefile @@ -229,7 +229,8 @@ include third_party/libcxx/BUILD.mk # │ include third_party/pcre/BUILD.mk # │ include third_party/less/BUILD.mk # │ include net/https/BUILD.mk # │ -include third_party/regex/BUILD.mk #─┘ +include third_party/regex/BUILD.mk # │ +include third_party/bash/BUILD.mk #─┘ include third_party/tidy/BUILD.mk include third_party/BUILD.mk include third_party/nsync/testing/BUILD.mk diff --git a/third_party/BUILD.mk b/third_party/BUILD.mk index 58a00b7fa..886867b04 100644 --- a/third_party/BUILD.mk +++ b/third_party/BUILD.mk @@ -5,6 +5,7 @@ o/$(MODE)/third_party: \ o/$(MODE)/third_party/argon2 \ o/$(MODE)/third_party/awk \ + o/$(MODE)/third_party/bash \ o/$(MODE)/third_party/bzip2 \ o/$(MODE)/third_party/chibicc \ o/$(MODE)/third_party/compiler_rt \ diff --git a/third_party/bash/BUILD.mk b/third_party/bash/BUILD.mk new file mode 100644 index 000000000..4cf2a8fee --- /dev/null +++ b/third_party/bash/BUILD.mk @@ -0,0 +1,97 @@ +#-*-mode:bashfile-gbash;indent-tabs-mode:t;tab-width:8;coding:utf-8-*-┐ +#───vi: set et ft=bash ts=8 tw=8 fenc=utf-8 :vi───────────────────────┘ + +PKGS += THIRD_PARTY_BASH + +THIRD_PARTY_BASH_A = o/$(MODE)/third_party/bash/bash.a +THIRD_PARTY_BASH_FILES := $(wildcard third_party/bash/*) +THIRD_PARTY_BASH_HDRS = $(filter %.h,$(THIRD_PARTY_BASH_FILES)) +THIRD_PARTY_BASH_INCS = $(filter %.inc,$(THIRD_PARTY_BASH_FILES)) +THIRD_PARTY_BASH_SRCS = $(filter %.c,$(THIRD_PARTY_BASH_FILES)) +THIRD_PARTY_BASH_OBJS = $(THIRD_PARTY_BASH_SRCS:%.c=o/$(MODE)/%.o) +THIRD_PARTY_BASH_COMS = o/$(MODE)/third_party/bash/bash.com +THIRD_PARTY_BASH_CHECKS = $(THIRD_PARTY_BASH_A).pkg + +THIRD_PARTY_BASH_BINS = \ + $(THIRD_PARTY_BASH_COMS) \ + $(THIRD_PARTY_BASH_COMS:%=%.dbg) + +THIRD_PARTY_BASH_DIRECTDEPS = \ + LIBC_CALLS \ + LIBC_DNS \ + LIBC_FMT \ + LIBC_INTRIN \ + LIBC_MEM \ + LIBC_NEXGEN32E \ + LIBC_PROC \ + LIBC_RUNTIME \ + LIBC_SOCK \ + LIBC_STDIO \ + LIBC_STR \ + LIBC_SYSV \ + LIBC_THREAD \ + LIBC_TIME \ + THIRD_PARTY_GDTOA \ + THIRD_PARTY_GETOPT \ + THIRD_PARTY_MUSL \ + THIRD_PARTY_NCURSES \ + THIRD_PARTY_READLINE \ + THIRD_PARTY_REGEX + +THIRD_PARTY_BASH_DEPS := \ + $(call uniq,$(foreach x,$(THIRD_PARTY_BASH_DIRECTDEPS),$($(x)))) + +$(THIRD_PARTY_BASH_A).pkg: \ + $(THIRD_PARTY_BASH_OBJS) \ + $(foreach x,$(THIRD_PARTY_BASH_DIRECTDEPS),$($(x)_A).pkg) + +$(THIRD_PARTY_BASH_A): \ + third_party/bash/ \ + $(THIRD_PARTY_BASH_A).pkg \ + $(filter-out %main.o,$(THIRD_PARTY_BASH_OBJS)) + +o/$(MODE)/third_party/bash/bash.com.dbg: \ + $(THIRD_PARTY_BASH_DEPS) \ + $(THIRD_PARTY_BASH_A) \ + $(THIRD_PARTY_BASH_A).pkg \ + o/$(MODE)/third_party/bash/shell.o \ + $(CRT) \ + $(APE_NO_MODIFY_SELF) + @$(APELINK) + +$(THIRD_PARTY_BASH_OBJS): private \ + CPPFLAGS += \ + -DHAVE_CONFIG_H \ + -DSHELL \ + -DPACKAGE=\"bash\" \ + -DLOCALEDIR=\"/zip/usr/share/locale\" \ + -DCONF_HOSTTYPE=\"unknown\" \ + -DCONF_OSTYPE=\"linux-cosmo\" \ + -DCONF_MACHTYPE=\"unknown-pc-unknown-cosmo\" \ + -DCONF_VENDOR=\"pc\" + +$(THIRD_PARTY_BASH_OBJS): private \ + CFLAGS += \ + -Wno-unused-but-set-variable \ + -Wno-discarded-qualifiers \ + -Wno-maybe-uninitialized \ + -Wno-pointer-to-int-cast \ + -Wno-stringop-truncation \ + -Wno-format-zero-length \ + -Wno-format-overflow \ + -Wno-char-subscripts \ + -Wno-nonnull-compare \ + -Wno-unused-variable \ + -Wno-missing-braces \ + -Wno-unused-label \ + -Wno-unused-value \ + -Wno-parentheses \ + -fportcosmo + +$(THIRD_PARTY_BASH_OBJS): third_party/bash/BUILD.mk + +.PHONY: o/$(MODE)/third_party/bash +o/$(MODE)/third_party/bash: \ + $(THIRD_PARTY_BASH_BINS) \ + $(THIRD_PARTY_BASH_CHECKS) + diff --git a/third_party/bash/LICENSE b/third_party/bash/LICENSE new file mode 100644 index 000000000..94a9ed024 --- /dev/null +++ b/third_party/bash/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/third_party/bash/README.cosmo b/third_party/bash/README.cosmo new file mode 100644 index 000000000..cfee2c748 --- /dev/null +++ b/third_party/bash/README.cosmo @@ -0,0 +1,15 @@ +DESCRIPTION + + the bourne again shell + +LICENSE + + GPL v3 + +ORIGIN + + https://ftp.gnu.org/gnu/bash/bash-5.2.tar.gz + +LOCAL CHANGES + + - Force disable mkfifo() code diff --git a/third_party/bash/alias.c b/third_party/bash/alias.c new file mode 100644 index 000000000..23f967fa8 --- /dev/null +++ b/third_party/bash/alias.c @@ -0,0 +1,594 @@ +/* alias.c -- Not a full alias, but just the kind that we use in the + shell. Csh style alias is somewhere else (`over there, in a box'). */ + +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (ALIAS) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "chartypes.h" +#include "bashansi.h" +#include "command.h" +#include "general.h" +#include "externs.h" +#include "alias.h" + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include /* mbschr */ +#endif + +#define ALIAS_HASH_BUCKETS 64 /* must be power of two */ + +typedef int sh_alias_map_func_t PARAMS((alias_t *)); + +static void free_alias_data PARAMS((PTR_T)); +static alias_t **map_over_aliases PARAMS((sh_alias_map_func_t *)); +static void sort_aliases PARAMS((alias_t **)); +static int qsort_alias_compare PARAMS((alias_t **, alias_t **)); + +#if defined (READLINE) +static int skipquotes PARAMS((char *, int)); +static int skipws PARAMS((char *, int)); +static int rd_token PARAMS((char *, int)); +#endif + +/* Non-zero means expand all words on the line. Otherwise, expand + after first expansion if the expansion ends in a space. */ +int alias_expand_all = 0; + +/* The list of aliases that we have. */ +HASH_TABLE *aliases = (HASH_TABLE *)NULL; + +void +initialize_aliases () +{ + if (aliases == 0) + aliases = hash_create (ALIAS_HASH_BUCKETS); +} + +/* Scan the list of aliases looking for one with NAME. Return NULL + if the alias doesn't exist, else a pointer to the alias_t. */ +alias_t * +find_alias (name) + char *name; +{ + BUCKET_CONTENTS *al; + + if (aliases == 0) + return ((alias_t *)NULL); + + al = hash_search (name, aliases, 0); + return (al ? (alias_t *)al->data : (alias_t *)NULL); +} + +/* Return the value of the alias for NAME, or NULL if there is none. */ +char * +get_alias_value (name) + char *name; +{ + alias_t *alias; + + if (aliases == 0) + return ((char *)NULL); + + alias = find_alias (name); + return (alias ? alias->value : (char *)NULL); +} + +/* Make a new alias from NAME and VALUE. If NAME can be found, + then replace its value. */ +void +add_alias (name, value) + char *name, *value; +{ + BUCKET_CONTENTS *elt; + alias_t *temp; + int n; + + if (aliases == 0) + { + initialize_aliases (); + temp = (alias_t *)NULL; + } + else + temp = find_alias (name); + + if (temp) + { + free (temp->value); + temp->value = savestring (value); + temp->flags &= ~AL_EXPANDNEXT; + if (value[0]) + { + n = value[strlen (value) - 1]; + if (n == ' ' || n == '\t') + temp->flags |= AL_EXPANDNEXT; + } + } + else + { + temp = (alias_t *)xmalloc (sizeof (alias_t)); + temp->name = savestring (name); + temp->value = savestring (value); + temp->flags = 0; + + if (value[0]) + { + n = value[strlen (value) - 1]; + if (n == ' ' || n == '\t') + temp->flags |= AL_EXPANDNEXT; + } + + elt = hash_insert (savestring (name), aliases, HASH_NOSRCH); + elt->data = temp; +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_aliases); +#endif + } +} + +/* Delete a single alias structure. */ +static void +free_alias_data (data) + PTR_T data; +{ + register alias_t *a; + + a = (alias_t *)data; + + if (a->flags & AL_BEINGEXPANDED) + clear_string_list_expander (a); /* call back to the parser */ + + free (a->value); + free (a->name); + free (data); +} + +/* Remove the alias with name NAME from the alias table. Returns + the number of aliases left in the table, or -1 if the alias didn't + exist. */ +int +remove_alias (name) + char *name; +{ + BUCKET_CONTENTS *elt; + + if (aliases == 0) + return (-1); + + elt = hash_remove (name, aliases, 0); + if (elt) + { + free_alias_data (elt->data); + free (elt->key); /* alias name */ + free (elt); /* XXX */ +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_aliases); +#endif + return (aliases->nentries); + } + return (-1); +} + +/* Delete all aliases. */ +void +delete_all_aliases () +{ + if (aliases == 0) + return; + + hash_flush (aliases, free_alias_data); + hash_dispose (aliases); + aliases = (HASH_TABLE *)NULL; +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_aliases); +#endif +} + +/* Return an array of aliases that satisfy the conditions tested by FUNCTION. + If FUNCTION is NULL, return all aliases. */ +static alias_t ** +map_over_aliases (function) + sh_alias_map_func_t *function; +{ + register int i; + register BUCKET_CONTENTS *tlist; + alias_t *alias, **list; + int list_index; + + i = HASH_ENTRIES (aliases); + if (i == 0) + return ((alias_t **)NULL); + + list = (alias_t **)xmalloc ((i + 1) * sizeof (alias_t *)); + for (i = list_index = 0; i < aliases->nbuckets; i++) + { + for (tlist = hash_items (i, aliases); tlist; tlist = tlist->next) + { + alias = (alias_t *)tlist->data; + + if (!function || (*function) (alias)) + { + list[list_index++] = alias; + list[list_index] = (alias_t *)NULL; + } + } + } + return (list); +} + +static void +sort_aliases (array) + alias_t **array; +{ + qsort (array, strvec_len ((char **)array), sizeof (alias_t *), (QSFUNC *)qsort_alias_compare); +} + +static int +qsort_alias_compare (as1, as2) + alias_t **as1, **as2; +{ + int result; + + if ((result = (*as1)->name[0] - (*as2)->name[0]) == 0) + result = strcmp ((*as1)->name, (*as2)->name); + + return (result); +} + +/* Return a sorted list of all defined aliases */ +alias_t ** +all_aliases () +{ + alias_t **list; + + if (aliases == 0 || HASH_ENTRIES (aliases) == 0) + return ((alias_t **)NULL); + + list = map_over_aliases ((sh_alias_map_func_t *)NULL); + if (list) + sort_aliases (list); + return (list); +} + +char * +alias_expand_word (s) + char *s; +{ + alias_t *r; + + r = find_alias (s); + return (r ? savestring (r->value) : (char *)NULL); +} + +/* Readline support functions -- expand all aliases in a line. */ + +#if defined (READLINE) + +/* Return non-zero if CHARACTER is a member of the class of characters + that are self-delimiting in the shell (this really means that these + characters delimit tokens). */ +#define self_delimiting(character) (member ((character), " \t\n\r;|&()")) + +/* Return non-zero if CHARACTER is a member of the class of characters + that delimit commands in the shell. */ +#define command_separator(character) (member ((character), "\r\n;|&(")) + +/* If this is 1, we are checking the next token read for alias expansion + because it is the first word in a command. */ +static int command_word; + +/* This is for skipping quoted strings in alias expansions. */ +#define quote_char(c) (((c) == '\'') || ((c) == '"')) + +/* Consume a quoted string from STRING, starting at string[START] (so + string[START] is the opening quote character), and return the index + of the closing quote character matching the opening quote character. + This handles single matching pairs of unquoted quotes; it could afford + to be a little smarter... This skips words between balanced pairs of + quotes, words where the first character is quoted with a `\', and other + backslash-escaped characters. */ + +static int +skipquotes (string, start) + char *string; + int start; +{ + register int i; + int delimiter = string[start]; + + /* i starts at START + 1 because string[START] is the opening quote + character. */ + for (i = start + 1 ; string[i] ; i++) + { + if (string[i] == '\\') + { + i++; /* skip backslash-quoted quote characters, too */ + if (string[i] == 0) + break; + continue; + } + + if (string[i] == delimiter) + return i; + } + return (i); +} + +/* Skip the white space and any quoted characters in STRING, starting at + START. Return the new index into STRING, after zero or more characters + have been skipped. */ +static int +skipws (string, start) + char *string; + int start; +{ + register int i; + int pass_next, backslash_quoted_word; + unsigned char peekc; + + /* skip quoted strings, in ' or ", and words in which a character is quoted + with a `\'. */ + i = backslash_quoted_word = pass_next = 0; + + /* Skip leading whitespace (or separator characters), and quoted words. + But save it in the output. */ + + for (i = start; string[i]; i++) + { + if (pass_next) + { + pass_next = 0; + continue; + } + + if (whitespace (string[i])) + { + backslash_quoted_word = 0; /* we are no longer in a backslash-quoted word */ + continue; + } + + if (string[i] == '\\') + { + peekc = string[i+1]; + if (peekc == 0) + break; + if (ISLETTER (peekc)) + backslash_quoted_word++; /* this is a backslash-quoted word */ + else + pass_next++; + continue; + } + + /* This only handles single pairs of non-escaped quotes. This + overloads backslash_quoted_word to also mean that a word like + ""f is being scanned, so that the quotes will inhibit any expansion + of the word. */ + if (quote_char(string[i])) + { + i = skipquotes (string, i); + /* This could be a line that contains a single quote character, + in which case skipquotes () terminates with string[i] == '\0' + (the end of the string). Check for that here. */ + if (string[i] == '\0') + break; + + peekc = string[i + 1]; + if (ISLETTER (peekc)) + backslash_quoted_word++; + continue; + } + + /* If we're in the middle of some kind of quoted word, let it + pass through. */ + if (backslash_quoted_word) + continue; + + /* If this character is a shell command separator, then set a hint for + alias_expand that the next token is the first word in a command. */ + + if (command_separator (string[i])) + { + command_word++; + continue; + } + break; + } + return (i); +} + +/* Characters that may appear in a token. Basically, anything except white + space and a token separator. */ +#define token_char(c) (!((whitespace (string[i]) || self_delimiting (string[i])))) + +/* Read from START in STRING until the next separator character, and return + the index of that separator. Skip backslash-quoted characters. Call + skipquotes () for quoted strings in the middle or at the end of tokens, + so all characters show up (e.g. foo'' and foo""bar) */ +static int +rd_token (string, start) + char *string; + int start; +{ + register int i; + + /* From here to next separator character is a token. */ + for (i = start; string[i] && token_char (string[i]); i++) + { + if (string[i] == '\\') + { + i++; /* skip backslash-escaped character */ + if (string[i] == 0) + break; + continue; + } + + /* If this character is a quote character, we want to call skipquotes + to get the whole quoted portion as part of this word. That word + will not generally match an alias, even if te unquoted word would + have. The presence of the quotes in the token serves then to + inhibit expansion. */ + if (quote_char (string[i])) + { + i = skipquotes (string, i); + /* This could be a line that contains a single quote character, + in which case skipquotes () terminates with string[i] == '\0' + (the end of the string). Check for that here. */ + if (string[i] == '\0') + break; + + /* Now string[i] is the matching quote character, and the + quoted portion of the token has been scanned. */ + continue; + } + } + return (i); +} + +/* Return a new line, with any aliases substituted. */ +char * +alias_expand (string) + char *string; +{ + register int i, j, start; + char *line, *token; + int line_len, tl, real_start, expand_next, expand_this_token; + alias_t *alias; + + line_len = strlen (string) + 1; + line = (char *)xmalloc (line_len); + token = (char *)xmalloc (line_len); + + line[0] = i = 0; + expand_next = 0; + command_word = 1; /* initialized to expand the first word on the line */ + + /* Each time through the loop we find the next word in line. If it + has an alias, substitute the alias value. If the value ends in ` ', + then try again with the next word. Else, if there is no value, or if + the value does not end in space, we are done. */ + + for (;;) + { + + token[0] = 0; + start = i; + + /* Skip white space and quoted characters */ + i = skipws (string, start); + + if (start == i && string[i] == '\0') + { + free (token); + return (line); + } + + /* copy the just-skipped characters into the output string, + expanding it if there is not enough room. */ + j = strlen (line); + tl = i - start; /* number of characters just skipped */ + RESIZE_MALLOCED_BUFFER (line, j, (tl + 1), line_len, (tl + 50)); + strncpy (line + j, string + start, tl); + line[j + tl] = '\0'; + + real_start = i; + + command_word = command_word || (command_separator (string[i])); + expand_this_token = (command_word || expand_next); + expand_next = 0; + + /* Read the next token, and copy it into TOKEN. */ + start = i; + i = rd_token (string, start); + + tl = i - start; /* token length */ + + /* If tl == 0, but we're not at the end of the string, then we have a + single-character token, probably a delimiter */ + if (tl == 0 && string[i] != '\0') + { + tl = 1; + i++; /* move past it */ + } + + strncpy (token, string + start, tl); + token [tl] = '\0'; + + /* If there is a backslash-escaped character quoted in TOKEN, + then we don't do alias expansion. This should check for all + other quoting characters, too. */ + if (mbschr (token, '\\')) + expand_this_token = 0; + + /* If we should be expanding here, if we are expanding all words, or if + we are in a location in the string where an expansion is supposed to + take place, see if this word has a substitution. If it does, then do + the expansion. Note that we defer the alias value lookup until we + are sure we are expanding this token. */ + + if ((token[0]) && + (expand_this_token || alias_expand_all) && + (alias = find_alias (token))) + { + char *v; + int vlen, llen; + + v = alias->value; + vlen = strlen (v); + llen = strlen (line); + + /* +3 because we possibly add one more character below. */ + RESIZE_MALLOCED_BUFFER (line, llen, (vlen + 3), line_len, (vlen + 50)); + + strcpy (line + llen, v); + + if ((expand_this_token && vlen && whitespace (v[vlen - 1])) || + alias_expand_all) + expand_next = 1; + } + else + { + int llen, tlen; + + llen = strlen (line); + tlen = i - real_start; /* tlen == strlen(token) */ + + RESIZE_MALLOCED_BUFFER (line, llen, (tlen + 1), line_len, (llen + tlen + 50)); + + strncpy (line + llen, string + real_start, tlen); + line[llen + tlen] = '\0'; + } + command_word = 0; + } +} +#endif /* READLINE */ +#endif /* ALIAS */ diff --git a/third_party/bash/alias.h b/third_party/bash/alias.h new file mode 100644 index 000000000..4e2d67c0f --- /dev/null +++ b/third_party/bash/alias.h @@ -0,0 +1,73 @@ +/* alias.h -- structure definitions. */ + +/* Copyright (C) 1987-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_ALIAS_H_) +#define _ALIAS_H_ + +#include "stdc.h" + +#include "hashlib.h" + +typedef struct alias { + char *name; + char *value; + char flags; +} alias_t; + +/* Values for `flags' member of struct alias. */ +#define AL_EXPANDNEXT 0x1 +#define AL_BEINGEXPANDED 0x2 + +/* The list of known aliases. */ +extern HASH_TABLE *aliases; + +extern void initialize_aliases PARAMS((void)); + +/* Scan the list of aliases looking for one with NAME. Return NULL + if the alias doesn't exist, else a pointer to the alias. */ +extern alias_t *find_alias PARAMS((char *)); + +/* Return the value of the alias for NAME, or NULL if there is none. */ +extern char *get_alias_value PARAMS((char *)); + +/* Make a new alias from NAME and VALUE. If NAME can be found, + then replace its value. */ +extern void add_alias PARAMS((char *, char *)); + +/* Remove the alias with name NAME from the alias list. Returns + the index of the removed alias, or -1 if the alias didn't exist. */ +extern int remove_alias PARAMS((char *)); + +/* Remove all aliases. */ +extern void delete_all_aliases PARAMS((void)); + +/* Return an array of all defined aliases. */ +extern alias_t **all_aliases PARAMS((void)); + +/* Expand a single word for aliases. */ +extern char *alias_expand_word PARAMS((char *)); + +/* Return a new line, with any aliases expanded. */ +extern char *alias_expand PARAMS((char *)); + +/* Helper definition for the parser */ +extern void clear_string_list_expander PARAMS((alias_t *)); + +#endif /* _ALIAS_H_ */ diff --git a/third_party/bash/ansi_stdlib.h b/third_party/bash/ansi_stdlib.h new file mode 100644 index 000000000..7dc2ee0cf --- /dev/null +++ b/third_party/bash/ansi_stdlib.h @@ -0,0 +1,54 @@ +/* ansi_stdlib.h -- An ANSI Standard stdlib.h. */ +/* A minimal stdlib.h containing extern declarations for those functions + that bash uses. */ + +/* Copyright (C) 1993 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_STDLIB_H_) +#define _STDLIB_H_ 1 + +/* String conversion functions. */ +extern int atoi (); + +extern double atof (); +extern double strtod (); + +/* Memory allocation functions. */ +/* Generic pointer type. */ +#ifndef PTR_T + +#if defined (__STDC__) +# define PTR_T void * +#else +# define PTR_T char * +#endif + +#endif /* PTR_T */ + +extern PTR_T malloc (); +extern PTR_T realloc (); +extern void free (); + +/* Other miscellaneous functions. */ +extern void abort (); +extern void exit (); +extern char *getenv (); +extern void qsort (); + +#endif /* _STDLIB_H */ diff --git a/third_party/bash/array.c b/third_party/bash/array.c new file mode 100644 index 000000000..39a0ef51b --- /dev/null +++ b/third_party/bash/array.c @@ -0,0 +1,1303 @@ +/* + * array.c - functions to create, destroy, access, and manipulate arrays + * of strings. + * + * Arrays are sparse doubly-linked lists. An element's index is stored + * with it. + * + * Chet Ramey + * chet@ins.cwru.edu + */ + +/* Copyright (C) 1997-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (ARRAY_VARS) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "bashansi.h" + +#include "shell.h" +#include "array.h" +#include "common.h" + +#define ADD_BEFORE(ae, new) \ + do { \ + ae->prev->next = new; \ + new->prev = ae->prev; \ + ae->prev = new; \ + new->next = ae; \ + } while(0) + +#define ADD_AFTER(ae, new) \ + do { \ + ae->next->prev = new; \ + new->next = ae->next; \ + new->prev = ae; \ + ae->next = new; \ + } while (0) + +static char *array_to_string_internal PARAMS((ARRAY_ELEMENT *, ARRAY_ELEMENT *, char *, int)); + +static char *spacesep = " "; + +#define IS_LASTREF(a) (a->lastref) + +#define LASTREF_START(a, i) \ + (IS_LASTREF(a) && i >= element_index(a->lastref)) ? a->lastref \ + : element_forw(a->head) + +#define LASTREF(a) (a->lastref ? a->lastref : element_forw(a->head)) + +#define INVALIDATE_LASTREF(a) a->lastref = 0 +#define SET_LASTREF(a, e) a->lastref = (e) +#define UNSET_LASTREF(a) a->lastref = 0; + +ARRAY * +array_create() +{ + ARRAY *r; + ARRAY_ELEMENT *head; + + r = (ARRAY *)xmalloc(sizeof(ARRAY)); + r->max_index = -1; + r->num_elements = 0; + r->lastref = (ARRAY_ELEMENT *)0; + head = array_create_element(-1, (char *)NULL); /* dummy head */ + head->prev = head->next = head; + r->head = head; + return(r); +} + +void +array_flush (a) +ARRAY *a; +{ + register ARRAY_ELEMENT *r, *r1; + + if (a == 0) + return; + for (r = element_forw(a->head); r != a->head; ) { + r1 = element_forw(r); + array_dispose_element(r); + r = r1; + } + a->head->next = a->head->prev = a->head; + a->max_index = -1; + a->num_elements = 0; + INVALIDATE_LASTREF(a); +} + +void +array_dispose(a) +ARRAY *a; +{ + if (a == 0) + return; + array_flush (a); + array_dispose_element(a->head); + free(a); +} + +ARRAY * +array_copy(a) +ARRAY *a; +{ + ARRAY *a1; + ARRAY_ELEMENT *ae, *new; + + if (a == 0) + return((ARRAY *) NULL); + a1 = array_create(); + a1->max_index = a->max_index; + a1->num_elements = a->num_elements; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + new = array_create_element(element_index(ae), element_value(ae)); + ADD_BEFORE(a1->head, new); + if (ae == LASTREF(a)) + SET_LASTREF(a1, new); + } + return(a1); +} + +/* + * Make and return a new array composed of the elements in array A from + * S to E, inclusive. + */ +ARRAY * +array_slice(array, s, e) +ARRAY *array; +ARRAY_ELEMENT *s, *e; +{ + ARRAY *a; + ARRAY_ELEMENT *p, *n; + int i; + arrayind_t mi; + + a = array_create (); + + for (mi = 0, p = s, i = 0; p != e; p = element_forw(p), i++) { + n = array_create_element (element_index(p), element_value(p)); + ADD_BEFORE(a->head, n); + mi = element_index(n); + } + a->num_elements = i; + a->max_index = mi; + return a; +} + +/* + * Walk the array, calling FUNC once for each element, with the array + * element as the argument. + */ +void +array_walk(a, func, udata) +ARRAY *a; +sh_ae_map_func_t *func; +void *udata; +{ + register ARRAY_ELEMENT *ae; + + if (a == 0 || array_empty(a)) + return; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) + if ((*func)(ae, udata) < 0) + return; +} + +/* + * Shift the array A N elements to the left. Delete the first N elements + * and subtract N from the indices of the remaining elements. If FLAGS + * does not include AS_DISPOSE, this returns a singly-linked null-terminated + * list of elements so the caller can dispose of the chain. If FLAGS + * includes AS_DISPOSE, this function disposes of the shifted-out elements + * and returns NULL. + */ +ARRAY_ELEMENT * +array_shift(a, n, flags) +ARRAY *a; +int n, flags; +{ + register ARRAY_ELEMENT *ae, *ret; + register int i; + + if (a == 0 || array_empty(a) || n <= 0) + return ((ARRAY_ELEMENT *)NULL); + + INVALIDATE_LASTREF(a); + for (i = 0, ret = ae = element_forw(a->head); ae != a->head && i < n; ae = element_forw(ae), i++) + ; + if (ae == a->head) { + /* Easy case; shifting out all of the elements */ + if (flags & AS_DISPOSE) { + array_flush (a); + return ((ARRAY_ELEMENT *)NULL); + } + for (ae = ret; element_forw(ae) != a->head; ae = element_forw(ae)) + ; + element_forw(ae) = (ARRAY_ELEMENT *)NULL; + a->head->next = a->head->prev = a->head; + a->max_index = -1; + a->num_elements = 0; + return ret; + } + /* + * ae now points to the list of elements we want to retain. + * ret points to the list we want to either destroy or return. + */ + ae->prev->next = (ARRAY_ELEMENT *)NULL; /* null-terminate RET */ + + a->head->next = ae; /* slice RET out of the array */ + ae->prev = a->head; + + for ( ; ae != a->head; ae = element_forw(ae)) + element_index(ae) -= n; /* renumber retained indices */ + + a->num_elements -= n; /* modify bookkeeping information */ + a->max_index = element_index(a->head->prev); + + if (flags & AS_DISPOSE) { + for (ae = ret; ae; ) { + ret = element_forw(ae); + array_dispose_element(ae); + ae = ret; + } + return ((ARRAY_ELEMENT *)NULL); + } + + return ret; +} + +/* + * Shift array A right N indices. If S is non-null, it becomes the value of + * the new element 0. Returns the number of elements in the array after the + * shift. + */ +int +array_rshift (a, n, s) +ARRAY *a; +int n; +char *s; +{ + register ARRAY_ELEMENT *ae, *new; + + if (a == 0 || (array_empty(a) && s == 0)) + return 0; + else if (n <= 0) + return (a->num_elements); + + ae = element_forw(a->head); + if (s) { + new = array_create_element(0, s); + ADD_BEFORE(ae, new); + a->num_elements++; + if (array_num_elements(a) == 1) { /* array was empty */ + a->max_index = 0; + return 1; + } + } + + /* + * Renumber all elements in the array except the one we just added. + */ + for ( ; ae != a->head; ae = element_forw(ae)) + element_index(ae) += n; + + a->max_index = element_index(a->head->prev); + + INVALIDATE_LASTREF(a); + return (a->num_elements); +} + +ARRAY_ELEMENT * +array_unshift_element(a) +ARRAY *a; +{ + return (array_shift (a, 1, 0)); +} + +int +array_shift_element(a, v) +ARRAY *a; +char *v; +{ + return (array_rshift (a, 1, v)); +} + +ARRAY * +array_quote(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { + t = quote_string (a->value); + FREE(a->value); + a->value = t; + } + return array; +} + +ARRAY * +array_quote_escapes(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { + t = quote_escapes (a->value); + FREE(a->value); + a->value = t; + } + return array; +} + +ARRAY * +array_dequote(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { + t = dequote_string (a->value); + FREE(a->value); + a->value = t; + } + return array; +} + +ARRAY * +array_dequote_escapes(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + char *t; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { + t = dequote_escapes (a->value); + FREE(a->value); + a->value = t; + } + return array; +} + +ARRAY * +array_remove_quoted_nulls(array) +ARRAY *array; +{ + ARRAY_ELEMENT *a; + + if (array == 0 || array_head(array) == 0 || array_empty(array)) + return (ARRAY *)NULL; + for (a = element_forw(array->head); a != array->head; a = element_forw(a)) + a->value = remove_quoted_nulls (a->value); + return array; +} + +/* + * Return a string whose elements are the members of array A beginning at + * index START and spanning NELEM members. Null elements are counted. + * Since arrays are sparse, unset array elements are not counted. + */ +char * +array_subrange (a, start, nelem, starsub, quoted, pflags) +ARRAY *a; +arrayind_t start, nelem; +int starsub, quoted, pflags; +{ + ARRAY *a2; + ARRAY_ELEMENT *h, *p; + arrayind_t i; + char *t; + WORD_LIST *wl; + + p = a ? array_head (a) : 0; + if (p == 0 || array_empty (a) || start > array_max_index(a)) + return ((char *)NULL); + + /* + * Find element with index START. If START corresponds to an unset + * element (arrays can be sparse), use the first element whose index + * is >= START. If START is < 0, we count START indices back from + * the end of A (not elements, even with sparse arrays -- START is an + * index). + */ + for (p = element_forw(p); p != array_head(a) && start > element_index(p); p = element_forw(p)) + ; + + if (p == a->head) + return ((char *)NULL); + + /* Starting at P, take NELEM elements, inclusive. */ + for (i = 0, h = p; p != a->head && i < nelem; i++, p = element_forw(p)) + ; + + a2 = array_slice(a, h, p); + + wl = array_to_word_list(a2); + array_dispose(a2); + if (wl == 0) + return (char *)NULL; + t = string_list_pos_params(starsub ? '*' : '@', wl, quoted, pflags); /* XXX */ + dispose_words(wl); + + return t; +} + +char * +array_patsub (a, pat, rep, mflags) +ARRAY *a; +char *pat, *rep; +int mflags; +{ + char *t; + int pchar, qflags, pflags; + WORD_LIST *wl, *save; + + if (a == 0 || array_head(a) == 0 || array_empty(a)) + return ((char *)NULL); + + wl = array_to_word_list(a); + if (wl == 0) + return (char *)NULL; + + for (save = wl; wl; wl = wl->next) { + t = pat_subst (wl->word->word, pat, rep, mflags); + FREE (wl->word->word); + wl->word->word = t; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + pflags = (mflags & MATCH_ASSIGNRHS) ? PF_ASSIGNRHS : 0; + + t = string_list_pos_params (pchar, save, qflags, pflags); + dispose_words(save); + + return t; +} + +char * +array_modcase (a, pat, modop, mflags) +ARRAY *a; +char *pat; +int modop; +int mflags; +{ + char *t; + int pchar, qflags, pflags; + WORD_LIST *wl, *save; + + if (a == 0 || array_head(a) == 0 || array_empty(a)) + return ((char *)NULL); + + wl = array_to_word_list(a); + if (wl == 0) + return ((char *)NULL); + + for (save = wl; wl; wl = wl->next) { + t = sh_modcase(wl->word->word, pat, modop); + FREE(wl->word->word); + wl->word->word = t; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + pflags = (mflags & MATCH_ASSIGNRHS) ? PF_ASSIGNRHS : 0; + + t = string_list_pos_params (pchar, save, qflags, pflags); + dispose_words(save); + + return t; +} + +/* + * Allocate and return a new array element with index INDEX and value + * VALUE. + */ +ARRAY_ELEMENT * +array_create_element(indx, value) +arrayind_t indx; +char *value; +{ + ARRAY_ELEMENT *r; + + r = (ARRAY_ELEMENT *)xmalloc(sizeof(ARRAY_ELEMENT)); + r->ind = indx; + r->value = value ? savestring(value) : (char *)NULL; + r->next = r->prev = (ARRAY_ELEMENT *) NULL; + return(r); +} + +#ifdef INCLUDE_UNUSED +ARRAY_ELEMENT * +array_copy_element(ae) +ARRAY_ELEMENT *ae; +{ + return(ae ? array_create_element(element_index(ae), element_value(ae)) + : (ARRAY_ELEMENT *) NULL); +} +#endif + +void +array_dispose_element(ae) +ARRAY_ELEMENT *ae; +{ + if (ae) { + FREE(ae->value); + free(ae); + } +} + +/* + * Add a new element with index I and value V to array A (a[i] = v). + */ +int +array_insert(a, i, v) +ARRAY *a; +arrayind_t i; +char *v; +{ + register ARRAY_ELEMENT *new, *ae, *start; + arrayind_t startind; + int direction; + + if (a == 0) + return(-1); + new = array_create_element(i, v); + if (i > array_max_index(a)) { + /* + * Hook onto the end. This also works for an empty array. + * Fast path for the common case of allocating arrays + * sequentially. + */ + ADD_BEFORE(a->head, new); + a->max_index = i; + a->num_elements++; + SET_LASTREF(a, new); + return(0); + } else if (i < array_first_index(a)) { + /* Hook at the beginning */ + ADD_AFTER(a->head, new); + a->num_elements++; + SET_LASTREF(a, new); + return(0); + } +#if OPTIMIZE_SEQUENTIAL_ARRAY_ASSIGNMENT + /* + * Otherwise we search for the spot to insert it. The lastref + * handle optimizes the case of sequential or almost-sequential + * assignments that are not at the end of the array. + */ + start = LASTREF(a); + /* Use same strategy as array_reference to avoid paying large penalty + for semi-random assignment pattern. */ + startind = element_index(start); + if (i < startind/2) { + start = element_forw(a->head); + startind = element_index(start); + direction = 1; + } else if (i >= startind) { + direction = 1; + } else { + direction = -1; + } +#else + start = element_forw(ae->head); + startind = element_index(start); + direction = 1; +#endif + for (ae = start; ae != a->head; ) { + if (element_index(ae) == i) { + /* + * Replacing an existing element. + */ + free(element_value(ae)); + /* Just swap in the new value */ + ae->value = new->value; + new->value = 0; + array_dispose_element(new); + SET_LASTREF(a, ae); + return(0); + } else if (direction == 1 && element_index(ae) > i) { + ADD_BEFORE(ae, new); + a->num_elements++; + SET_LASTREF(a, new); + return(0); + } else if (direction == -1 && element_index(ae) < i) { + ADD_AFTER(ae, new); + a->num_elements++; + SET_LASTREF(a, new); + return(0); + } + ae = direction == 1 ? element_forw(ae) : element_back(ae); + } + array_dispose_element(new); + INVALIDATE_LASTREF(a); + return (-1); /* problem */ +} + +/* + * Delete the element with index I from array A and return it so the + * caller can dispose of it. + */ +ARRAY_ELEMENT * +array_remove(a, i) +ARRAY *a; +arrayind_t i; +{ + register ARRAY_ELEMENT *ae, *start; + arrayind_t startind; + int direction; + + if (a == 0 || array_empty(a)) + return((ARRAY_ELEMENT *) NULL); + if (i > array_max_index(a) || i < array_first_index(a)) + return((ARRAY_ELEMENT *)NULL); /* Keep roving pointer into array to optimize sequential access */ + start = LASTREF(a); + /* Use same strategy as array_reference to avoid paying large penalty + for semi-random assignment pattern. */ + startind = element_index(start); + if (i < startind/2) { + start = element_forw(a->head); + startind = element_index(start); + direction = 1; + } else if (i >= startind) { + direction = 1; + } else { + direction = -1; + } + for (ae = start; ae != a->head; ) { + if (element_index(ae) == i) { + ae->next->prev = ae->prev; + ae->prev->next = ae->next; + a->num_elements--; + if (i == array_max_index(a)) + a->max_index = element_index(ae->prev); +#if 0 + INVALIDATE_LASTREF(a); +#else + if (ae->next != a->head) + SET_LASTREF(a, ae->next); + else if (ae->prev != a->head) + SET_LASTREF(a, ae->prev); + else + INVALIDATE_LASTREF(a); +#endif + return(ae); + } + ae = (direction == 1) ? element_forw(ae) : element_back(ae); + if (direction == 1 && element_index(ae) > i) + break; + else if (direction == -1 && element_index(ae) < i) + break; + } + return((ARRAY_ELEMENT *) NULL); +} + +/* + * Return the value of a[i]. + */ +char * +array_reference(a, i) +ARRAY *a; +arrayind_t i; +{ + register ARRAY_ELEMENT *ae, *start; + arrayind_t startind; + int direction; + + if (a == 0 || array_empty(a)) + return((char *) NULL); + if (i > array_max_index(a) || i < array_first_index(a)) + return((char *)NULL); /* Keep roving pointer into array to optimize sequential access */ + start = LASTREF(a); /* lastref pointer */ + startind = element_index(start); + if (i < startind/2) { /* XXX - guess */ + start = element_forw(a->head); + startind = element_index(start); + direction = 1; + } else if (i >= startind) { + direction = 1; + } else { + direction = -1; + } + for (ae = start; ae != a->head; ) { + if (element_index(ae) == i) { + SET_LASTREF(a, ae); + return(element_value(ae)); + } + ae = (direction == 1) ? element_forw(ae) : element_back(ae); + /* Take advantage of index ordering to short-circuit */ + /* If we don't find it, set the lastref pointer to the element + that's `closest', assuming that the unsuccessful reference + will quickly be followed by an assignment. No worse than + not changing it from the previous value or resetting it. */ + if (direction == 1 && element_index(ae) > i) { + start = ae; /* use for SET_LASTREF below */ + break; + } else if (direction == -1 && element_index(ae) < i) { + start = ae; /* use for SET_LASTREF below */ + break; + } + } +#if 0 + UNSET_LASTREF(a); +#else + SET_LASTREF(a, start); +#endif + return((char *) NULL); +} + +/* Convenience routines for the shell to translate to and from the form used + by the rest of the code. */ + +WORD_LIST * +array_to_word_list(a) +ARRAY *a; +{ + WORD_LIST *list; + ARRAY_ELEMENT *ae; + + if (a == 0 || array_empty(a)) + return((WORD_LIST *)NULL); + list = (WORD_LIST *)NULL; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) + list = make_word_list (make_bare_word(element_value(ae)), list); + return (REVERSE_LIST(list, WORD_LIST *)); +} + +ARRAY * +array_from_word_list (list) +WORD_LIST *list; +{ + ARRAY *a; + + if (list == 0) + return((ARRAY *)NULL); + a = array_create(); + return (array_assign_list (a, list)); +} + +WORD_LIST * +array_keys_to_word_list(a) +ARRAY *a; +{ + WORD_LIST *list; + ARRAY_ELEMENT *ae; + char *t; + + if (a == 0 || array_empty(a)) + return((WORD_LIST *)NULL); + list = (WORD_LIST *)NULL; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + t = itos(element_index(ae)); + list = make_word_list (make_bare_word(t), list); + free(t); + } + return (REVERSE_LIST(list, WORD_LIST *)); +} + +WORD_LIST * +array_to_kvpair_list(a) +ARRAY *a; +{ + WORD_LIST *list; + ARRAY_ELEMENT *ae; + char *k, *v; + + if (a == 0 || array_empty(a)) + return((WORD_LIST *)NULL); + list = (WORD_LIST *)NULL; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + k = itos(element_index(ae)); + v = element_value(ae); + list = make_word_list (make_bare_word(k), list); + list = make_word_list (make_bare_word(v), list); + free(k); + } + return (REVERSE_LIST(list, WORD_LIST *)); +} + +ARRAY * +array_assign_list (array, list) +ARRAY *array; +WORD_LIST *list; +{ + register WORD_LIST *l; + register arrayind_t i; + + for (l = list, i = 0; l; l = l->next, i++) + array_insert(array, i, l->word->word); + return array; +} + +char ** +array_to_argv (a, countp) +ARRAY *a; +int *countp; +{ + char **ret, *t; + int i; + ARRAY_ELEMENT *ae; + + if (a == 0 || array_empty(a)) { + if (countp) + *countp = 0; + return ((char **)NULL); + } + ret = strvec_create (array_num_elements (a) + 1); + i = 0; + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + t = element_value (ae); + if (t) + ret[i++] = savestring (t); + } + ret[i] = (char *)NULL; + if (countp) + *countp = i; + return (ret); +} + +ARRAY * +array_from_argv(a, vec, count) +ARRAY *a; +char **vec; +int count; +{ + arrayind_t i; + ARRAY_ELEMENT *ae; + char *t; + + if (a == 0 || array_num_elements (a) == 0) + { + for (i = 0; i < count; i++) + array_insert (a, i, t); + return a; + } + + /* Fast case */ + if (array_num_elements (a) == count && count == 1) + { + ae = element_forw (a->head); + t = vec[0] ? savestring (vec[0]) : 0; + ARRAY_ELEMENT_REPLACE (ae, t); + } + else if (array_num_elements (a) <= count) + { + /* modify in array_num_elements members in place, then add */ + ae = a->head; + for (i = 0; i < array_num_elements (a); i++) + { + ae = element_forw (ae); + t = vec[0] ? savestring (vec[0]) : 0; + ARRAY_ELEMENT_REPLACE (ae, t); + } + /* add any more */ + for ( ; i < count; i++) + array_insert (a, i, vec[i]); + } + else + { + /* deleting elements. it's faster to rebuild the array. */ + array_flush (a); + for (i = 0; i < count; i++) + array_insert (a, i, vec[i]); + } + + return a; +} + +/* + * Return a string that is the concatenation of the elements in A from START + * to END, separated by SEP. + */ +static char * +array_to_string_internal (start, end, sep, quoted) +ARRAY_ELEMENT *start, *end; +char *sep; +int quoted; +{ + char *result, *t; + ARRAY_ELEMENT *ae; + int slen, rsize, rlen, reg; + + if (start == end) /* XXX - should not happen */ + return ((char *)NULL); + + slen = strlen(sep); + result = NULL; + for (rsize = rlen = 0, ae = start; ae != end; ae = element_forw(ae)) { + if (rsize == 0) + result = (char *)xmalloc (rsize = 64); + if (element_value(ae)) { + t = quoted ? quote_string(element_value(ae)) : element_value(ae); + reg = strlen(t); + RESIZE_MALLOCED_BUFFER (result, rlen, (reg + slen + 2), + rsize, rsize); + strcpy(result + rlen, t); + rlen += reg; + if (quoted) + free(t); + /* + * Add a separator only after non-null elements. + */ + if (element_forw(ae) != end) { + strcpy(result + rlen, sep); + rlen += slen; + } + } + } + if (result) + result[rlen] = '\0'; /* XXX */ + return(result); +} + +char * +array_to_kvpair (a, quoted) +ARRAY *a; +int quoted; +{ + char *result, *valstr, *is; + char indstr[INT_STRLEN_BOUND(intmax_t) + 1]; + ARRAY_ELEMENT *ae; + int rsize, rlen, elen; + + if (a == 0 || array_empty (a)) + return((char *)NULL); + + result = (char *)xmalloc (rsize = 128); + result[rlen = 0] = '\0'; + + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + is = inttostr (element_index(ae), indstr, sizeof(indstr)); + valstr = element_value (ae) ? + (ansic_shouldquote (element_value (ae)) ? + ansic_quote (element_value(ae), 0, (int *)0) : + sh_double_quote (element_value (ae))) + : (char *)NULL; + elen = STRLEN (is) + 8 + STRLEN (valstr); + RESIZE_MALLOCED_BUFFER (result, rlen, (elen + 1), rsize, rsize); + + strcpy (result + rlen, is); + rlen += STRLEN (is); + result[rlen++] = ' '; + if (valstr) { + strcpy (result + rlen, valstr); + rlen += STRLEN (valstr); + } else { + strcpy (result + rlen, "\"\""); + rlen += 2; + } + + if (element_forw(ae) != a->head) + result[rlen++] = ' '; + + FREE (valstr); + } + RESIZE_MALLOCED_BUFFER (result, rlen, 1, rsize, 8); + result[rlen] = '\0'; + + if (quoted) { + /* This is not as efficient as it could be... */ + valstr = sh_single_quote (result); + free (result); + result = valstr; + } + return(result); +} + +char * +array_to_assign (a, quoted) +ARRAY *a; +int quoted; +{ + char *result, *valstr, *is; + char indstr[INT_STRLEN_BOUND(intmax_t) + 1]; + ARRAY_ELEMENT *ae; + int rsize, rlen, elen; + + if (a == 0 || array_empty (a)) + return((char *)NULL); + + result = (char *)xmalloc (rsize = 128); + result[0] = '('; + rlen = 1; + + for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { + is = inttostr (element_index(ae), indstr, sizeof(indstr)); + valstr = element_value (ae) ? + (ansic_shouldquote (element_value (ae)) ? + ansic_quote (element_value(ae), 0, (int *)0) : + sh_double_quote (element_value (ae))) + : (char *)NULL; + elen = STRLEN (is) + 8 + STRLEN (valstr); + RESIZE_MALLOCED_BUFFER (result, rlen, (elen + 1), rsize, rsize); + + result[rlen++] = '['; + strcpy (result + rlen, is); + rlen += STRLEN (is); + result[rlen++] = ']'; + result[rlen++] = '='; + if (valstr) { + strcpy (result + rlen, valstr); + rlen += STRLEN (valstr); + } + + if (element_forw(ae) != a->head) + result[rlen++] = ' '; + + FREE (valstr); + } + RESIZE_MALLOCED_BUFFER (result, rlen, 1, rsize, 8); + result[rlen++] = ')'; + result[rlen] = '\0'; + if (quoted) { + /* This is not as efficient as it could be... */ + valstr = sh_single_quote (result); + free (result); + result = valstr; + } + return(result); +} + +char * +array_to_string (a, sep, quoted) +ARRAY *a; +char *sep; +int quoted; +{ + if (a == 0) + return((char *)NULL); + if (array_empty(a)) + return(savestring("")); + return (array_to_string_internal (element_forw(a->head), a->head, sep, quoted)); +} + +#if defined (INCLUDE_UNUSED) || defined (TEST_ARRAY) +/* + * Return an array consisting of elements in S, separated by SEP + */ +ARRAY * +array_from_string(s, sep) +char *s, *sep; +{ + ARRAY *a; + WORD_LIST *w; + + if (s == 0) + return((ARRAY *)NULL); + w = list_string (s, sep, 0); + if (w == 0) + return((ARRAY *)NULL); + a = array_from_word_list (w); + return (a); +} +#endif + +#if defined (TEST_ARRAY) +/* + * To make a running version, compile -DTEST_ARRAY and link with: + * xmalloc.o syntax.o lib/malloc/libmalloc.a lib/sh/libsh.a + */ +int interrupt_immediately = 0; + +int +signal_is_trapped(s) +int s; +{ + return 0; +} + +void +fatal_error(const char *s, ...) +{ + fprintf(stderr, "array_test: fatal memory error\n"); + abort(); +} + +void +programming_error(const char *s, ...) +{ + fprintf(stderr, "array_test: fatal programming error\n"); + abort(); +} + +WORD_DESC * +make_bare_word (s) +const char *s; +{ + WORD_DESC *w; + + w = (WORD_DESC *)xmalloc(sizeof(WORD_DESC)); + w->word = s ? savestring(s) : savestring (""); + w->flags = 0; + return w; +} + +WORD_LIST * +make_word_list(x, l) +WORD_DESC *x; +WORD_LIST *l; +{ + WORD_LIST *w; + + w = (WORD_LIST *)xmalloc(sizeof(WORD_LIST)); + w->word = x; + w->next = l; + return w; +} + +WORD_LIST * +list_string(s, t, i) +char *s, *t; +int i; +{ + char *r, *a; + WORD_LIST *wl; + + if (s == 0) + return (WORD_LIST *)NULL; + r = savestring(s); + wl = (WORD_LIST *)NULL; + a = strtok(r, t); + while (a) { + wl = make_word_list (make_bare_word(a), wl); + a = strtok((char *)NULL, t); + } + return (REVERSE_LIST (wl, WORD_LIST *)); +} + +GENERIC_LIST * +list_reverse (list) +GENERIC_LIST *list; +{ + register GENERIC_LIST *next, *prev; + + for (prev = 0; list; ) { + next = list->next; + list->next = prev; + prev = list; + list = next; + } + return prev; +} + +char * +pat_subst(s, t, u, i) +char *s, *t, *u; +int i; +{ + return ((char *)NULL); +} + +char * +quote_string(s) +char *s; +{ + return savestring(s); +} + +print_element(ae) +ARRAY_ELEMENT *ae; +{ + char lbuf[INT_STRLEN_BOUND (intmax_t) + 1]; + + printf("array[%s] = %s\n", + inttostr (element_index(ae), lbuf, sizeof (lbuf)), + element_value(ae)); +} + +print_array(a) +ARRAY *a; +{ + printf("\n"); + array_walk(a, print_element, (void *)NULL); +} + +main() +{ + ARRAY *a, *new_a, *copy_of_a; + ARRAY_ELEMENT *ae, *aew; + char *s; + + a = array_create(); + array_insert(a, 1, "one"); + array_insert(a, 7, "seven"); + array_insert(a, 4, "four"); + array_insert(a, 1029, "one thousand twenty-nine"); + array_insert(a, 12, "twelve"); + array_insert(a, 42, "forty-two"); + print_array(a); + s = array_to_string (a, " ", 0); + printf("s = %s\n", s); + copy_of_a = array_from_string(s, " "); + printf("copy_of_a:"); + print_array(copy_of_a); + array_dispose(copy_of_a); + printf("\n"); + free(s); + ae = array_remove(a, 4); + array_dispose_element(ae); + ae = array_remove(a, 1029); + array_dispose_element(ae); + array_insert(a, 16, "sixteen"); + print_array(a); + s = array_to_string (a, " ", 0); + printf("s = %s\n", s); + copy_of_a = array_from_string(s, " "); + printf("copy_of_a:"); + print_array(copy_of_a); + array_dispose(copy_of_a); + printf("\n"); + free(s); + array_insert(a, 2, "two"); + array_insert(a, 1029, "new one thousand twenty-nine"); + array_insert(a, 0, "zero"); + array_insert(a, 134, ""); + print_array(a); + s = array_to_string (a, ":", 0); + printf("s = %s\n", s); + copy_of_a = array_from_string(s, ":"); + printf("copy_of_a:"); + print_array(copy_of_a); + array_dispose(copy_of_a); + printf("\n"); + free(s); + new_a = array_copy(a); + print_array(new_a); + s = array_to_string (new_a, ":", 0); + printf("s = %s\n", s); + copy_of_a = array_from_string(s, ":"); + free(s); + printf("copy_of_a:"); + print_array(copy_of_a); + array_shift(copy_of_a, 2, AS_DISPOSE); + printf("copy_of_a shifted by two:"); + print_array(copy_of_a); + ae = array_shift(copy_of_a, 2, 0); + printf("copy_of_a shifted by two:"); + print_array(copy_of_a); + for ( ; ae; ) { + aew = element_forw(ae); + array_dispose_element(ae); + ae = aew; + } + array_rshift(copy_of_a, 1, (char *)0); + printf("copy_of_a rshift by 1:"); + print_array(copy_of_a); + array_rshift(copy_of_a, 2, "new element zero"); + printf("copy_of_a rshift again by 2 with new element zero:"); + print_array(copy_of_a); + s = array_to_assign(copy_of_a, 0); + printf("copy_of_a=%s\n", s); + free(s); + ae = array_shift(copy_of_a, array_num_elements(copy_of_a), 0); + for ( ; ae; ) { + aew = element_forw(ae); + array_dispose_element(ae); + ae = aew; + } + array_dispose(copy_of_a); + printf("\n"); + array_dispose(a); + array_dispose(new_a); +} + +#endif /* TEST_ARRAY */ +#endif /* ARRAY_VARS */ diff --git a/third_party/bash/array.h b/third_party/bash/array.h new file mode 100644 index 000000000..4214e8b41 --- /dev/null +++ b/third_party/bash/array.h @@ -0,0 +1,182 @@ +/* array.h -- definitions for the interface exported by array.c that allows + the rest of the shell to manipulate array variables. */ + +/* Copyright (C) 1997-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + + +#ifndef _ARRAY_H_ +#define _ARRAY_H_ + +#include "stdc.h" + +typedef intmax_t arrayind_t; + +typedef struct array { + arrayind_t max_index; + arrayind_t num_elements; +#ifdef ALT_ARRAY_IMPLEMENTATION + arrayind_t first_index; + arrayind_t alloc_size; + struct array_element **elements; +#else + struct array_element *head; + struct array_element *lastref; +#endif +} ARRAY; + +typedef struct array_element { + arrayind_t ind; + char *value; +#ifndef ALT_ARRAY_IMPLEMENTATION + struct array_element *next, *prev; +#endif +} ARRAY_ELEMENT; + +#define ARRAY_DEFAULT_SIZE 1024 + +typedef int sh_ae_map_func_t PARAMS((ARRAY_ELEMENT *, void *)); + +/* Basic operations on entire arrays */ +#ifdef ALT_ARRAY_IMPLEMENTATION +extern void array_alloc PARAMS((ARRAY *, arrayind_t)); +extern void array_resize PARAMS((ARRAY *, arrayind_t)); +extern void array_expand PARAMS((ARRAY *, arrayind_t)); +extern void array_dispose_elements PARAMS((ARRAY_ELEMENT **)); +#endif +extern ARRAY *array_create PARAMS((void)); +extern void array_flush PARAMS((ARRAY *)); +extern void array_dispose PARAMS((ARRAY *)); +extern ARRAY *array_copy PARAMS((ARRAY *)); +#ifndef ALT_ARRAY_IMPLEMENTATION +extern ARRAY *array_slice PARAMS((ARRAY *, ARRAY_ELEMENT *, ARRAY_ELEMENT *)); +#else +extern ARRAY *array_slice PARAMS((ARRAY *, arrayind_t, arrayind_t)); +#endif + +extern void array_walk PARAMS((ARRAY *, sh_ae_map_func_t *, void *)); + +#ifndef ALT_ARRAY_IMPLEMENTATION +extern ARRAY_ELEMENT *array_shift PARAMS((ARRAY *, int, int)); +#else +extern ARRAY_ELEMENT **array_shift PARAMS((ARRAY *, int, int)); +#endif +extern int array_rshift PARAMS((ARRAY *, int, char *)); +extern ARRAY_ELEMENT *array_unshift_element PARAMS((ARRAY *)); +extern int array_shift_element PARAMS((ARRAY *, char *)); + +extern ARRAY *array_quote PARAMS((ARRAY *)); +extern ARRAY *array_quote_escapes PARAMS((ARRAY *)); +extern ARRAY *array_dequote PARAMS((ARRAY *)); +extern ARRAY *array_dequote_escapes PARAMS((ARRAY *)); +extern ARRAY *array_remove_quoted_nulls PARAMS((ARRAY *)); + +extern char *array_subrange PARAMS((ARRAY *, arrayind_t, arrayind_t, int, int, int)); +extern char *array_patsub PARAMS((ARRAY *, char *, char *, int)); +extern char *array_modcase PARAMS((ARRAY *, char *, int, int)); + +/* Basic operations on array elements. */ +extern ARRAY_ELEMENT *array_create_element PARAMS((arrayind_t, char *)); +extern ARRAY_ELEMENT *array_copy_element PARAMS((ARRAY_ELEMENT *)); +extern void array_dispose_element PARAMS((ARRAY_ELEMENT *)); + +extern int array_insert PARAMS((ARRAY *, arrayind_t, char *)); +extern ARRAY_ELEMENT *array_remove PARAMS((ARRAY *, arrayind_t)); +extern char *array_reference PARAMS((ARRAY *, arrayind_t)); + +/* Converting to and from arrays */ +extern WORD_LIST *array_to_word_list PARAMS((ARRAY *)); +extern ARRAY *array_from_word_list PARAMS((WORD_LIST *)); +extern WORD_LIST *array_keys_to_word_list PARAMS((ARRAY *)); +extern WORD_LIST *array_to_kvpair_list PARAMS((ARRAY *)); + +extern ARRAY *array_assign_list PARAMS((ARRAY *, WORD_LIST *)); + +extern char **array_to_argv PARAMS((ARRAY *, int *)); +extern ARRAY *array_from_argv PARAMS((ARRAY *, char **, int)); + +extern char *array_to_kvpair PARAMS((ARRAY *, int)); +extern char *array_to_assign PARAMS((ARRAY *, int)); +extern char *array_to_string PARAMS((ARRAY *, char *, int)); +extern ARRAY *array_from_string PARAMS((char *, char *)); + +/* Flags for array_shift */ +#define AS_DISPOSE 0x01 + +#define array_num_elements(a) ((a)->num_elements) +#define array_max_index(a) ((a)->max_index) +#ifndef ALT_ARRAY_IMPLEMENTATION +#define array_first_index(a) ((a)->head->next->ind) +#define array_head(a) ((a)->head) +#define array_alloc_size(a) ((a)->alloc_size) +#else +#define array_first_index(a) ((a)->first_index) +#define array_head(a) ((a)->elements) +#endif +#define array_empty(a) ((a)->num_elements == 0) + +#define element_value(ae) ((ae)->value) +#define element_index(ae) ((ae)->ind) + +#ifndef ALT_ARRAY_IMPLEMENTATION +#define element_forw(ae) ((ae)->next) +#define element_back(ae) ((ae)->prev) +#else +extern arrayind_t element_forw PARAMS((ARRAY *, arrayind_t)); +extern arrayind_t element_back PARAMS((ARRAY *, arrayind_t)); +#endif + + +#define set_element_value(ae, val) ((ae)->value = (val)) + +#ifdef ALT_ARRAY_IMPLEMENTATION +#define set_first_index(a, i) ((a)->first_index = (i)) +#endif + +#define set_max_index(a, i) ((a)->max_index = (i)) +#define set_num_elements(a, n) ((a)->num_elements = (n)) + +/* Convenience */ +#define array_push(a,v) \ + do { array_rshift ((a), 1, (v)); } while (0) +#define array_pop(a) \ + do { array_shift ((a), 1, AS_DISPOSE); } while (0) + +#define GET_ARRAY_FROM_VAR(n, v, a) \ + do { \ + (v) = find_variable (n); \ + (a) = ((v) && array_p ((v))) ? array_cell (v) : (ARRAY *)0; \ + } while (0) + +#define ARRAY_ELEMENT_REPLACE(ae, v) \ + do { \ + free ((ae)->value); \ + (ae)->value = (v); \ + } while (0) + +#ifdef ALT_ARRAY_IMPLEMENTATION +#define ARRAY_VALUE_REPLACE(a, i, v) \ + ARRAY_ELEMENT_REPLACE((a)->elements[(i)], (v)) +#endif + +#define ALL_ELEMENT_SUB(c) ((c) == '@' || (c) == '*') + +/* In eval.c, but uses ARRAY * */ +extern int execute_array_command PARAMS((ARRAY *, void *)); + +#endif /* _ARRAY_H_ */ diff --git a/third_party/bash/arrayfunc.c b/third_party/bash/arrayfunc.c new file mode 100644 index 000000000..db8e07f3f --- /dev/null +++ b/third_party/bash/arrayfunc.c @@ -0,0 +1,1699 @@ +/* arrayfunc.c -- High-level array functions used by other parts of the shell. */ + +/* Copyright (C) 2001-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (ARRAY_VARS) + +#if defined (HAVE_UNISTD_H) +# include +#endif +#include + +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "pathexp.h" + +#include "shmbutil.h" +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include /* mbschr */ +#endif + +#include "common.h" + +#ifndef LBRACK +# define LBRACK '[' +# define RBRACK ']' +#endif + +/* This variable means to not expand associative array subscripts more than + once, when performing variable expansion. */ +int assoc_expand_once = 0; + +/* Ditto for indexed array subscripts -- currently unused */ +int array_expand_once = 0; + +static SHELL_VAR *bind_array_var_internal PARAMS((SHELL_VAR *, arrayind_t, char *, char *, int)); +static SHELL_VAR *assign_array_element_internal PARAMS((SHELL_VAR *, char *, char *, char *, int, char *, int, array_eltstate_t *)); + +static void assign_assoc_from_kvlist PARAMS((SHELL_VAR *, WORD_LIST *, HASH_TABLE *, int)); + +static char *quote_assign PARAMS((const char *)); +static void quote_array_assignment_chars PARAMS((WORD_LIST *)); +static char *quote_compound_array_word PARAMS((char *, int)); +static char *array_value_internal PARAMS((const char *, int, int, array_eltstate_t *)); + +/* Standard error message to use when encountering an invalid array subscript */ +const char * const bash_badsub_errmsg = N_("bad array subscript"); + +/* **************************************************************** */ +/* */ +/* Functions to manipulate array variables and perform assignments */ +/* */ +/* **************************************************************** */ + +/* Convert a shell variable to an array variable. The original value is + saved as array[0]. */ +SHELL_VAR * +convert_var_to_array (var) + SHELL_VAR *var; +{ + char *oldval; + ARRAY *array; + + oldval = value_cell (var); + array = array_create (); + if (oldval) + array_insert (array, 0, oldval); + + FREE (value_cell (var)); + var_setarray (var, array); + + /* these aren't valid anymore */ + var->dynamic_value = (sh_var_value_func_t *)NULL; + var->assign_func = (sh_var_assign_func_t *)NULL; + + INVALIDATE_EXPORTSTR (var); + if (exported_p (var)) + array_needs_making++; + + VSETATTR (var, att_array); + if (oldval) + VUNSETATTR (var, att_invisible); + + /* Make sure it's not marked as an associative array any more */ + VUNSETATTR (var, att_assoc); + + /* Since namerefs can't be array variables, turn off nameref attribute */ + VUNSETATTR (var, att_nameref); + + return var; +} + +/* Convert a shell variable to an array variable. The original value is + saved as array[0]. */ +SHELL_VAR * +convert_var_to_assoc (var) + SHELL_VAR *var; +{ + char *oldval; + HASH_TABLE *hash; + + oldval = value_cell (var); + hash = assoc_create (0); + if (oldval) + assoc_insert (hash, savestring ("0"), oldval); + + FREE (value_cell (var)); + var_setassoc (var, hash); + + /* these aren't valid anymore */ + var->dynamic_value = (sh_var_value_func_t *)NULL; + var->assign_func = (sh_var_assign_func_t *)NULL; + + INVALIDATE_EXPORTSTR (var); + if (exported_p (var)) + array_needs_making++; + + VSETATTR (var, att_assoc); + if (oldval) + VUNSETATTR (var, att_invisible); + + /* Make sure it's not marked as an indexed array any more */ + VUNSETATTR (var, att_array); + + /* Since namerefs can't be array variables, turn off nameref attribute */ + VUNSETATTR (var, att_nameref); + + return var; +} + +char * +make_array_variable_value (entry, ind, key, value, flags) + SHELL_VAR *entry; + arrayind_t ind; + char *key; + char *value; + int flags; +{ + SHELL_VAR *dentry; + char *newval; + + /* If we're appending, we need the old value of the array reference, so + fake out make_variable_value with a dummy SHELL_VAR */ + if (flags & ASS_APPEND) + { + dentry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + dentry->name = savestring (entry->name); + if (assoc_p (entry)) + newval = assoc_reference (assoc_cell (entry), key); + else + newval = array_reference (array_cell (entry), ind); + if (newval) + dentry->value = savestring (newval); + else + { + dentry->value = (char *)xmalloc (1); + dentry->value[0] = '\0'; + } + dentry->exportstr = 0; + dentry->attributes = entry->attributes & ~(att_array|att_assoc|att_exported); + /* Leave the rest of the members uninitialized; the code doesn't look + at them. */ + newval = make_variable_value (dentry, value, flags); + dispose_variable (dentry); + } + else + newval = make_variable_value (entry, value, flags); + + return newval; +} + +/* Assign HASH[KEY]=VALUE according to FLAGS. ENTRY is an associative array + variable; HASH is the hash table to assign into. HASH may or may not be + the hash table associated with ENTRY; if it's not, the caller takes care + of it. + XXX - make sure that any dynamic associative array variables recreate the + hash table on each assignment. BASH_CMDS and BASH_ALIASES already do this */ +static SHELL_VAR * +bind_assoc_var_internal (entry, hash, key, value, flags) + SHELL_VAR *entry; + HASH_TABLE *hash; + char *key; + char *value; + int flags; +{ + char *newval; + + /* Use the existing array contents to expand the value */ + newval = make_array_variable_value (entry, 0, key, value, flags); + + if (entry->assign_func) + (*entry->assign_func) (entry, newval, 0, key); + else + assoc_insert (hash, key, newval); + + FREE (newval); + + VUNSETATTR (entry, att_invisible); /* no longer invisible */ + + /* check mark_modified_variables if we ever want to export array vars */ + return (entry); +} + +/* Perform ENTRY[IND]=VALUE or ENTRY[KEY]=VALUE. This is not called for every + assignment to an associative array; see assign_compound_array_list below. */ +static SHELL_VAR * +bind_array_var_internal (entry, ind, key, value, flags) + SHELL_VAR *entry; + arrayind_t ind; + char *key; + char *value; + int flags; +{ + char *newval; + + newval = make_array_variable_value (entry, ind, key, value, flags); + + if (entry->assign_func) + (*entry->assign_func) (entry, newval, ind, key); + else if (assoc_p (entry)) + assoc_insert (assoc_cell (entry), key, newval); + else + array_insert (array_cell (entry), ind, newval); + FREE (newval); + + VUNSETATTR (entry, att_invisible); /* no longer invisible */ + + /* check mark_modified_variables if we ever want to export array vars */ + return (entry); +} + +/* Perform an array assignment name[ind]=value. If NAME already exists and + is not an array, and IND is 0, perform name=value instead. If NAME exists + and is not an array, and IND is not 0, convert it into an array with the + existing value as name[0]. + + If NAME does not exist, just create an array variable, no matter what + IND's value may be. */ +SHELL_VAR * +bind_array_variable (name, ind, value, flags) + char *name; + arrayind_t ind; + char *value; + int flags; +{ + SHELL_VAR *entry; + + entry = find_shell_variable (name); + + if (entry == (SHELL_VAR *) 0) + { + /* Is NAME a nameref variable that points to an unset variable? */ + entry = find_variable_nameref_for_create (name, 0); + if (entry == INVALID_NAMEREF_VALUE) + return ((SHELL_VAR *)0); + if (entry && nameref_p (entry)) + entry = make_new_array_variable (nameref_cell (entry)); + } + if (entry == (SHELL_VAR *) 0) + entry = make_new_array_variable (name); + else if ((readonly_p (entry) && (flags&ASS_FORCE) == 0) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name); + return (entry); + } + else if (array_p (entry) == 0) + entry = convert_var_to_array (entry); + + /* ENTRY is an array variable, and ARRAY points to the value. */ + return (bind_array_var_internal (entry, ind, 0, value, flags)); +} + +SHELL_VAR * +bind_array_element (entry, ind, value, flags) + SHELL_VAR *entry; + arrayind_t ind; + char *value; + int flags; +{ + return (bind_array_var_internal (entry, ind, 0, value, flags)); +} + +SHELL_VAR * +bind_assoc_variable (entry, name, key, value, flags) + SHELL_VAR *entry; + char *name; + char *key; + char *value; + int flags; +{ + if ((readonly_p (entry) && (flags&ASS_FORCE) == 0) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name); + return (entry); + } + + return (bind_assoc_var_internal (entry, assoc_cell (entry), key, value, flags)); +} + +inline void +init_eltstate (array_eltstate_t *estatep) +{ + if (estatep) + { + estatep->type = ARRAY_INVALID; + estatep->subtype = 0; + estatep->key = estatep->value = 0; + estatep->ind = INTMAX_MIN; + } +} + +inline void +flush_eltstate (array_eltstate_t *estatep) +{ + if (estatep) + FREE (estatep->key); +} + +/* Parse NAME, a lhs of an assignment statement of the form v[s], and + assign VALUE to that array element by calling bind_array_variable(). + Flags are ASS_ assignment flags */ +SHELL_VAR * +assign_array_element (name, value, flags, estatep) + char *name, *value; + int flags; + array_eltstate_t *estatep; +{ + char *sub, *vname; + int sublen, isassoc, avflags; + SHELL_VAR *entry; + + avflags = 0; + if (flags & ASS_NOEXPAND) + avflags |= AV_NOEXPAND; + if (flags & ASS_ONEWORD) + avflags |= AV_ONEWORD; + vname = array_variable_name (name, avflags, &sub, &sublen); + + if (vname == 0) + return ((SHELL_VAR *)NULL); + + entry = find_variable (vname); + isassoc = entry && assoc_p (entry); + + /* We don't allow assignment to `*' or `@' associative array keys if the + caller hasn't told us the subscript has already been expanded + (ASS_NOEXPAND). If the caller has explicitly told us it's ok + (ASS_ALLOWALLSUB) we allow it. */ + if (((isassoc == 0 || (flags & (ASS_NOEXPAND|ASS_ALLOWALLSUB)) == 0) && + (ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']')) || + (sublen <= 1) || + (sub[sublen] != '\0')) /* sanity check */ + { + free (vname); + err_badarraysub (name); + return ((SHELL_VAR *)NULL); + } + + entry = assign_array_element_internal (entry, name, vname, sub, sublen, value, flags, estatep); + +#if ARRAY_EXPORT + if (entry && exported_p (entry)) + { + INVALIDATE_EXPORTSTR (entry); + array_needs_making = 1; + } +#endif + + free (vname); + return entry; +} + +static SHELL_VAR * +assign_array_element_internal (entry, name, vname, sub, sublen, value, flags, estatep) + SHELL_VAR *entry; + char *name; /* only used for error messages */ + char *vname; + char *sub; + int sublen; + char *value; + int flags; + array_eltstate_t *estatep; +{ + char *akey, *nkey; + arrayind_t ind; + char *newval; + + /* rely on the caller to initialize estatep */ + + if (entry && assoc_p (entry)) + { + sub[sublen-1] = '\0'; + if ((flags & ASS_NOEXPAND) == 0) + akey = expand_subscript_string (sub, 0); /* [ */ + else + akey = savestring (sub); + sub[sublen-1] = ']'; + if (akey == 0 || *akey == 0) + { + err_badarraysub (name); + FREE (akey); + return ((SHELL_VAR *)NULL); + } + if (estatep) + nkey = savestring (akey); /* assoc_insert/assoc_replace frees akey */ + entry = bind_assoc_variable (entry, vname, akey, value, flags); + if (estatep) + { + estatep->type = ARRAY_ASSOC; + estatep->key = nkey; + estatep->value = entry ? assoc_reference (assoc_cell (entry), nkey) : 0; + } + } + else + { + ind = array_expand_index (entry, sub, sublen, 0); + /* negative subscripts to indexed arrays count back from end */ + if (entry && ind < 0) + ind = (array_p (entry) ? array_max_index (array_cell (entry)) : 0) + 1 + ind; + if (ind < 0) + { + err_badarraysub (name); + return ((SHELL_VAR *)NULL); + } + entry = bind_array_variable (vname, ind, value, flags); + if (estatep) + { + estatep->type = ARRAY_INDEXED; + estatep->ind = ind; + estatep->value = entry ? array_reference (array_cell (entry), ind) : 0; + } + } + + return (entry); +} + +/* Find the array variable corresponding to NAME. If there is no variable, + create a new array variable. If the variable exists but is not an array, + convert it to an indexed array. If FLAGS&1 is non-zero, an existing + variable is checked for the readonly or noassign attribute in preparation + for assignment (e.g., by the `read' builtin). If FLAGS&2 is non-zero, we + create an associative array. */ +SHELL_VAR * +find_or_make_array_variable (name, flags) + char *name; + int flags; +{ + SHELL_VAR *var; + + var = find_variable (name); + if (var == 0) + { + /* See if we have a nameref pointing to a variable that hasn't been + created yet. */ + var = find_variable_last_nameref (name, 1); + if (var && nameref_p (var) && invisible_p (var)) + { + internal_warning (_("%s: removing nameref attribute"), name); + VUNSETATTR (var, att_nameref); + } + if (var && nameref_p (var)) + { + if (valid_nameref_value (nameref_cell (var), 2) == 0) + { + sh_invalidid (nameref_cell (var)); + return ((SHELL_VAR *)NULL); + } + var = (flags & 2) ? make_new_assoc_variable (nameref_cell (var)) : make_new_array_variable (nameref_cell (var)); + } + } + + if (var == 0) + var = (flags & 2) ? make_new_assoc_variable (name) : make_new_array_variable (name); + else if ((flags & 1) && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + err_readonly (name); + return ((SHELL_VAR *)NULL); + } + else if ((flags & 2) && array_p (var)) + { + set_exit_status (EXECUTION_FAILURE); + report_error (_("%s: cannot convert indexed to associative array"), name); + return ((SHELL_VAR *)NULL); + } + else if (flags & 2) + var = assoc_p (var) ? var : convert_var_to_assoc (var); + else if (array_p (var) == 0 && assoc_p (var) == 0) + var = convert_var_to_array (var); + + return (var); +} + +/* Perform a compound assignment statement for array NAME, where VALUE is + the text between the parens: NAME=( VALUE ) */ +SHELL_VAR * +assign_array_from_string (name, value, flags) + char *name, *value; + int flags; +{ + SHELL_VAR *var; + int vflags; + + vflags = 1; + if (flags & ASS_MKASSOC) + vflags |= 2; + + var = find_or_make_array_variable (name, vflags); + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (assign_array_var_from_string (var, value, flags)); +} + +/* Sequentially assign the indices of indexed array variable VAR from the + words in LIST. */ +SHELL_VAR * +assign_array_var_from_word_list (var, list, flags) + SHELL_VAR *var; + WORD_LIST *list; + int flags; +{ + register arrayind_t i; + register WORD_LIST *l; + ARRAY *a; + + a = array_cell (var); + i = (flags & ASS_APPEND) ? array_max_index (a) + 1 : 0; + + for (l = list; l; l = l->next, i++) + bind_array_var_internal (var, i, 0, l->word->word, flags & ~ASS_APPEND); + + VUNSETATTR (var, att_invisible); /* no longer invisible */ + + return var; +} + +WORD_LIST * +expand_compound_array_assignment (var, value, flags) + SHELL_VAR *var; + char *value; + int flags; +{ + WORD_LIST *list, *nlist; + char *val; + int ni; + + /* This condition is true when invoked from the declare builtin with a + command like + declare -a d='([1]="" [2]="bdef" [5]="hello world" "test")' */ + if (*value == '(') /*)*/ + { + ni = 1; + val = extract_array_assignment_list (value, &ni); + if (val == 0) + return (WORD_LIST *)NULL; + } + else + val = value; + + /* Expand the value string into a list of words, performing all the + shell expansions including pathname generation and word splitting. */ + /* First we split the string on whitespace, using the shell parser + (ksh93 seems to do this). */ + /* XXX - this needs a rethink, maybe use split_at_delims */ + list = parse_string_to_word_list (val, 1, "array assign"); + + /* If the parser has quoted CTLESC and CTNLNUL with CTLESC in unquoted + words, we need to remove those here because the code below assumes + they are there because they exist in the original word. */ + /* XXX - if we rethink parse_string_to_word_list above, change this. */ + for (nlist = list; nlist; nlist = nlist->next) + if ((nlist->word->flags & W_QUOTED) == 0) + remove_quoted_escapes (nlist->word->word); + + /* Note that we defer expansion of the assignment statements for associative + arrays here, so we don't have to scan the subscript and find the ending + bracket twice. See the caller below. */ + if (var && assoc_p (var)) + { + if (val != value) + free (val); + return list; + } + + /* If we're using [subscript]=value, we need to quote each [ and ] to + prevent unwanted filename expansion. This doesn't need to be done + for associative array expansion, since that uses a different expansion + function (see assign_compound_array_list below). */ + if (list) + quote_array_assignment_chars (list); + + /* Now that we've split it, perform the shell expansions on each + word in the list. */ + nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL; + + dispose_words (list); + + if (val != value) + free (val); + + return nlist; +} + +#if ASSOC_KVPAIR_ASSIGNMENT +static void +assign_assoc_from_kvlist (var, nlist, h, flags) + SHELL_VAR *var; + WORD_LIST *nlist; + HASH_TABLE *h; + int flags; +{ + WORD_LIST *list; + char *akey, *aval, *k, *v; + + for (list = nlist; list; list = list->next) + { + k = list->word->word; + v = list->next ? list->next->word->word : 0; + + if (list->next) + list = list->next; + + akey = expand_subscript_string (k, 0); + if (akey == 0 || *akey == 0) + { + err_badarraysub (k); + FREE (akey); + continue; + } + + aval = expand_subscript_string (v, 0); + if (aval == 0) + { + aval = (char *)xmalloc (1); + aval[0] = '\0'; /* like do_assignment_internal */ + } + + bind_assoc_var_internal (var, h, akey, aval, flags); + free (aval); + } +} + +/* Return non-zero if L appears to be a key-value pair associative array + compound assignment. */ +int +kvpair_assignment_p (l) + WORD_LIST *l; +{ + return (l && (l->word->flags & W_ASSIGNMENT) == 0 && l->word->word[0] != '['); /*]*/ +} + +char * +expand_and_quote_kvpair_word (w) + char *w; +{ + char *r, *s, *t; + + t = w ? expand_subscript_string (w, 0) : 0; + s = (t && strchr (t, CTLESC)) ? quote_escapes (t) : t; + r = sh_single_quote (s ? s : ""); + if (s != t) + free (s); + free (t); + return r; +} +#endif + +/* Callers ensure that VAR is not NULL. Associative array assignments have not + been expanded when this is called, or have been expanded once and single- + quoted, so we don't have to scan through an unquoted expanded subscript to + find the ending bracket; indexed array assignments have been expanded and + possibly single-quoted to prevent further expansion. + + If this is an associative array, we perform the assignments into NHASH and + set NHASH to be the value of VAR after processing the assignments in NLIST */ +void +assign_compound_array_list (var, nlist, flags) + SHELL_VAR *var; + WORD_LIST *nlist; + int flags; +{ + ARRAY *a; + HASH_TABLE *h, *nhash; + WORD_LIST *list; + char *w, *val, *nval, *savecmd; + int len, iflags, free_val; + arrayind_t ind, last_ind; + char *akey; + + a = (var && array_p (var)) ? array_cell (var) : (ARRAY *)0; + nhash = h = (var && assoc_p (var)) ? assoc_cell (var) : (HASH_TABLE *)0; + + akey = (char *)0; + ind = 0; + + /* Now that we are ready to assign values to the array, kill the existing + value. */ + if ((flags & ASS_APPEND) == 0) + { + if (a && array_p (var)) + array_flush (a); + else if (h && assoc_p (var)) + nhash = assoc_create (h->nbuckets); + } + + last_ind = (a && (flags & ASS_APPEND)) ? array_max_index (a) + 1 : 0; + +#if ASSOC_KVPAIR_ASSIGNMENT + if (assoc_p (var) && kvpair_assignment_p (nlist)) + { + iflags = flags & ~ASS_APPEND; + assign_assoc_from_kvlist (var, nlist, nhash, iflags); + if (nhash && nhash != h) + { + h = assoc_cell (var); + var_setassoc (var, nhash); + assoc_dispose (h); + } + return; + } +#endif + + for (list = nlist; list; list = list->next) + { + /* Don't allow var+=(values) to make assignments in VALUES append to + existing values by default. */ + iflags = flags & ~ASS_APPEND; + w = list->word->word; + + /* We have a word of the form [ind]=value */ + if ((list->word->flags & W_ASSIGNMENT) && w[0] == '[') + { + /* Don't have to handle embedded quotes specially any more, since + associative array subscripts have not been expanded yet (see + above). */ + len = skipsubscript (w, 0, 0); + + /* XXX - changes for `+=' */ + if (w[len] != ']' || (w[len+1] != '=' && (w[len+1] != '+' || w[len+2] != '='))) + { + if (assoc_p (var)) + { + err_badarraysub (w); + continue; + } + nval = make_variable_value (var, w, flags); + if (var->assign_func) + (*var->assign_func) (var, nval, last_ind, 0); + else + array_insert (a, last_ind, nval); + FREE (nval); + last_ind++; + continue; + } + + if (len == 1) + { + err_badarraysub (w); + continue; + } + + if (ALL_ELEMENT_SUB (w[1]) && len == 2 && array_p (var)) + { + set_exit_status (EXECUTION_FAILURE); + report_error (_("%s: cannot assign to non-numeric index"), w); + continue; + } + + if (array_p (var)) + { + ind = array_expand_index (var, w + 1, len, 0); + /* negative subscripts to indexed arrays count back from end */ + if (ind < 0) + ind = array_max_index (array_cell (var)) + 1 + ind; + if (ind < 0) + { + err_badarraysub (w); + continue; + } + + last_ind = ind; + } + else if (assoc_p (var)) + { + /* This is not performed above, see expand_compound_array_assignment */ + w[len] = '\0'; /*[*/ + akey = expand_subscript_string (w+1, 0); + w[len] = ']'; + /* And we need to expand the value also, see below */ + if (akey == 0 || *akey == 0) + { + err_badarraysub (w); + FREE (akey); + continue; + } + } + + /* XXX - changes for `+=' -- just accept the syntax. ksh93 doesn't do this */ + if (w[len + 1] == '+' && w[len + 2] == '=') + { + iflags |= ASS_APPEND; + val = w + len + 3; + } + else + val = w + len + 2; + } + else if (assoc_p (var)) + { + set_exit_status (EXECUTION_FAILURE); + report_error (_("%s: %s: must use subscript when assigning associative array"), var->name, w); + continue; + } + else /* No [ind]=value, just a stray `=' */ + { + ind = last_ind; + val = w; + } + + free_val = 0; + /* See above; we need to expand the value here */ + if (assoc_p (var)) + { + val = expand_subscript_string (val, 0); + if (val == 0) + { + val = (char *)xmalloc (1); + val[0] = '\0'; /* like do_assignment_internal */ + } + free_val = 1; + } + + savecmd = this_command_name; + if (integer_p (var)) + this_command_name = (char *)NULL; /* no command name for errors */ + if (assoc_p (var)) + bind_assoc_var_internal (var, nhash, akey, val, iflags); + else + bind_array_var_internal (var, ind, akey, val, iflags); + last_ind++; + this_command_name = savecmd; + + if (free_val) + free (val); + } + + if (assoc_p (var) && nhash && nhash != h) + { + h = assoc_cell (var); + var_setassoc (var, nhash); + assoc_dispose (h); + } +} + +/* Perform a compound array assignment: VAR->name=( VALUE ). The + VALUE has already had the parentheses stripped. */ +SHELL_VAR * +assign_array_var_from_string (var, value, flags) + SHELL_VAR *var; + char *value; + int flags; +{ + WORD_LIST *nlist; + + if (value == 0) + return var; + + nlist = expand_compound_array_assignment (var, value, flags); + assign_compound_array_list (var, nlist, flags); + + if (nlist) + dispose_words (nlist); + + if (var) + VUNSETATTR (var, att_invisible); /* no longer invisible */ + + return (var); +} + +/* Quote globbing chars and characters in $IFS before the `=' in an assignment + statement (usually a compound array assignment) to protect them from + unwanted filename expansion or word splitting. */ +static char * +quote_assign (string) + const char *string; +{ + size_t slen; + int saw_eq; + char *temp, *t, *subs; + const char *s, *send; + int ss, se; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + t = temp = (char *)xmalloc (slen * 2 + 1); + saw_eq = 0; + for (s = string; *s; ) + { + if (*s == '=') + saw_eq = 1; + if (saw_eq == 0 && *s == '[') /* looks like a subscript */ + { + ss = s - string; + se = skipsubscript (string, ss, 0); + subs = substring (s, ss, se); + *t++ = '\\'; + strcpy (t, subs); + t += se - ss; + *t++ = '\\'; + *t++ = ']'; + s += se + 1; + free (subs); + continue; + } + if (saw_eq == 0 && (glob_char_p (s) || isifs (*s))) + *t++ = '\\'; + + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + return temp; +} + +/* Take a word W of the form [IND]=VALUE and transform it to ['IND']='VALUE' + to prevent further expansion. This is called for compound assignments to + indexed arrays. W has already undergone word expansions. If W has no [IND]=, + just single-quote and return it. */ +static char * +quote_compound_array_word (w, type) + char *w; + int type; +{ + char *nword, *sub, *value, *t; + int ind, wlen, i; + + if (w[0] != LBRACK) + return (sh_single_quote (w)); /* XXX - quote CTLESC */ + ind = skipsubscript (w, 0, 0); + if (w[ind] != RBRACK) + return (sh_single_quote (w)); /* XXX - quote CTLESC */ + + wlen = strlen (w); + w[ind] = '\0'; + t = (strchr (w+1, CTLESC)) ? quote_escapes (w+1) : w+1; + sub = sh_single_quote (t); + if (t != w+1) + free (t); + w[ind] = RBRACK; + + nword = xmalloc (wlen * 4 + 5); /* wlen*4 is max single quoted length */ + nword[0] = LBRACK; + i = STRLEN (sub); + memcpy (nword+1, sub, i); + free (sub); + i++; /* accommodate the opening LBRACK */ + nword[i++] = w[ind++]; /* RBRACK */ + if (w[ind] == '+') + nword[i++] = w[ind++]; + nword[i++] = w[ind++]; + t = (strchr (w+ind, CTLESC)) ? quote_escapes (w+ind) : w+ind; + value = sh_single_quote (t); + if (t != w+ind) + free (t); + strcpy (nword + i, value); + + return nword; +} + +/* Expand the key and value in W, which is of the form [KEY]=VALUE, and + reconstruct W with the expanded and single-quoted version: + ['expanded-key']='expanded-value'. If there is no [KEY]=, single-quote the + word and return it. Very similar to previous function, but does not assume + W has already been expanded, and expands the KEY and VALUE separately. + Used for compound assignments to associative arrays that are arguments to + declaration builtins (declare -A a=( list )). */ +char * +expand_and_quote_assoc_word (w, type) + char *w; + int type; +{ + char *nword, *key, *value, *s, *t; + int ind, wlen, i; + + if (w[0] != LBRACK) + return (sh_single_quote (w)); /* XXX - quote_escapes */ + ind = skipsubscript (w, 0, 0); + if (w[ind] != RBRACK) + return (sh_single_quote (w)); /* XXX - quote_escapes */ + + w[ind] = '\0'; + t = expand_subscript_string (w+1, 0); + s = (t && strchr (t, CTLESC)) ? quote_escapes (t) : t; + key = sh_single_quote (s ? s : ""); + if (s != t) + free (s); + w[ind] = RBRACK; + free (t); + + wlen = STRLEN (key); + nword = xmalloc (wlen + 5); + nword[0] = LBRACK; + memcpy (nword+1, key, wlen); + i = wlen + 1; /* accommodate the opening LBRACK */ + + nword[i++] = w[ind++]; /* RBRACK */ + if (w[ind] == '+') + nword[i++] = w[ind++]; + nword[i++] = w[ind++]; + + t = expand_subscript_string (w+ind, 0); + s = (t && strchr (t, CTLESC)) ? quote_escapes (t) : t; + value = sh_single_quote (s ? s : ""); + if (s != t) + free (s); + free (t); + nword = xrealloc (nword, wlen + 5 + STRLEN (value)); + strcpy (nword + i, value); + + free (key); + free (value); + + return nword; +} + +/* For each word in a compound array assignment, if the word looks like + [ind]=value, single-quote ind and value, but leave the brackets and + the = sign (and any `+') alone. If it's not an assignment, just single- + quote the word. This is used for indexed arrays. */ +void +quote_compound_array_list (list, type) + WORD_LIST *list; + int type; +{ + char *s, *t; + WORD_LIST *l; + + for (l = list; l; l = l->next) + { + if (l->word == 0 || l->word->word == 0) + continue; /* should not happen, but just in case... */ + if ((l->word->flags & W_ASSIGNMENT) == 0) + { + s = (strchr (l->word->word, CTLESC)) ? quote_escapes (l->word->word) : l->word->word; + t = sh_single_quote (s); + if (s != l->word->word) + free (s); + } + else + t = quote_compound_array_word (l->word->word, type); + free (l->word->word); + l->word->word = t; + } +} + +/* For each word in a compound array assignment, if the word looks like + [ind]=value, quote globbing chars and characters in $IFS before the `='. */ +static void +quote_array_assignment_chars (list) + WORD_LIST *list; +{ + char *nword; + WORD_LIST *l; + + for (l = list; l; l = l->next) + { + if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0') + continue; /* should not happen, but just in case... */ + /* Don't bother if it hasn't been recognized as an assignment or + doesn't look like [ind]=value */ + if ((l->word->flags & W_ASSIGNMENT) == 0) + continue; + if (l->word->word[0] != '[' || mbschr (l->word->word, '=') == 0) /* ] */ + continue; + + nword = quote_assign (l->word->word); + free (l->word->word); + l->word->word = nword; + l->word->flags |= W_NOGLOB; /* XXX - W_NOSPLIT also? */ + } +} + +/* skipsubscript moved to subst.c to use private functions. 2009/02/24. */ + +/* This function is called with SUB pointing to just after the beginning + `[' of an array subscript and removes the array element to which SUB + expands from array VAR. A subscript of `*' or `@' unsets the array. */ +/* If FLAGS&1 (VA_NOEXPAND) we don't expand the subscript; we just use it + as-is. If FLAGS&VA_ONEWORD, we don't try to use skipsubscript to parse + the subscript, we just assume the subscript ends with a close bracket, + if one is present, and use what's inside the brackets. */ +int +unbind_array_element (var, sub, flags) + SHELL_VAR *var; + char *sub; + int flags; +{ + arrayind_t ind; + char *akey; + ARRAY_ELEMENT *ae; + + /* Assume that the caller (unset_builtin) passes us a null-terminated SUB, + so we don't have to use VA_ONEWORD or parse the subscript again with + skipsubscript(). */ + + if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0) + { + if (array_p (var) || assoc_p (var)) + { + if (flags & VA_ALLOWALL) + { + unbind_variable (var->name); /* XXX -- {array,assoc}_flush ? */ + return (0); + } + /* otherwise we fall through and try to unset element `@' or `*' */ + } + else + return -2; /* don't allow this to unset scalar variables */ + } + + if (assoc_p (var)) + { + akey = (flags & VA_NOEXPAND) ? sub : expand_subscript_string (sub, 0); + if (akey == 0 || *akey == 0) + { + builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg)); + FREE (akey); + return -1; + } + assoc_remove (assoc_cell (var), akey); + if (akey != sub) + free (akey); + } + else if (array_p (var)) + { + if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0) + { + /* We can go several ways here: + 1) remove the array (backwards compatible) + 2) empty the array (new behavior) + 3) do nothing; treat the `@' or `*' as an expression and throw + an error + */ + /* Behavior 1 */ + if (shell_compatibility_level <= 51) + { + unbind_variable (name_cell (var)); + return 0; + } + else /* Behavior 2 */ + { + array_flush (array_cell (var)); + return 0; + } + /* Fall through for behavior 3 */ + } + ind = array_expand_index (var, sub, strlen (sub) + 1, 0); + /* negative subscripts to indexed arrays count back from end */ + if (ind < 0) + ind = array_max_index (array_cell (var)) + 1 + ind; + if (ind < 0) + { + builtin_error ("[%s]: %s", sub, _(bash_badsub_errmsg)); + return -1; + } + ae = array_remove (array_cell (var), ind); + if (ae) + array_dispose_element (ae); + } + else /* array_p (var) == 0 && assoc_p (var) == 0 */ + { + akey = this_command_name; + ind = array_expand_index (var, sub, strlen (sub) + 1, 0); + this_command_name = akey; + if (ind == 0) + { + unbind_variable (var->name); + return (0); + } + else + return -2; /* any subscript other than 0 is invalid with scalar variables */ + } + + return 0; +} + +/* Format and output an array assignment in compound form VAR=(VALUES), + suitable for re-use as input. */ +void +print_array_assignment (var, quoted) + SHELL_VAR *var; + int quoted; +{ + char *vstr; + + vstr = array_to_assign (array_cell (var), quoted); + + if (vstr == 0) + printf ("%s=%s\n", var->name, quoted ? "'()'" : "()"); + else + { + printf ("%s=%s\n", var->name, vstr); + free (vstr); + } +} + +/* Format and output an associative array assignment in compound form + VAR=(VALUES), suitable for re-use as input. */ +void +print_assoc_assignment (var, quoted) + SHELL_VAR *var; + int quoted; +{ + char *vstr; + + vstr = assoc_to_assign (assoc_cell (var), quoted); + + if (vstr == 0) + printf ("%s=%s\n", var->name, quoted ? "'()'" : "()"); + else + { + printf ("%s=%s\n", var->name, vstr); + free (vstr); + } +} + +/***********************************************************************/ +/* */ +/* Utility functions to manage arrays and their contents for expansion */ +/* */ +/***********************************************************************/ + +/* Return 1 if NAME is a properly-formed array reference v[sub]. */ + +/* Return 1 if NAME is a properly-formed array reference v[sub]. */ + +/* When NAME is a properly-formed array reference and a non-null argument SUBP + is supplied, '[' and ']' that enclose the subscript are replaced by '\0', + and the pointer to the subscript in NAME is assigned to *SUBP, so that NAME + and SUBP can be later used as the array name and the subscript, + respectively. When SUBP is the null pointer, the original string NAME will + not be modified. */ +/* We need to reserve 1 for FLAGS, which we pass to skipsubscript. */ +int +tokenize_array_reference (name, flags, subp) + char *name; + int flags; + char **subp; +{ + char *t; + int r, len, isassoc, ssflags; + SHELL_VAR *entry; + + t = mbschr (name, '['); /* ] */ + isassoc = 0; + if (t) + { + *t = '\0'; + r = legal_identifier (name); + if (flags & VA_NOEXPAND) /* Don't waste a lookup if we don't need one */ + isassoc = (entry = find_variable (name)) && assoc_p (entry); + *t = '['; + if (r == 0) + return 0; + + ssflags = 0; + if (isassoc && ((flags & (VA_NOEXPAND|VA_ONEWORD)) == (VA_NOEXPAND|VA_ONEWORD))) + len = strlen (t) - 1; + else if (isassoc) + { + if (flags & VA_NOEXPAND) + ssflags |= 1; + len = skipsubscript (t, 0, ssflags); + } + else + /* Check for a properly-terminated non-null subscript. */ + len = skipsubscript (t, 0, 0); /* arithmetic expression */ + + if (t[len] != ']' || len == 1 || t[len+1] != '\0') + return 0; + +#if 0 + /* Could check and allow subscripts consisting only of whitespace for + existing associative arrays, using isassoc */ + for (r = 1; r < len; r++) + if (whitespace (t[r]) == 0) + break; + if (r == len) + return 0; /* Fail if the subscript contains only whitespaces. */ +#endif + + if (subp) + { + t[0] = t[len] = '\0'; + *subp = t + 1; + } + + /* This allows blank subscripts */ + return 1; + } + return 0; +} + +/* Return 1 if NAME is a properly-formed array reference v[sub]. */ + +/* We need to reserve 1 for FLAGS, which we pass to skipsubscript. */ +int +valid_array_reference (name, flags) + const char *name; + int flags; +{ + return tokenize_array_reference ((char *)name, flags, (char **)NULL); +} + +/* Expand the array index beginning at S and extending LEN characters. */ +arrayind_t +array_expand_index (var, s, len, flags) + SHELL_VAR *var; + char *s; + int len; + int flags; +{ + char *exp, *t, *savecmd; + int expok, eflag; + arrayind_t val; + + exp = (char *)xmalloc (len); + strncpy (exp, s, len - 1); + exp[len - 1] = '\0'; +#if 0 /* TAG: maybe bash-5.2 */ + if ((flags & AV_NOEXPAND) == 0) + t = expand_arith_string (exp, Q_DOUBLE_QUOTES|Q_ARITH|Q_ARRAYSUB); /* XXX - Q_ARRAYSUB for future use */ + else + t = exp; +#else + t = expand_arith_string (exp, Q_DOUBLE_QUOTES|Q_ARITH|Q_ARRAYSUB); /* XXX - Q_ARRAYSUB for future use */ +#endif + savecmd = this_command_name; + this_command_name = (char *)NULL; + eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED; + val = evalexp (t, eflag, &expok); /* XXX - was 0 but we expanded exp already */ + this_command_name = savecmd; + if (t != exp) + free (t); + free (exp); + if (expok == 0) + { + set_exit_status (EXECUTION_FAILURE); + + if (no_longjmp_on_fatal_error) + return 0; + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + return val; +} + +/* Return the name of the variable specified by S without any subscript. + If SUBP is non-null, return a pointer to the start of the subscript + in *SUBP. If LENP is non-null, the length of the subscript is returned + in *LENP. This returns newly-allocated memory. */ +char * +array_variable_name (s, flags, subp, lenp) + const char *s; + int flags; + char **subp; + int *lenp; +{ + char *t, *ret; + int ind, ni, ssflags; + + t = mbschr (s, '['); + if (t == 0) + { + if (subp) + *subp = t; + if (lenp) + *lenp = 0; + return ((char *)NULL); + } + ind = t - s; + if ((flags & (AV_NOEXPAND|AV_ONEWORD)) == (AV_NOEXPAND|AV_ONEWORD)) + ni = strlen (s) - 1; + else + { + ssflags = 0; + if (flags & AV_NOEXPAND) + ssflags |= 1; + ni = skipsubscript (s, ind, ssflags); + } + if (ni <= ind + 1 || s[ni] != ']') + { + err_badarraysub (s); + if (subp) + *subp = t; + if (lenp) + *lenp = 0; + return ((char *)NULL); + } + + *t = '\0'; + ret = savestring (s); + *t++ = '['; /* ] */ + + if (subp) + *subp = t; + if (lenp) + *lenp = ni - ind; + + return ret; +} + +/* Return the variable specified by S without any subscript. If SUBP is + non-null, return a pointer to the start of the subscript in *SUBP. + If LENP is non-null, the length of the subscript is returned in *LENP. */ +SHELL_VAR * +array_variable_part (s, flags, subp, lenp) + const char *s; + int flags; + char **subp; + int *lenp; +{ + char *t; + SHELL_VAR *var; + + t = array_variable_name (s, flags, subp, lenp); + if (t == 0) + return ((SHELL_VAR *)NULL); + var = find_variable (t); /* XXX - handle namerefs here? */ + + free (t); + return var; /* now return invisible variables; caller must handle */ +} + +#define INDEX_ERROR() \ + do \ + { \ + if (var) \ + err_badarraysub (var->name); \ + else \ + { \ + t[-1] = '\0'; \ + err_badarraysub (s); \ + t[-1] = '['; /* ] */\ + } \ + return ((char *)NULL); \ + } \ + while (0) + +/* Return a string containing the elements in the array and subscript + described by S. If the subscript is * or @, obeys quoting rules akin + to the expansion of $* and $@ including double quoting. If RTYPE + is non-null it gets 1 if the array reference is name[*], 2 if the + reference is name[@], and 0 otherwise. */ +static char * +array_value_internal (s, quoted, flags, estatep) + const char *s; + int quoted, flags; + array_eltstate_t *estatep; +{ + int len, isassoc, subtype; + arrayind_t ind; + char *akey; + char *retval, *t, *temp; + WORD_LIST *l; + SHELL_VAR *var; + + var = array_variable_part (s, flags, &t, &len); /* XXX */ + + /* Expand the index, even if the variable doesn't exist, in case side + effects are needed, like ${w[i++]} where w is unset. */ +#if 0 + if (var == 0) + return (char *)NULL; +#endif + + if (len == 0) + return ((char *)NULL); /* error message already printed */ + + isassoc = var && assoc_p (var); + /* [ */ + akey = 0; + subtype = 0; + if (estatep) + estatep->value = (char *)NULL; + + /* Backwards compatibility: we only change the behavior of A[@] and A[*] + for associative arrays, and the caller has to request it. */ + if ((isassoc == 0 || (flags & AV_ATSTARKEYS) == 0) && ALL_ELEMENT_SUB (t[0]) && t[1] == ']') + { + if (estatep) + estatep->subtype = (t[0] == '*') ? 1 : 2; + if ((flags & AV_ALLOWALL) == 0) + { + err_badarraysub (s); + return ((char *)NULL); + } + else if (var == 0 || value_cell (var) == 0) + return ((char *)NULL); + else if (invisible_p (var)) + return ((char *)NULL); + else if (array_p (var) == 0 && assoc_p (var) == 0) + { + if (estatep) + estatep->type = ARRAY_SCALAR; + l = add_string_to_list (value_cell (var), (WORD_LIST *)NULL); + } + else if (assoc_p (var)) + { + if (estatep) + estatep->type = ARRAY_ASSOC; + l = assoc_to_word_list (assoc_cell (var)); + if (l == (WORD_LIST *)NULL) + return ((char *)NULL); + } + else + { + if (estatep) + estatep->type = ARRAY_INDEXED; + l = array_to_word_list (array_cell (var)); + if (l == (WORD_LIST *)NULL) + return ((char *) NULL); + } + + /* Caller of array_value takes care of inspecting estatep->subtype and + duplicating retval if subtype == 0, so this is not a memory leak */ + if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + { + temp = string_list_dollar_star (l, quoted, (flags & AV_ASSIGNRHS) ? PF_ASSIGNRHS : 0); + retval = quote_string (temp); + free (temp); + } + else /* ${name[@]} or unquoted ${name[*]} */ + retval = string_list_dollar_at (l, quoted, (flags & AV_ASSIGNRHS) ? PF_ASSIGNRHS : 0); + + dispose_words (l); + } + else + { + if (estatep) + estatep->subtype = 0; + if (var == 0 || array_p (var) || assoc_p (var) == 0) + { + if ((flags & AV_USEIND) == 0 || estatep == 0) + { + ind = array_expand_index (var, t, len, flags); + if (ind < 0) + { + /* negative subscripts to indexed arrays count back from end */ + if (var && array_p (var)) + ind = array_max_index (array_cell (var)) + 1 + ind; + if (ind < 0) + INDEX_ERROR(); + } + if (estatep) + estatep->ind = ind; + } + else if (estatep && (flags & AV_USEIND)) + ind = estatep->ind; + if (estatep && var) + estatep->type = array_p (var) ? ARRAY_INDEXED : ARRAY_SCALAR; + } + else if (assoc_p (var)) + { + t[len - 1] = '\0'; + if (estatep) + estatep->type = ARRAY_ASSOC; + if ((flags & AV_USEIND) && estatep && estatep->key) + akey = savestring (estatep->key); + else if ((flags & AV_NOEXPAND) == 0) + akey = expand_subscript_string (t, 0); /* [ */ + else + akey = savestring (t); + t[len - 1] = ']'; + if (akey == 0 || *akey == 0) + { + FREE (akey); + INDEX_ERROR(); + } + } + + if (var == 0 || value_cell (var) == 0) + { + FREE (akey); + return ((char *)NULL); + } + else if (invisible_p (var)) + { + FREE (akey); + return ((char *)NULL); + } + if (array_p (var) == 0 && assoc_p (var) == 0) + retval = (ind == 0) ? value_cell (var) : (char *)NULL; + else if (assoc_p (var)) + { + retval = assoc_reference (assoc_cell (var), akey); + if (estatep && estatep->key && (flags & AV_USEIND)) + free (akey); /* duplicated estatep->key */ + else if (estatep) + estatep->key = akey; /* XXX - caller must manage */ + else /* not saving it anywhere */ + free (akey); + } + else + retval = array_reference (array_cell (var), ind); + + if (estatep) + estatep->value = retval; + } + + return retval; +} + +/* Return a string containing the elements described by the array and + subscript contained in S, obeying quoting for subscripts * and @. */ +char * +array_value (s, quoted, flags, estatep) + const char *s; + int quoted, flags; + array_eltstate_t *estatep; +{ + char *retval; + + retval = array_value_internal (s, quoted, flags|AV_ALLOWALL, estatep); + return retval; +} + +/* Return the value of the array indexing expression S as a single string. + If (FLAGS & AV_ALLOWALL) is 0, do not allow `@' and `*' subscripts. This + is used by other parts of the shell such as the arithmetic expression + evaluator in expr.c. */ +char * +get_array_value (s, flags, estatep) + const char *s; + int flags; + array_eltstate_t *estatep; +{ + char *retval; + + retval = array_value_internal (s, 0, flags, estatep); + return retval; +} + +char * +array_keys (s, quoted, pflags) + char *s; + int quoted, pflags; +{ + int len; + char *retval, *t, *temp; + WORD_LIST *l; + SHELL_VAR *var; + + var = array_variable_part (s, 0, &t, &len); + + /* [ */ + if (var == 0 || ALL_ELEMENT_SUB (t[0]) == 0 || t[1] != ']') + return (char *)NULL; + + if (var_isset (var) == 0 || invisible_p (var)) + return (char *)NULL; + + if (array_p (var) == 0 && assoc_p (var) == 0) + l = add_string_to_list ("0", (WORD_LIST *)NULL); + else if (assoc_p (var)) + l = assoc_keys_to_word_list (assoc_cell (var)); + else + l = array_keys_to_word_list (array_cell (var)); + if (l == (WORD_LIST *)NULL) + return ((char *) NULL); + + retval = string_list_pos_params (t[0], l, quoted, pflags); + + dispose_words (l); + return retval; +} +#endif /* ARRAY_VARS */ diff --git a/third_party/bash/arrayfunc.h b/third_party/bash/arrayfunc.h new file mode 100644 index 000000000..69112b579 --- /dev/null +++ b/third_party/bash/arrayfunc.h @@ -0,0 +1,140 @@ +/* arrayfunc.h -- declarations for miscellaneous array functions in arrayfunc.c */ + +/* Copyright (C) 2001-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_ARRAYFUNC_H_) +#define _ARRAYFUNC_H_ + +/* Must include variables.h before including this file. */ + +/* An object to encapsulate the state of an array element. It can describe + an array assignment A[KEY]=VALUE or a[IND]=VALUE depending on TYPE, or + for passing array subscript references around, where VALUE would be + ${a[IND]} or ${A[KEY]}. This is not dependent on ARRAY_VARS so we can + use it in function parameters. */ + +/* values for `type' field */ +#define ARRAY_INVALID -1 +#define ARRAY_SCALAR 0 +#define ARRAY_INDEXED 1 +#define ARRAY_ASSOC 2 + +/* KEY will contain allocated memory if called through the assign_array_element + code path because of how assoc_insert works. */ +typedef struct element_state +{ + short type; /* assoc or indexed, says which fields are valid */ + short subtype; /* `*', `@', or something else */ + arrayind_t ind; + char *key; /* can be allocated memory */ + char *value; +} array_eltstate_t; + +#if defined (ARRAY_VARS) + +/* This variable means to not expand associative array subscripts more than + once, when performing variable expansion. */ +extern int assoc_expand_once; + +/* The analog for indexed array subscripts */ +extern int array_expand_once; + +/* Flags for array_value_internal and callers array_value/get_array_value; also + used by array_variable_name and array_variable_part. */ +#define AV_ALLOWALL 0x001 /* treat a[@] like $@ and a[*] like $* */ +#define AV_QUOTED 0x002 +#define AV_USEIND 0x004 +#define AV_USEVAL 0x008 /* XXX - should move this */ +#define AV_ASSIGNRHS 0x010 /* no splitting, special case ${a[@]} */ +#define AV_NOEXPAND 0x020 /* don't run assoc subscripts through word expansion */ +#define AV_ONEWORD 0x040 /* not used yet */ +#define AV_ATSTARKEYS 0x080 /* accept a[@] and a[*] but use them as keys, not special values */ + +/* Flags for valid_array_reference. Value 1 is reserved for skipsubscript(). + Also used by unbind_array_element, which is currently the only function + that uses VA_ALLOWALL. */ +#define VA_NOEXPAND 0x001 +#define VA_ONEWORD 0x002 +#define VA_ALLOWALL 0x004 /* allow @ to mean all elements of the array */ + +extern SHELL_VAR *convert_var_to_array PARAMS((SHELL_VAR *)); +extern SHELL_VAR *convert_var_to_assoc PARAMS((SHELL_VAR *)); + +extern char *make_array_variable_value PARAMS((SHELL_VAR *, arrayind_t, char *, char *, int)); + +extern SHELL_VAR *bind_array_variable PARAMS((char *, arrayind_t, char *, int)); +extern SHELL_VAR *bind_array_element PARAMS((SHELL_VAR *, arrayind_t, char *, int)); +extern SHELL_VAR *assign_array_element PARAMS((char *, char *, int, array_eltstate_t *)); + +extern SHELL_VAR *bind_assoc_variable PARAMS((SHELL_VAR *, char *, char *, char *, int)); + +extern SHELL_VAR *find_or_make_array_variable PARAMS((char *, int)); + +extern SHELL_VAR *assign_array_from_string PARAMS((char *, char *, int)); +extern SHELL_VAR *assign_array_var_from_word_list PARAMS((SHELL_VAR *, WORD_LIST *, int)); + +extern WORD_LIST *expand_compound_array_assignment PARAMS((SHELL_VAR *, char *, int)); +extern void assign_compound_array_list PARAMS((SHELL_VAR *, WORD_LIST *, int)); +extern SHELL_VAR *assign_array_var_from_string PARAMS((SHELL_VAR *, char *, int)); + +extern char *expand_and_quote_assoc_word PARAMS((char *, int)); +extern void quote_compound_array_list PARAMS((WORD_LIST *, int)); + +extern int kvpair_assignment_p PARAMS((WORD_LIST *)); +extern char *expand_and_quote_kvpair_word PARAMS((char *)); + +extern int unbind_array_element PARAMS((SHELL_VAR *, char *, int)); +extern int skipsubscript PARAMS((const char *, int, int)); + +extern void print_array_assignment PARAMS((SHELL_VAR *, int)); +extern void print_assoc_assignment PARAMS((SHELL_VAR *, int)); + +extern arrayind_t array_expand_index PARAMS((SHELL_VAR *, char *, int, int)); +extern int valid_array_reference PARAMS((const char *, int)); +extern int tokenize_array_reference PARAMS((char *, int, char **)); + +extern char *array_value PARAMS((const char *, int, int, array_eltstate_t *)); +extern char *get_array_value PARAMS((const char *, int, array_eltstate_t *)); + +extern char *array_keys PARAMS((char *, int, int)); + +extern char *array_variable_name PARAMS((const char *, int, char **, int *)); +extern SHELL_VAR *array_variable_part PARAMS((const char *, int, char **, int *)); + +extern void init_eltstate (array_eltstate_t *); +extern void flush_eltstate (array_eltstate_t *); + +#else + +#define AV_ALLOWALL 0 +#define AV_QUOTED 0 +#define AV_USEIND 0 +#define AV_USEVAL 0 +#define AV_ASSIGNRHS 0 +#define AV_NOEXPAND 0 +#define AV_ONEWORD 0 +#define AV_ATSTARKEYS 0 + +#define VA_NOEXPAND 0 +#define VA_ONEWORD 0 +#define VA_ALLOWALL 0 + +#endif + +#endif /* !_ARRAYFUNC_H_ */ diff --git a/third_party/bash/assoc.c b/third_party/bash/assoc.c new file mode 100644 index 000000000..e29fade75 --- /dev/null +++ b/third_party/bash/assoc.c @@ -0,0 +1,611 @@ +/* + * assoc.c - functions to manipulate associative arrays + * + * Associative arrays are standard shell hash tables. + * + * Chet Ramey + * chet@ins.cwru.edu + */ + +/* Copyright (C) 2008,2009,2011-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (ARRAY_VARS) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "bashansi.h" + +#include "shell.h" +#include "array.h" +#include "assoc.h" +#include "common.h" + +static WORD_LIST *assoc_to_word_list_internal PARAMS((HASH_TABLE *, int)); + +/* assoc_create == hash_create */ + +void +assoc_dispose (hash) + HASH_TABLE *hash; +{ + if (hash) + { + hash_flush (hash, 0); + hash_dispose (hash); + } +} + +void +assoc_flush (hash) + HASH_TABLE *hash; +{ + hash_flush (hash, 0); +} + +int +assoc_insert (hash, key, value) + HASH_TABLE *hash; + char *key; + char *value; +{ + BUCKET_CONTENTS *b; + + b = hash_search (key, hash, HASH_CREATE); + if (b == 0) + return -1; + /* If we are overwriting an existing element's value, we're not going to + use the key. Nothing in the array assignment code path frees the key + string, so we can free it here to avoid a memory leak. */ + if (b->key != key) + free (key); + FREE (b->data); + b->data = value ? savestring (value) : (char *)0; + return (0); +} + +/* Like assoc_insert, but returns b->data instead of freeing it */ +PTR_T +assoc_replace (hash, key, value) + HASH_TABLE *hash; + char *key; + char *value; +{ + BUCKET_CONTENTS *b; + PTR_T t; + + b = hash_search (key, hash, HASH_CREATE); + if (b == 0) + return (PTR_T)0; + /* If we are overwriting an existing element's value, we're not going to + use the key. Nothing in the array assignment code path frees the key + string, so we can free it here to avoid a memory leak. */ + if (b->key != key) + free (key); + t = b->data; + b->data = value ? savestring (value) : (char *)0; + return t; +} + +void +assoc_remove (hash, string) + HASH_TABLE *hash; + char *string; +{ + BUCKET_CONTENTS *b; + + b = hash_remove (string, hash, 0); + if (b) + { + free ((char *)b->data); + free (b->key); + free (b); + } +} + +char * +assoc_reference (hash, string) + HASH_TABLE *hash; + char *string; +{ + BUCKET_CONTENTS *b; + + if (hash == 0) + return (char *)0; + + b = hash_search (string, hash, 0); + return (b ? (char *)b->data : 0); +} + +/* Quote the data associated with each element of the hash table ASSOC, + using quote_string */ +HASH_TABLE * +assoc_quote (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = quote_string ((char *)tlist->data); + FREE (tlist->data); + tlist->data = t; + } + + return h; +} + +/* Quote escape characters in the data associated with each element + of the hash table ASSOC, using quote_escapes */ +HASH_TABLE * +assoc_quote_escapes (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = quote_escapes ((char *)tlist->data); + FREE (tlist->data); + tlist->data = t; + } + + return h; +} + +HASH_TABLE * +assoc_dequote (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = dequote_string ((char *)tlist->data); + FREE (tlist->data); + tlist->data = t; + } + + return h; +} + +HASH_TABLE * +assoc_dequote_escapes (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = dequote_escapes ((char *)tlist->data); + FREE (tlist->data); + tlist->data = t; + } + + return h; +} + +HASH_TABLE * +assoc_remove_quoted_nulls (h) + HASH_TABLE *h; +{ + int i; + BUCKET_CONTENTS *tlist; + char *t; + + if (h == 0 || assoc_empty (h)) + return ((HASH_TABLE *)NULL); + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + t = remove_quoted_nulls ((char *)tlist->data); + tlist->data = t; + } + + return h; +} + +/* + * Return a string whose elements are the members of array H beginning at + * the STARTth element and spanning NELEM members. Null elements are counted. + */ +char * +assoc_subrange (hash, start, nelem, starsub, quoted, pflags) + HASH_TABLE *hash; + arrayind_t start, nelem; + int starsub, quoted, pflags; +{ + WORD_LIST *l, *save, *h, *t; + int i, j; + char *ret; + + if (assoc_empty (hash)) + return ((char *)NULL); + + save = l = assoc_to_word_list (hash); + if (save == 0) + return ((char *)NULL); + + for (i = 1; l && i < start; i++) + l = l->next; + if (l == 0) + { + dispose_words (save); + return ((char *)NULL); + } + for (j = 0,h = t = l; l && j < nelem; j++) + { + t = l; + l = l->next; + } + + t->next = (WORD_LIST *)NULL; + + ret = string_list_pos_params (starsub ? '*' : '@', h, quoted, pflags); + + if (t != l) + t->next = l; + + dispose_words (save); + return (ret); + +} + +char * +assoc_patsub (h, pat, rep, mflags) + HASH_TABLE *h; + char *pat, *rep; + int mflags; +{ + char *t; + int pchar, qflags, pflags; + WORD_LIST *wl, *save; + + if (h == 0 || assoc_empty (h)) + return ((char *)NULL); + + wl = assoc_to_word_list (h); + if (wl == 0) + return (char *)NULL; + + for (save = wl; wl; wl = wl->next) + { + t = pat_subst (wl->word->word, pat, rep, mflags); + FREE (wl->word->word); + wl->word->word = t; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + pflags = (mflags & MATCH_ASSIGNRHS) == MATCH_ASSIGNRHS ? PF_ASSIGNRHS : 0; + + t = string_list_pos_params (pchar, save, qflags, pflags); + dispose_words (save); + + return t; +} + +char * +assoc_modcase (h, pat, modop, mflags) + HASH_TABLE *h; + char *pat; + int modop; + int mflags; +{ + char *t; + int pchar, qflags, pflags; + WORD_LIST *wl, *save; + + if (h == 0 || assoc_empty (h)) + return ((char *)NULL); + + wl = assoc_to_word_list (h); + if (wl == 0) + return ((char *)NULL); + + for (save = wl; wl; wl = wl->next) + { + t = sh_modcase (wl->word->word, pat, modop); + FREE (wl->word->word); + wl->word->word = t; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + pflags = (mflags & MATCH_ASSIGNRHS) == MATCH_ASSIGNRHS ? PF_ASSIGNRHS : 0; + + t = string_list_pos_params (pchar, save, qflags, pflags); + dispose_words (save); + + return t; +} + +char * +assoc_to_kvpair (hash, quoted) + HASH_TABLE *hash; + int quoted; +{ + char *ret; + char *istr, *vstr; + int i, rsize, rlen, elen; + BUCKET_CONTENTS *tlist; + + if (hash == 0 || assoc_empty (hash)) + return (char *)0; + + ret = xmalloc (rsize = 128); + ret[rlen = 0] = '\0'; + + for (i = 0; i < hash->nbuckets; i++) + for (tlist = hash_items (i, hash); tlist; tlist = tlist->next) + { + if (ansic_shouldquote (tlist->key)) + istr = ansic_quote (tlist->key, 0, (int *)0); + else if (sh_contains_shell_metas (tlist->key)) + istr = sh_double_quote (tlist->key); + else if (ALL_ELEMENT_SUB (tlist->key[0]) && tlist->key[1] == '\0') + istr = sh_double_quote (tlist->key); + else + istr = tlist->key; + + vstr = tlist->data ? (ansic_shouldquote ((char *)tlist->data) ? + ansic_quote ((char *)tlist->data, 0, (int *)0) : + sh_double_quote ((char *)tlist->data)) + : (char *)0; + + elen = STRLEN (istr) + 4 + STRLEN (vstr); + RESIZE_MALLOCED_BUFFER (ret, rlen, (elen+1), rsize, rsize); + + strcpy (ret+rlen, istr); + rlen += STRLEN (istr); + ret[rlen++] = ' '; + if (vstr) + { + strcpy (ret + rlen, vstr); + rlen += STRLEN (vstr); + } + else + { + strcpy (ret + rlen, "\"\""); + rlen += 2; + } + ret[rlen++] = ' '; + + if (istr != tlist->key) + FREE (istr); + + FREE (vstr); + } + + RESIZE_MALLOCED_BUFFER (ret, rlen, 1, rsize, 8); + ret[rlen] = '\0'; + + if (quoted) + { + vstr = sh_single_quote (ret); + free (ret); + ret = vstr; + } + + return ret; +} + +char * +assoc_to_assign (hash, quoted) + HASH_TABLE *hash; + int quoted; +{ + char *ret; + char *istr, *vstr; + int i, rsize, rlen, elen; + BUCKET_CONTENTS *tlist; + + if (hash == 0 || assoc_empty (hash)) + return (char *)0; + + ret = xmalloc (rsize = 128); + ret[0] = '('; + rlen = 1; + + for (i = 0; i < hash->nbuckets; i++) + for (tlist = hash_items (i, hash); tlist; tlist = tlist->next) + { + if (ansic_shouldquote (tlist->key)) + istr = ansic_quote (tlist->key, 0, (int *)0); + else if (sh_contains_shell_metas (tlist->key)) + istr = sh_double_quote (tlist->key); + else if (ALL_ELEMENT_SUB (tlist->key[0]) && tlist->key[1] == '\0') + istr = sh_double_quote (tlist->key); + else + istr = tlist->key; + + vstr = tlist->data ? (ansic_shouldquote ((char *)tlist->data) ? + ansic_quote ((char *)tlist->data, 0, (int *)0) : + sh_double_quote ((char *)tlist->data)) + : (char *)0; + + elen = STRLEN (istr) + 8 + STRLEN (vstr); + RESIZE_MALLOCED_BUFFER (ret, rlen, (elen+1), rsize, rsize); + + ret[rlen++] = '['; + strcpy (ret+rlen, istr); + rlen += STRLEN (istr); + ret[rlen++] = ']'; + ret[rlen++] = '='; + if (vstr) + { + strcpy (ret + rlen, vstr); + rlen += STRLEN (vstr); + } + ret[rlen++] = ' '; + + if (istr != tlist->key) + FREE (istr); + + FREE (vstr); + } + + RESIZE_MALLOCED_BUFFER (ret, rlen, 1, rsize, 8); + ret[rlen++] = ')'; + ret[rlen] = '\0'; + + if (quoted) + { + vstr = sh_single_quote (ret); + free (ret); + ret = vstr; + } + + return ret; +} + +static WORD_LIST * +assoc_to_word_list_internal (h, t) + HASH_TABLE *h; + int t; +{ + WORD_LIST *list; + int i; + BUCKET_CONTENTS *tlist; + char *w; + + if (h == 0 || assoc_empty (h)) + return((WORD_LIST *)NULL); + list = (WORD_LIST *)NULL; + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + w = (t == 0) ? (char *)tlist->data : (char *)tlist->key; + list = make_word_list (make_bare_word(w), list); + } + return (REVERSE_LIST(list, WORD_LIST *)); +} + +WORD_LIST * +assoc_to_word_list (h) + HASH_TABLE *h; +{ + return (assoc_to_word_list_internal (h, 0)); +} + +WORD_LIST * +assoc_keys_to_word_list (h) + HASH_TABLE *h; +{ + return (assoc_to_word_list_internal (h, 1)); +} + +WORD_LIST * +assoc_to_kvpair_list (h) + HASH_TABLE *h; +{ + WORD_LIST *list; + int i; + BUCKET_CONTENTS *tlist; + char *k, *v; + + if (h == 0 || assoc_empty (h)) + return((WORD_LIST *)NULL); + list = (WORD_LIST *)NULL; + + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + k = (char *)tlist->key; + v = (char *)tlist->data; + list = make_word_list (make_bare_word (k), list); + list = make_word_list (make_bare_word (v), list); + } + return (REVERSE_LIST(list, WORD_LIST *)); +} + +char * +assoc_to_string (h, sep, quoted) + HASH_TABLE *h; + char *sep; + int quoted; +{ + BUCKET_CONTENTS *tlist; + int i; + char *result, *t, *w; + WORD_LIST *list, *l; + + if (h == 0) + return ((char *)NULL); + if (assoc_empty (h)) + return (savestring ("")); + + result = NULL; + l = list = NULL; + /* This might be better implemented directly, but it's simple to implement + by converting to a word list first, possibly quoting the data, then + using list_string */ + for (i = 0; i < h->nbuckets; i++) + for (tlist = hash_items (i, h); tlist; tlist = tlist->next) + { + w = (char *)tlist->data; + if (w == 0) + continue; + t = quoted ? quote_string (w) : savestring (w); + list = make_word_list (make_bare_word(t), list); + FREE (t); + } + + l = REVERSE_LIST(list, WORD_LIST *); + + result = l ? string_list_internal (l, sep) : savestring (""); + dispose_words (l); + + return result; +} + +#endif /* ARRAY_VARS */ diff --git a/third_party/bash/assoc.h b/third_party/bash/assoc.h new file mode 100644 index 000000000..664d13740 --- /dev/null +++ b/third_party/bash/assoc.h @@ -0,0 +1,66 @@ +/* assoc.h -- definitions for the interface exported by assoc.c that allows + the rest of the shell to manipulate associative array variables. */ + +/* Copyright (C) 2008,2009-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _ASSOC_H_ +#define _ASSOC_H_ + +#include "stdc.h" +#include "hashlib.h" + +#define ASSOC_HASH_BUCKETS 1024 + +#define assoc_empty(h) ((h)->nentries == 0) +#define assoc_num_elements(h) ((h)->nentries) + +#define assoc_create(n) (hash_create((n))) + +#define assoc_copy(h) (hash_copy((h), 0)) + +#define assoc_walk(h, f) (hash_walk((h), (f)) + +extern void assoc_dispose PARAMS((HASH_TABLE *)); +extern void assoc_flush PARAMS((HASH_TABLE *)); + +extern int assoc_insert PARAMS((HASH_TABLE *, char *, char *)); +extern PTR_T assoc_replace PARAMS((HASH_TABLE *, char *, char *)); +extern void assoc_remove PARAMS((HASH_TABLE *, char *)); + +extern char *assoc_reference PARAMS((HASH_TABLE *, char *)); + +extern char *assoc_subrange PARAMS((HASH_TABLE *, arrayind_t, arrayind_t, int, int, int)); +extern char *assoc_patsub PARAMS((HASH_TABLE *, char *, char *, int)); +extern char *assoc_modcase PARAMS((HASH_TABLE *, char *, int, int)); + +extern HASH_TABLE *assoc_quote PARAMS((HASH_TABLE *)); +extern HASH_TABLE *assoc_quote_escapes PARAMS((HASH_TABLE *)); +extern HASH_TABLE *assoc_dequote PARAMS((HASH_TABLE *)); +extern HASH_TABLE *assoc_dequote_escapes PARAMS((HASH_TABLE *)); +extern HASH_TABLE *assoc_remove_quoted_nulls PARAMS((HASH_TABLE *)); + +extern char *assoc_to_kvpair PARAMS((HASH_TABLE *, int)); +extern char *assoc_to_assign PARAMS((HASH_TABLE *, int)); + +extern WORD_LIST *assoc_to_word_list PARAMS((HASH_TABLE *)); +extern WORD_LIST *assoc_keys_to_word_list PARAMS((HASH_TABLE *)); +extern WORD_LIST *assoc_to_kvpair_list PARAMS((HASH_TABLE *)); + +extern char *assoc_to_string PARAMS((HASH_TABLE *, char *, int)); +#endif /* _ASSOC_H_ */ diff --git a/third_party/bash/bashansi.h b/third_party/bash/bashansi.h new file mode 100644 index 000000000..dd2a544cd --- /dev/null +++ b/third_party/bash/bashansi.h @@ -0,0 +1,38 @@ +/* bashansi.h -- Typically included information required by picky compilers. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_BASHANSI_H_) +#define _BASHANSI_H_ + +#if defined (HAVE_STRING_H) +# include +#endif /* !HAVE_STRING_H */ + +#if defined (HAVE_STRINGS_H) +# include +#endif /* !HAVE_STRINGS_H */ + +#if defined (HAVE_STDLIB_H) +# include +#else +# include "ansi_stdlib.h" +#endif /* !HAVE_STDLIB_H */ + +#endif /* !_BASHANSI_H_ */ diff --git a/third_party/bash/bashgetopt.c b/third_party/bash/bashgetopt.c new file mode 100644 index 000000000..7c41d6433 --- /dev/null +++ b/third_party/bash/bashgetopt.c @@ -0,0 +1,194 @@ +/* bashgetopt.c -- `getopt' for use by the builtins. */ + +/* Copyright (C) 1992-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "chartypes.h" +#include + +#include "shell.h" +#include "common.h" + +#include "bashgetopt.h" + +#define ISOPT(s) (((*(s) == '-') || (plus && *(s) == '+')) && (s)[1]) +#define NOTOPT(s) (((*(s) != '-') && (!plus || *(s) != '+')) || (s)[1] == '\0') + +static int sp; + +char *list_optarg; +int list_optflags; +int list_optopt; +int list_opttype; + +static WORD_LIST *lhead = (WORD_LIST *)NULL; +WORD_LIST *lcurrent = (WORD_LIST *)NULL; +WORD_LIST *loptend; /* Points to the first non-option argument in the list */ + +int +internal_getopt(list, opts) +WORD_LIST *list; +char *opts; +{ + register int c; + register char *cp; + int plus; /* nonzero means to handle +option */ + static char errstr[3] = { '-', '\0', '\0' }; + + plus = *opts == '+'; + if (plus) + opts++; + + if (list == 0) { + list_optarg = (char *)NULL; + list_optflags = 0; + loptend = (WORD_LIST *)NULL; /* No non-option arguments */ + return -1; + } + + if (list != lhead || lhead == 0) { + /* Hmmm.... called with a different word list. Reset. */ + sp = 1; + lcurrent = lhead = list; + loptend = (WORD_LIST *)NULL; + } + + if (sp == 1) { + if (lcurrent == 0 || NOTOPT(lcurrent->word->word)) { + lhead = (WORD_LIST *)NULL; + loptend = lcurrent; + return(-1); + } else if (ISHELP (lcurrent->word->word)) { + lhead = (WORD_LIST *)NULL; + loptend = lcurrent; + return (GETOPT_HELP); + } else if (lcurrent->word->word[0] == '-' && + lcurrent->word->word[1] == '-' && + lcurrent->word->word[2] == 0) { + lhead = (WORD_LIST *)NULL; + loptend = lcurrent->next; + return(-1); + } + errstr[0] = list_opttype = lcurrent->word->word[0]; + } + + list_optopt = c = lcurrent->word->word[sp]; + + if (c == ':' || (cp = strchr(opts, c)) == NULL) { + errstr[1] = c; + sh_invalidopt (errstr); + if (lcurrent->word->word[++sp] == '\0') { + lcurrent = lcurrent->next; + sp = 1; + } + list_optarg = NULL; + list_optflags = 0; + if (lcurrent) + loptend = lcurrent->next; + return('?'); + } + + if (*++cp == ':' || *cp == ';') { + /* `:': Option requires an argument. */ + /* `;': option argument may be missing */ + /* We allow -l2 as equivalent to -l 2 */ + if (lcurrent->word->word[sp+1]) { + list_optarg = lcurrent->word->word + sp + 1; + list_optflags = 0; + lcurrent = lcurrent->next; + /* If the specifier is `;', don't set optarg if the next + argument looks like another option. */ +#if 0 + } else if (lcurrent->next && (*cp == ':' || lcurrent->next->word->word[0] != '-')) { +#else + } else if (lcurrent->next && (*cp == ':' || NOTOPT(lcurrent->next->word->word))) { +#endif + lcurrent = lcurrent->next; + list_optarg = lcurrent->word->word; + list_optflags = lcurrent->word->flags; + lcurrent = lcurrent->next; + } else if (*cp == ';') { + list_optarg = (char *)NULL; + list_optflags = 0; + lcurrent = lcurrent->next; + } else { /* lcurrent->next == NULL */ + errstr[1] = c; + sh_needarg (errstr); + sp = 1; + list_optarg = (char *)NULL; + list_optflags = 0; + return('?'); + } + sp = 1; + } else if (*cp == '#') { + /* option requires a numeric argument */ + if (lcurrent->word->word[sp+1]) { + if (DIGIT(lcurrent->word->word[sp+1])) { + list_optarg = lcurrent->word->word + sp + 1; + list_optflags = 0; + lcurrent = lcurrent->next; + } else { + list_optarg = (char *)NULL; + list_optflags = 0; + } + } else { + if (lcurrent->next && legal_number(lcurrent->next->word->word, (intmax_t *)0)) { + lcurrent = lcurrent->next; + list_optarg = lcurrent->word->word; + list_optflags = lcurrent->word->flags; + lcurrent = lcurrent->next; + } else { + errstr[1] = c; + sh_neednumarg (errstr); + sp = 1; + list_optarg = (char *)NULL; + list_optflags = 0; + return ('?'); + } + } + + } else { + /* No argument, just return the option. */ + if (lcurrent->word->word[++sp] == '\0') { + sp = 1; + lcurrent = lcurrent->next; + } + list_optarg = (char *)NULL; + list_optflags = 0; + } + + return(c); +} + +/* + * reset_internal_getopt -- force the in[ft]ernal getopt to reset + */ + +void +reset_internal_getopt () +{ + lhead = lcurrent = loptend = (WORD_LIST *)NULL; + sp = 1; +} diff --git a/third_party/bash/bashgetopt.h b/third_party/bash/bashgetopt.h new file mode 100644 index 000000000..5ee811ad1 --- /dev/null +++ b/third_party/bash/bashgetopt.h @@ -0,0 +1,43 @@ +/* bashgetopt.h -- extern declarations for stuff defined in bashgetopt.c. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* See getopt.h for the explanation of these variables. */ + +#if !defined (__BASH_GETOPT_H) +# define __BASH_GETOPT_H + +#include "stdc.h" + +#define GETOPT_EOF -1 +#define GETOPT_HELP -99 + +extern char *list_optarg; +extern int list_optflags; + +extern int list_optopt; +extern int list_opttype; + +extern WORD_LIST *lcurrent; +extern WORD_LIST *loptend; + +extern int internal_getopt PARAMS((WORD_LIST *, char *)); +extern void reset_internal_getopt PARAMS((void)); + +#endif /* !__BASH_GETOPT_H */ diff --git a/third_party/bash/bashhist.c b/third_party/bash/bashhist.c new file mode 100644 index 000000000..fc7979abf --- /dev/null +++ b/third_party/bash/bashhist.c @@ -0,0 +1,1079 @@ +/* bashhist.c -- bash interface to the GNU history library. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HISTORY) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX + # include +# endif +# include +#endif + +#include "bashtypes.h" +#include +#include +#include "bashansi.h" +#include "posixstat.h" +#include "filecntl.h" + +#include "bashintl.h" + +#if defined (SYSLOG_HISTORY) +# include +#endif + +#include "shell.h" +#include "flags.h" +#include "parser.h" +#include "input.h" +#include "parser.h" /* for the struct dstack stuff. */ +#include "pathexp.h" /* for the struct ignorevar stuff */ +#include "bashhist.h" /* matching prototypes and declarations */ +#include "common.h" + +#include "third_party/readline/history.h" +#include "glob.h" +#include "strmatch.h" + +#if defined (READLINE) +# include "bashline.h" +extern int rl_done, rl_dispatching; /* should really include readline.h */ +#endif + +#ifndef HISTSIZE_DEFAULT +# define HISTSIZE_DEFAULT "500" +#endif + +#if !defined (errno) +extern int errno; +#endif + +static int histignore_item_func PARAMS((struct ign *)); +static int check_history_control PARAMS((char *)); +static void hc_erasedups PARAMS((char *)); +static void really_add_history PARAMS((char *)); + +static struct ignorevar histignore = +{ + "HISTIGNORE", + (struct ign *)0, + 0, + (char *)0, + (sh_iv_item_func_t *)histignore_item_func, +}; + +#define HIGN_EXPAND 0x01 + +/* Declarations of bash history variables. */ +/* Non-zero means to remember lines typed to the shell on the history + list. This is different than the user-controlled behaviour; this + becomes zero when we read lines from a file, for example. */ +int remember_on_history = 0; +int enable_history_list = -1; /* value for `set -o history' */ + +/* The number of lines that Bash has added to this history session. The + difference between the number of the top element in the history list + (offset from history_base) and the number of lines in the history file. + Appending this session's history to the history file resets this to 0. */ +int history_lines_this_session; + +/* The number of lines that Bash has read from the history file. */ +int history_lines_in_file; + +#if defined (BANG_HISTORY) +/* Non-zero means do no history expansion on this line, regardless + of what history_expansion says. */ +int history_expansion_inhibited; +/* If non-zero, double quotes can quote the history expansion character. */ +int double_quotes_inhibit_history_expansion = 0; +#endif + +/* With the old default, every line was saved in the history individually. + I.e., if the user enters: + bash$ for i in a b c + > do + > echo $i + > done + Each line will be individually saved in the history. + bash$ history + 10 for i in a b c + 11 do + 12 echo $i + 13 done + 14 history + If the variable command_oriented_history is set, multiple lines + which form one command will be saved as one history entry. + bash$ for i in a b c + > do + > echo $i + > done + bash$ history + 10 for i in a b c + do + echo $i + done + 11 history + The user can then recall the whole command all at once instead + of just being able to recall one line at a time. + + This is now enabled by default. + */ +int command_oriented_history = 1; + +/* Set to 1 if the first line of a possibly-multi-line command was saved + in the history list. Managed by maybe_add_history(), but global so + the history-manipluating builtins can see it. */ +int current_command_first_line_saved = 0; + +/* Set to the number of the most recent line of a possibly-multi-line command + that contains a shell comment. Used by bash_add_history() to determine + whether to add a newline or a semicolon. */ +int current_command_line_comment = 0; + +/* Non-zero means to store newlines in the history list when using + command_oriented_history rather than trying to use semicolons. */ +int literal_history; + +/* Non-zero means to append the history to the history file at shell + exit, even if the history has been stifled. */ +int force_append_history; + +/* A nit for picking at history saving. Flags have the following values: + + Value == 0 means save all lines parsed by the shell on the history. + Value & HC_IGNSPACE means save all lines that do not start with a space. + Value & HC_IGNDUPS means save all lines that do not match the last + line saved. + Value & HC_ERASEDUPS means to remove all other matching lines from the + history list before saving the latest line. */ +int history_control; + +/* Set to 1 if the last command was added to the history list successfully + as a separate history entry; set to 0 if the line was ignored or added + to a previous entry as part of command-oriented-history processing. */ +int hist_last_line_added; + +/* Set to 1 if builtins/history.def:push_history added the last history + entry. */ +int hist_last_line_pushed; + +#if defined (READLINE) +/* If non-zero, and readline is being used, the user is offered the + chance to re-edit a failed history expansion. */ +int history_reediting; + +/* If non-zero, and readline is being used, don't directly execute a + line with history substitution. Reload it into the editing buffer + instead and let the user further edit and confirm with a newline. */ +int hist_verify; + +#endif /* READLINE */ + +/* Non-zero means to not save function definitions in the history list. */ +int dont_save_function_defs; + +#if defined (BANG_HISTORY) +static int bash_history_inhibit_expansion PARAMS((char *, int)); +#endif +#if defined (READLINE) +static void re_edit PARAMS((char *)); +#endif +static int history_expansion_p PARAMS((char *)); +static int shell_comment PARAMS((char *)); +static int should_expand PARAMS((char *)); +static HIST_ENTRY *last_history_entry PARAMS((void)); +static char *expand_histignore_pattern PARAMS((char *)); +static int history_should_ignore PARAMS((char *)); + +#if defined (BANG_HISTORY) +/* Is the history expansion starting at string[i] one that should not + be expanded? */ +static int +bash_history_inhibit_expansion (string, i) + char *string; + int i; +{ + int t, si; + char hx[2]; + + hx[0] = history_expansion_char; + hx[1] = '\0'; + + /* The shell uses ! as a pattern negation character in globbing [...] + expressions, so let those pass without expansion. */ + if (i > 0 && (string[i - 1] == '[') && member (']', string + i + 1)) + return (1); + /* The shell uses ! as the indirect expansion character, so let those + expansions pass as well. */ + else if (i > 1 && string[i - 1] == '{' && string[i - 2] == '$' && + member ('}', string + i + 1)) + return (1); + /* The shell uses $! as a defined parameter expansion. */ + else if (i > 1 && string[i - 1] == '$' && string[i] == '!') + return (1); +#if defined (EXTENDED_GLOB) + else if (extended_glob && i > 1 && string[i+1] == '(' && member (')', string + i + 2)) + return (1); +#endif + + si = 0; + /* If we're supposed to be in single-quoted string, skip over the + single-quoted part and then look at what's left. */ + if (history_quoting_state == '\'') + { + si = skip_to_delim (string, 0, "'", SD_NOJMP|SD_HISTEXP); + if (string[si] == 0 || si >= i) + return (1); + si++; + } + + /* Make sure the history expansion should not be skipped by quoting or + command/process substitution. */ + if ((t = skip_to_histexp (string, si, hx, SD_NOJMP|SD_HISTEXP)) > 0) + { + /* Skip instances of history expansion appearing on the line before + this one. */ + while (t < i) + { + t = skip_to_histexp (string, t+1, hx, SD_NOJMP|SD_HISTEXP); + if (t <= 0) + return 0; + } + return (t > i); + } + else + return (0); +} +#endif + +void +bash_initialize_history () +{ + history_quotes_inhibit_expansion = 1; + history_search_delimiter_chars = ";&()|<>"; +#if defined (BANG_HISTORY) + history_inhibit_expansion_function = bash_history_inhibit_expansion; + sv_histchars ("histchars"); +#endif +} + +void +bash_history_reinit (interact) + int interact; +{ +#if defined (BANG_HISTORY) + history_expansion = (interact == 0) ? histexp_flag : HISTEXPAND_DEFAULT; + history_expansion_inhibited = (interact == 0) ? 1 - histexp_flag : 0; /* changed in bash_history_enable() */ + history_inhibit_expansion_function = bash_history_inhibit_expansion; +#endif + remember_on_history = enable_history_list; +} + +void +bash_history_disable () +{ + remember_on_history = 0; +#if defined (BANG_HISTORY) + history_expansion_inhibited = 1; +#endif +} + +void +bash_history_enable () +{ + remember_on_history = enable_history_list = 1; +#if defined (BANG_HISTORY) + history_expansion_inhibited = 0; + history_inhibit_expansion_function = bash_history_inhibit_expansion; +#endif + sv_history_control ("HISTCONTROL"); + sv_histignore ("HISTIGNORE"); +} + +/* Load the history list from the history file. */ +void +load_history () +{ + char *hf; + + /* Truncate history file for interactive shells which desire it. + Note that the history file is automatically truncated to the + size of HISTSIZE if the user does not explicitly set the size + differently. */ + set_if_not ("HISTSIZE", HISTSIZE_DEFAULT); + sv_histsize ("HISTSIZE"); + + set_if_not ("HISTFILESIZE", get_string_value ("HISTSIZE")); + sv_histsize ("HISTFILESIZE"); + + /* Read the history in HISTFILE into the history list. */ + hf = get_string_value ("HISTFILE"); + + if (hf && *hf && file_exists (hf)) + { + read_history (hf); + /* We have read all of the lines from the history file, even if we + read more lines than $HISTSIZE. Remember the total number of lines + we read so we don't count the last N lines as new over and over + again. */ + history_lines_in_file = history_lines_read_from_file; + using_history (); + /* history_lines_in_file = where_history () + history_base - 1; */ + } +} + +void +bash_clear_history () +{ + clear_history (); + history_lines_this_session = 0; + /* XXX - reset history_lines_read_from_file? */ +} + +/* Delete and free the history list entry at offset I. */ +int +bash_delete_histent (i) + int i; +{ + HIST_ENTRY *discard; + + discard = remove_history (i); + if (discard) + { + free_history_entry (discard); + history_lines_this_session--; + } + return discard != 0; +} + +int +bash_delete_history_range (first, last) + int first, last; +{ + register int i; + HIST_ENTRY **discard_list; + + discard_list = remove_history_range (first, last); + if (discard_list == 0) + return 0; + for (i = 0; discard_list[i]; i++) + free_history_entry (discard_list[i]); + free (discard_list); + history_lines_this_session -= i; + + return 1; +} + +int +bash_delete_last_history () +{ + register int i; + HIST_ENTRY **hlist, *histent; + int r; + + hlist = history_list (); + if (hlist == NULL) + return 0; + + for (i = 0; hlist[i]; i++) + ; + i--; + + /* History_get () takes a parameter that must be offset by history_base. */ + histent = history_get (history_base + i); /* Don't free this */ + if (histent == NULL) + return 0; + + r = bash_delete_histent (i); + + if (where_history () > history_length) + history_set_pos (history_length); + + return r; +} + +#ifdef INCLUDE_UNUSED +/* Write the existing history out to the history file. */ +void +save_history () +{ + char *hf; + int r; + + hf = get_string_value ("HISTFILE"); + if (hf && *hf && file_exists (hf)) + { + /* Append only the lines that occurred this session to + the history file. */ + using_history (); + + if (history_lines_this_session <= where_history () || force_append_history) + r = append_history (history_lines_this_session, hf); + else + r = write_history (hf); + sv_histsize ("HISTFILESIZE"); + } +} +#endif + +int +maybe_append_history (filename) + char *filename; +{ + int fd, result, histlen; + struct stat buf; + + result = EXECUTION_SUCCESS; + if (history_lines_this_session > 0) + { + /* If the filename was supplied, then create it if necessary. */ + if (stat (filename, &buf) == -1 && errno == ENOENT) + { + fd = open (filename, O_WRONLY|O_CREAT, 0600); + if (fd < 0) + { + builtin_error (_("%s: cannot create: %s"), filename, strerror (errno)); + return (EXECUTION_FAILURE); + } + close (fd); + } + /* cap the number of lines we write at the length of the history list */ + histlen = where_history (); + if (histlen > 0 && history_lines_this_session > histlen) + history_lines_this_session = histlen; /* reset below anyway */ + result = append_history (history_lines_this_session, filename); + /* Pretend we already read these lines from the file because we just + added them */ + history_lines_in_file += history_lines_this_session; + history_lines_this_session = 0; + } + else + history_lines_this_session = 0; /* reset if > where_history() */ + + return (result); +} + +/* If this is an interactive shell, then append the lines executed + this session to the history file. */ +int +maybe_save_shell_history () +{ + int result; + char *hf; + + result = 0; + if (history_lines_this_session > 0) + { + hf = get_string_value ("HISTFILE"); + + if (hf && *hf) + { + /* If the file doesn't exist, then create it. */ + if (file_exists (hf) == 0) + { + int file; + file = open (hf, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (file != -1) + close (file); + } + + /* Now actually append the lines if the history hasn't been + stifled. If the history has been stifled, rewrite the + history file. */ + using_history (); + if (history_lines_this_session <= where_history () || force_append_history) + { + result = append_history (history_lines_this_session, hf); + history_lines_in_file += history_lines_this_session; + } + else + { + result = write_history (hf); + history_lines_in_file = history_lines_written_to_file; + /* history_lines_in_file = where_history () + history_base - 1; */ + } + history_lines_this_session = 0; + + sv_histsize ("HISTFILESIZE"); + } + } + return (result); +} + +#if defined (READLINE) +/* Tell readline () that we have some text for it to edit. */ +static void +re_edit (text) + char *text; +{ + if (bash_input.type == st_stdin) + bash_re_edit (text); +} +#endif /* READLINE */ + +/* Return 1 if this line needs history expansion. */ +static int +history_expansion_p (line) + char *line; +{ + register char *s; + + for (s = line; *s; s++) + if (*s == history_expansion_char || *s == history_subst_char) + return 1; + return 0; +} + +/* Do pre-processing on LINE. If PRINT_CHANGES is non-zero, then + print the results of expanding the line if there were any changes. + If there is an error, return NULL, otherwise the expanded line is + returned. If ADDIT is non-zero the line is added to the history + list after history expansion. ADDIT is just a suggestion; + REMEMBER_ON_HISTORY can veto, and does. + Right now this does history expansion. */ +char * +pre_process_line (line, print_changes, addit) + char *line; + int print_changes, addit; +{ + char *history_value; + char *return_value; + int expanded; + + return_value = line; + expanded = 0; + +# if defined (BANG_HISTORY) + /* History expand the line. If this results in no errors, then + add that line to the history if ADDIT is non-zero. */ + if (!history_expansion_inhibited && history_expansion && history_expansion_p (line)) + { + int old_len; + + /* If we are expanding the second or later line of a multi-line + command, decrease history_length so references to history expansions + in these lines refer to the previous history entry and not the + current command. */ + old_len = history_length; + if (history_length > 0 && command_oriented_history && current_command_first_line_saved && current_command_line_count > 1) + history_length--; + expanded = history_expand (line, &history_value); + if (history_length >= 0 && command_oriented_history && current_command_first_line_saved && current_command_line_count > 1) + history_length = old_len; + + if (expanded) + { + if (print_changes) + { + if (expanded < 0) + internal_error ("%s", history_value); +#if defined (READLINE) + else if (hist_verify == 0 || expanded == 2) +#else + else +#endif + fprintf (stderr, "%s\n", history_value); + } + + /* If there was an error, return NULL. */ + if (expanded < 0 || expanded == 2) /* 2 == print only */ + { +# if defined (READLINE) + if (expanded == 2 && rl_dispatching == 0 && *history_value) +# else + if (expanded == 2 && *history_value) +# endif /* !READLINE */ + maybe_add_history (history_value); + + free (history_value); + +# if defined (READLINE) + /* New hack. We can allow the user to edit the + failed history expansion. */ + if (history_reediting && expanded < 0 && rl_done) + re_edit (line); +# endif /* READLINE */ + return ((char *)NULL); + } + +# if defined (READLINE) + if (hist_verify && expanded == 1) + { + re_edit (history_value); + free (history_value); + return ((char *)NULL); + } +# endif + } + + /* Let other expansions know that return_value can be free'ed, + and that a line has been added to the history list. Note + that we only add lines that have something in them. */ + expanded = 1; + return_value = history_value; + } +# endif /* BANG_HISTORY */ + + if (addit && remember_on_history && *return_value) + maybe_add_history (return_value); + +#if 0 + if (expanded == 0) + return_value = savestring (line); +#endif + + return (return_value); +} + +/* Return 1 if the first non-whitespace character in LINE is a `#', indicating + that the line is a shell comment. Return 2 if there is a comment after the + first non-whitespace character. Return 0 if the line does not contain a + comment. */ +static int +shell_comment (line) + char *line; +{ + char *p; + int n; + + if (dstack.delimiter_depth != 0 || (parser_state & PST_HEREDOC)) + return 0; + if (line == 0) + return 0; + for (p = line; p && *p && whitespace (*p); p++) + ; + if (p && *p == '#') + return 1; + n = skip_to_delim (line, p - line, "#", SD_NOJMP|SD_GLOB|SD_EXTGLOB|SD_COMPLETE); + return (line[n] == '#') ? 2 : 0; +} + +#ifdef INCLUDE_UNUSED +/* Remove shell comments from LINE. A `#' and anything after it is a comment. + This isn't really useful yet, since it doesn't handle quoting. */ +static char * +filter_comments (line) + char *line; +{ + char *p; + + for (p = line; p && *p && *p != '#'; p++) + ; + if (p && *p == '#') + *p = '\0'; + return (line); +} +#endif + +/* Check LINE against what HISTCONTROL says to do. Returns 1 if the line + should be saved; 0 if it should be discarded. */ +static int +check_history_control (line) + char *line; +{ + HIST_ENTRY *temp; + int r; + + if (history_control == 0) + return 1; + + /* ignorespace or ignoreboth */ + if ((history_control & HC_IGNSPACE) && *line == ' ') + return 0; + + /* ignoredups or ignoreboth */ + if (history_control & HC_IGNDUPS) + { + using_history (); + temp = previous_history (); + + r = (temp == 0 || STREQ (temp->line, line) == 0); + + using_history (); + + if (r == 0) + return r; + } + + return 1; +} + +/* Remove all entries matching LINE from the history list. Triggered when + HISTCONTROL includes `erasedups'. */ +static void +hc_erasedups (line) + char *line; +{ + HIST_ENTRY *temp; + int r; + + using_history (); + while (temp = previous_history ()) + { + if (STREQ (temp->line, line)) + { + r = where_history (); + temp = remove_history (r); + if (temp) + free_history_entry (temp); + } + } + using_history (); +} + +/* Add LINE to the history list, handling possibly multi-line compound + commands. We note whether or not we save the first line of each command + (which is usually the entire command and history entry), and don't add + the second and subsequent lines of a multi-line compound command if we + didn't save the first line. We don't usually save shell comment lines in + compound commands in the history, because they could have the effect of + commenting out the rest of the command when the entire command is saved as + a single history entry (when COMMAND_ORIENTED_HISTORY is enabled). If + LITERAL_HISTORY is set, we're saving lines in the history with embedded + newlines, so it's OK to save comment lines. If we're collecting the body + of a here-document, we should act as if literal_history is enabled, because + we want to save the entire contents of the here-document as it was + entered. We also make sure to save multiple-line quoted strings or other + constructs. */ +void +maybe_add_history (line) + char *line; +{ + int is_comment; + + hist_last_line_added = 0; + is_comment = shell_comment (line); + + /* Don't use the value of history_control to affect the second + and subsequent lines of a multi-line command (old code did + this only when command_oriented_history is enabled). */ + if (current_command_line_count > 1) + { + if (current_command_first_line_saved && + ((parser_state & PST_HEREDOC) || literal_history || dstack.delimiter_depth != 0 || is_comment != 1)) + bash_add_history (line); + current_command_line_comment = is_comment ? current_command_line_count : -2; + return; + } + + /* This is the first line of a (possible multi-line) command. Note whether + or not we should save the first line and remember it. */ + current_command_line_comment = is_comment ? current_command_line_count : -2; + current_command_first_line_saved = check_add_history (line, 0); +} + +/* Just check LINE against HISTCONTROL and HISTIGNORE and add it to the + history if it's OK. Used by `history -s' as well as maybe_add_history(). + Returns 1 if the line was saved in the history, 0 otherwise. */ +int +check_add_history (line, force) + char *line; + int force; +{ + if (check_history_control (line) && history_should_ignore (line) == 0) + { + /* We're committed to saving the line. If the user has requested it, + remove other matching lines from the history. */ + if (history_control & HC_ERASEDUPS) + hc_erasedups (line); + + if (force) + { + really_add_history (line); + using_history (); + } + else + bash_add_history (line); + return 1; + } + return 0; +} + +#if defined (SYSLOG_HISTORY) +#define SYSLOG_MAXMSG 1024 +#define SYSLOG_MAXLEN SYSLOG_MAXMSG +#define SYSLOG_MAXHDR 256 + +#ifndef OPENLOG_OPTS +#define OPENLOG_OPTS 0 +#endif + +#if defined (SYSLOG_SHOPT) +int syslog_history = SYSLOG_SHOPT; +#else +int syslog_history = 1; +#endif + +void +bash_syslog_history (line) + const char *line; +{ + char trunc[SYSLOG_MAXLEN], *msg; + char loghdr[SYSLOG_MAXHDR]; + char seqbuf[32], *seqnum; + int hdrlen, msglen, seqlen, chunks, i; + static int first = 1; + + if (first) + { + openlog (shell_name, OPENLOG_OPTS, SYSLOG_FACILITY); + first = 0; + } + + hdrlen = snprintf (loghdr, sizeof(loghdr), "HISTORY: PID=%d UID=%d", getpid(), current_user.uid); + msglen = strlen (line); + + if ((msglen + hdrlen + 1) < SYSLOG_MAXLEN) + syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "%s %s", loghdr, line); + else + { + chunks = ((msglen + hdrlen) / SYSLOG_MAXLEN) + 1; + for (msg = line, i = 0; i < chunks; i++) + { + seqnum = inttostr (i + 1, seqbuf, sizeof (seqbuf)); + seqlen = STRLEN (seqnum); + + /* 7 == "(seq=) " */ + strncpy (trunc, msg, SYSLOG_MAXLEN - hdrlen - seqlen - 7 - 1); + trunc[SYSLOG_MAXLEN - 1] = '\0'; + syslog (SYSLOG_FACILITY|SYSLOG_LEVEL, "%s (seq=%s) %s", loghdr, seqnum, trunc); + msg += SYSLOG_MAXLEN - hdrlen - seqlen - 8; + } + } +} +#endif + +/* Add a line to the history list. + The variable COMMAND_ORIENTED_HISTORY controls the style of history + remembering; when non-zero, and LINE is not the first line of a + complete parser construct, append LINE to the last history line instead + of adding it as a new line. */ +void +bash_add_history (line) + char *line; +{ + int add_it, offset, curlen, is_comment; + HIST_ENTRY *current, *old; + char *chars_to_add, *new_line; + + add_it = 1; + if (command_oriented_history && current_command_line_count > 1) + { + is_comment = shell_comment (line); + + /* The second and subsequent lines of a here document have the trailing + newline preserved. We don't want to add extra newlines here, but we + do want to add one after the first line (which is the command that + contains the here-doc specifier). parse.y:history_delimiting_chars() + does the right thing to take care of this for us. We don't want to + add extra newlines if the user chooses to enable literal_history, + so we have to duplicate some of what that function does here. */ + /* If we're in a here document and past the first line, + (current_command_line_count > 2) + don't add a newline here. This will also take care of the literal_history + case if the other conditions are met. */ + if ((parser_state & PST_HEREDOC) && here_doc_first_line == 0 && line[strlen (line) - 1] == '\n') + chars_to_add = ""; + else if (current_command_line_count == current_command_line_comment+1) + chars_to_add = "\n"; + else if (literal_history) + chars_to_add = "\n"; + else + chars_to_add = history_delimiting_chars (line); + + using_history (); + current = previous_history (); + + current_command_line_comment = is_comment ? current_command_line_count : -2; + + if (current) + { + /* If the previous line ended with an escaped newline (escaped + with backslash, but otherwise unquoted), then remove the quoted + newline, since that is what happens when the line is parsed. */ + curlen = strlen (current->line); + + if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\\' && + current->line[curlen - 2] != '\\') + { + current->line[curlen - 1] = '\0'; + curlen--; + chars_to_add = ""; + } + + /* If we're not in some kind of quoted construct, the current history + entry ends with a newline, and we're going to add a semicolon, + don't. In some cases, it results in a syntax error (e.g., before + a close brace), and it should not be needed. */ + if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\n' && *chars_to_add == ';') + chars_to_add++; + + new_line = (char *)xmalloc (1 + + curlen + + strlen (line) + + strlen (chars_to_add)); + sprintf (new_line, "%s%s%s", current->line, chars_to_add, line); + offset = where_history (); + old = replace_history_entry (offset, new_line, current->data); + free (new_line); + + if (old) + free_history_entry (old); + + add_it = 0; + } + } + + if (add_it && history_is_stifled() && history_length == 0 && history_length == history_max_entries) + add_it = 0; + + if (add_it) + really_add_history (line); + +#if defined (SYSLOG_HISTORY) + if (syslog_history) + bash_syslog_history (line); +#endif + + using_history (); +} + +static void +really_add_history (line) + char *line; +{ + hist_last_line_added = 1; + hist_last_line_pushed = 0; + add_history (line); + history_lines_this_session++; +} + +int +history_number () +{ + using_history (); + return ((remember_on_history || enable_history_list) ? history_base + where_history () : 1); +} + +static int +should_expand (s) + char *s; +{ + char *p; + + for (p = s; p && *p; p++) + { + if (*p == '\\') + p++; + else if (*p == '&') + return 1; + } + return 0; +} + +static int +histignore_item_func (ign) + struct ign *ign; +{ + if (should_expand (ign->val)) + ign->flags |= HIGN_EXPAND; + return (0); +} + +void +setup_history_ignore (varname) + char *varname; +{ + setup_ignore_patterns (&histignore); +} + +static HIST_ENTRY * +last_history_entry () +{ + HIST_ENTRY *he; + + using_history (); + he = previous_history (); + using_history (); + return he; +} + +char * +last_history_line () +{ + HIST_ENTRY *he; + + he = last_history_entry (); + if (he == 0) + return ((char *)NULL); + return he->line; +} + +static char * +expand_histignore_pattern (pat) + char *pat; +{ + HIST_ENTRY *phe; + char *ret; + + phe = last_history_entry (); + + if (phe == (HIST_ENTRY *)0) + return (savestring (pat)); + + ret = strcreplace (pat, '&', phe->line, 1); + + return ret; +} + +/* Return 1 if we should not put LINE into the history according to the + patterns in HISTIGNORE. */ +static int +history_should_ignore (line) + char *line; +{ + register int i, match; + char *npat; + + if (histignore.num_ignores == 0) + return 0; + + for (i = match = 0; i < histignore.num_ignores; i++) + { + if (histignore.ignores[i].flags & HIGN_EXPAND) + npat = expand_histignore_pattern (histignore.ignores[i].val); + else + npat = histignore.ignores[i].val; + + match = strmatch (npat, line, FNMATCH_EXTFLAG) != FNM_NOMATCH; + + if (histignore.ignores[i].flags & HIGN_EXPAND) + free (npat); + + if (match) + break; + } + + return match; +} +#endif /* HISTORY */ diff --git a/third_party/bash/bashhist.h b/third_party/bash/bashhist.h new file mode 100644 index 000000000..615f5d256 --- /dev/null +++ b/third_party/bash/bashhist.h @@ -0,0 +1,89 @@ +/* bashhist.h -- interface to the bash history functions in bashhist.c. */ + +/* Copyright (C) 1993-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_BASHHIST_H_) +#define _BASHHIST_H_ + +#include "stdc.h" + +/* Flag values for history_control */ +#define HC_IGNSPACE 0x01 +#define HC_IGNDUPS 0x02 +#define HC_ERASEDUPS 0x04 + +#define HC_IGNBOTH (HC_IGNSPACE|HC_IGNDUPS) + +#if defined (STRICT_POSIX) +# undef HISTEXPAND_DEFAULT +# define HISTEXPAND_DEFAULT 0 +#else +# if !defined (HISTEXPAND_DEFAULT) +# define HISTEXPAND_DEFAULT 1 +# endif /* !HISTEXPAND_DEFAULT */ +#endif + +extern int remember_on_history; +extern int enable_history_list; /* value for `set -o history' */ +extern int literal_history; /* controlled by `shopt lithist' */ +extern int force_append_history; +extern int history_lines_this_session; +extern int history_lines_in_file; +extern int history_expansion; +extern int history_control; +extern int command_oriented_history; +extern int current_command_first_line_saved; +extern int current_command_first_line_comment; +extern int hist_last_line_added; +extern int hist_last_line_pushed; + +extern int dont_save_function_defs; + +# if defined (READLINE) +extern int hist_verify; +# endif + +# if defined (BANG_HISTORY) +extern int history_expansion_inhibited; +extern int double_quotes_inhibit_history_expansion; +# endif /* BANG_HISTORY */ + +extern void bash_initialize_history PARAMS((void)); +extern void bash_history_reinit PARAMS((int)); +extern void bash_history_disable PARAMS((void)); +extern void bash_history_enable PARAMS((void)); +extern void bash_clear_history PARAMS((void)); +extern int bash_delete_histent PARAMS((int)); +extern int bash_delete_history_range PARAMS((int, int)); +extern int bash_delete_last_history PARAMS((void)); +extern void load_history PARAMS((void)); +extern void save_history PARAMS((void)); +extern int maybe_append_history PARAMS((char *)); +extern int maybe_save_shell_history PARAMS((void)); +extern char *pre_process_line PARAMS((char *, int, int)); +extern void maybe_add_history PARAMS((char *)); +extern void bash_add_history PARAMS((char *)); +extern int check_add_history PARAMS((char *, int)); +extern int history_number PARAMS((void)); + +extern void setup_history_ignore PARAMS((char *)); + +extern char *last_history_line PARAMS((void)); + +#endif /* _BASHHIST_H_ */ diff --git a/third_party/bash/bashintl.h b/third_party/bash/bashintl.h new file mode 100644 index 000000000..dd3268331 --- /dev/null +++ b/third_party/bash/bashintl.h @@ -0,0 +1,54 @@ +/* bashintl.h -- Internationalization functions and defines. */ + +/* Copyright (C) 1996-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_BASHINTL_H_) +#define _BASHINTL_H_ + +#if defined (BUILDTOOL) +# undef ENABLE_NLS +# define ENABLE_NLS 0 +#endif + +/* Include this *after* config.h */ +#include "gettext.h" + +#if defined (HAVE_LOCALE_H) +# include +#endif + +#define _(msgid) gettext(msgid) +#define N_(msgid) msgid +#define D_(d, msgid) dgettext(d, msgid) + +#define P_(m1, m2, n) ngettext(m1, m2, n) + +#if defined (HAVE_SETLOCALE) && !defined (LC_ALL) +# undef HAVE_SETLOCALE +#endif + +#if !defined (HAVE_SETLOCALE) +# define setlocale(cat, loc) +#endif + +#if !defined (HAVE_LOCALE_H) || !defined (HAVE_LOCALECONV) +# define locale_decpoint() '.' +#endif + +#endif /* !_BASHINTL_H_ */ diff --git a/third_party/bash/bashjmp.h b/third_party/bash/bashjmp.h new file mode 100644 index 000000000..1a4721bc8 --- /dev/null +++ b/third_party/bash/bashjmp.h @@ -0,0 +1,47 @@ +/* bashjmp.h -- wrapper for setjmp.h with necessary bash definitions. */ + +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _BASHJMP_H_ +#define _BASHJMP_H_ + +#include "posixjmp.h" + +extern procenv_t top_level; +extern procenv_t subshell_top_level; +extern procenv_t return_catch; /* used by `return' builtin */ +extern procenv_t wait_intr_buf; + +extern int no_longjmp_on_fatal_error; + +#define SHFUNC_RETURN() sh_longjmp (return_catch, 1) + +#define COPY_PROCENV(old, save) \ + xbcopy ((char *)old, (char *)save, sizeof (procenv_t)); + +/* Values for the second argument to longjmp/siglongjmp. */ +#define NOT_JUMPED 0 /* Not returning from a longjmp. */ +#define FORCE_EOF 1 /* We want to stop parsing. */ +#define DISCARD 2 /* Discard current command. */ +#define EXITPROG 3 /* Unconditionally exit the program now. */ +#define ERREXIT 4 /* Exit due to error condition */ +#define SIGEXIT 5 /* Exit due to fatal terminating signal */ +#define EXITBLTIN 6 /* Exit due to the exit builtin. */ + +#endif /* _BASHJMP_H_ */ diff --git a/third_party/bash/bashline.c b/third_party/bash/bashline.c new file mode 100644 index 000000000..2870bd927 --- /dev/null +++ b/third_party/bash/bashline.c @@ -0,0 +1,4839 @@ +/* bashline.c -- Bash's interface to the readline library. */ + +/* Copyright (C) 1987-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (READLINE) + +#include "bashtypes.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_GRP_H) +# include +#endif + +#if defined (HAVE_NETDB_H) +# include +#endif + +#include + +#include +#include "chartypes.h" +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "input.h" +#include "parser.h" +#include "builtins.h" +#include "bashhist.h" +#include "bashline.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "pathexp.h" +#include "shmbutil.h" +#include "trap.h" +#include "flags.h" +#include "timer.h" + +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include /* mbschr */ +#endif + +#include "common.h" +#include "builtext.h" /* for read_builtin */ + +#include "third_party/readline/rlconf.h" +#include "third_party/readline/readline.h" +#include "third_party/readline/history.h" +#include "third_party/readline/rlmbutil.h" + +#include "glob.h" + +#if defined (ALIAS) +# include "alias.h" +#endif + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +/* These should agree with the defines for emacs_mode and vi_mode in + rldefs.h, even though that's not a public readline header file. */ +#ifndef EMACS_EDITING_MODE +# define NO_EDITING_MODE -1 +# define EMACS_EDITING_MODE 1 +# define VI_EDITING_MODE 0 +#endif + +/* Copied from rldefs.h, since that's not a public readline header file. */ +#ifndef FUNCTION_TO_KEYMAP + +#if defined (CRAY) +# define FUNCTION_TO_KEYMAP(map, key) (Keymap)((int)map[key].function) +# define KEYMAP_TO_FUNCTION(data) (rl_command_func_t *)((int)(data)) +#else +# define FUNCTION_TO_KEYMAP(map, key) (Keymap)(map[key].function) +# define KEYMAP_TO_FUNCTION(data) (rl_command_func_t *)(data) +#endif + +#endif + +#define RL_BOOLEAN_VARIABLE_VALUE(s) ((s)[0] == 'o' && (s)[1] == 'n' && (s)[2] == '\0') + +#if defined (BRACE_COMPLETION) +extern int bash_brace_completion PARAMS((int, int)); +#endif /* BRACE_COMPLETION */ + +/* To avoid including curses.h/term.h/termcap.h and that whole mess. */ +#ifdef _MINIX +extern int tputs PARAMS((const char *string, int nlines, void (*outx)(int))); +#else +extern int tputs PARAMS((const char *string, int nlines, int (*outx)(int))); +#endif + +/* Forward declarations */ + +/* Functions bound to keys in Readline for Bash users. */ +static int shell_expand_line PARAMS((int, int)); +static int display_shell_version PARAMS((int, int)); + +static int bash_ignore_filenames PARAMS((char **)); +static int bash_ignore_everything PARAMS((char **)); +static int bash_progcomp_ignore_filenames PARAMS((char **)); + +#if defined (BANG_HISTORY) +static char *history_expand_line_internal PARAMS((char *)); +static int history_expand_line PARAMS((int, int)); +static int tcsh_magic_space PARAMS((int, int)); +#endif /* BANG_HISTORY */ +#ifdef ALIAS +static int alias_expand_line PARAMS((int, int)); +#endif +#if defined (BANG_HISTORY) && defined (ALIAS) +static int history_and_alias_expand_line PARAMS((int, int)); +#endif + +static int bash_forward_shellword PARAMS((int, int)); +static int bash_backward_shellword PARAMS((int, int)); +static int bash_kill_shellword PARAMS((int, int)); +static int bash_backward_kill_shellword PARAMS((int, int)); +static int bash_transpose_shellwords PARAMS((int, int)); + +static int bash_spell_correct_shellword PARAMS((int, int)); + +/* Helper functions for Readline. */ +static char *restore_tilde PARAMS((char *, char *)); +static char *maybe_restore_tilde PARAMS((char *, char *)); + +static char *bash_filename_rewrite_hook PARAMS((char *, int)); + +static void bash_directory_expansion PARAMS((char **)); +static int bash_filename_stat_hook PARAMS((char **)); +static int bash_command_name_stat_hook PARAMS((char **)); +static int bash_directory_completion_hook PARAMS((char **)); +static int filename_completion_ignore PARAMS((char **)); +static int bash_push_line PARAMS((void)); + +static int executable_completion PARAMS((const char *, int)); + +static rl_icppfunc_t *save_directory_hook PARAMS((void)); +static void restore_directory_hook PARAMS((rl_icppfunc_t)); + +static int directory_exists PARAMS((const char *, int)); + +static void cleanup_expansion_error PARAMS((void)); +static void maybe_make_readline_line PARAMS((char *)); +static void set_up_new_line PARAMS((char *)); + +static int check_redir PARAMS((int)); +static char **attempt_shell_completion PARAMS((const char *, int, int)); +static char *variable_completion_function PARAMS((const char *, int)); +static char *hostname_completion_function PARAMS((const char *, int)); +static char *command_subst_completion_function PARAMS((const char *, int)); + +static void build_history_completion_array PARAMS((void)); +static char *history_completion_generator PARAMS((const char *, int)); +static int dynamic_complete_history PARAMS((int, int)); +static int bash_dabbrev_expand PARAMS((int, int)); + +static void initialize_hostname_list PARAMS((void)); +static void add_host_name PARAMS((char *)); +static void snarf_hosts_from_file PARAMS((char *)); +static char **hostnames_matching PARAMS((char *)); + +static void _ignore_completion_names PARAMS((char **, sh_ignore_func_t *)); +static int name_is_acceptable PARAMS((const char *)); +static int test_for_directory PARAMS((const char *)); +static int test_for_canon_directory PARAMS((const char *)); +static int return_zero PARAMS((const char *)); + +static char *bash_dequote_filename PARAMS((char *, int)); +static char *quote_word_break_chars PARAMS((char *)); +static int bash_check_expchar PARAMS((char *, int, int *, int *)); +static void set_filename_quote_chars PARAMS((int, int, int)); +static void set_filename_bstab PARAMS((const char *)); +static char *bash_quote_filename PARAMS((char *, int, char *)); + +#ifdef _MINIX +static void putx PARAMS((int)); +#else +static int putx PARAMS((int)); +#endif +static int readline_get_char_offset PARAMS((int)); +static void readline_set_char_offset PARAMS((int, int *)); + +static Keymap get_cmd_xmap_from_edit_mode PARAMS((void)); +static Keymap get_cmd_xmap_from_keymap PARAMS((Keymap)); + +static void init_unix_command_map PARAMS((void)); +static int isolate_sequence PARAMS((char *, int, int, int *)); + +static int set_saved_history PARAMS((void)); + +#if defined (ALIAS) +static int posix_edit_macros PARAMS((int, int)); +#endif + +static int bash_event_hook PARAMS((void)); + +#if defined (PROGRAMMABLE_COMPLETION) +static int find_cmd_start PARAMS((int)); +static int find_cmd_end PARAMS((int)); +static char *find_cmd_name PARAMS((int, int *, int *)); +static char *prog_complete_return PARAMS((const char *, int)); + +static char **prog_complete_matches; +#endif + +extern int no_symbolic_links; +extern STRING_INT_ALIST word_token_alist[]; +extern sh_timer *read_timeout; + +/* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual + completion functions which indicate what type of completion should be + done (at or before point) that can be bound to key sequences with + the readline library. */ +#define SPECIFIC_COMPLETION_FUNCTIONS + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) +static int bash_specific_completion PARAMS((int, rl_compentry_func_t *)); + +static int bash_complete_filename_internal PARAMS((int)); +static int bash_complete_username_internal PARAMS((int)); +static int bash_complete_hostname_internal PARAMS((int)); +static int bash_complete_variable_internal PARAMS((int)); +static int bash_complete_command_internal PARAMS((int)); + +static int bash_complete_filename PARAMS((int, int)); +static int bash_possible_filename_completions PARAMS((int, int)); +static int bash_complete_username PARAMS((int, int)); +static int bash_possible_username_completions PARAMS((int, int)); +static int bash_complete_hostname PARAMS((int, int)); +static int bash_possible_hostname_completions PARAMS((int, int)); +static int bash_complete_variable PARAMS((int, int)); +static int bash_possible_variable_completions PARAMS((int, int)); +static int bash_complete_command PARAMS((int, int)); +static int bash_possible_command_completions PARAMS((int, int)); + +static int completion_glob_pattern PARAMS((char *)); +static char *glob_complete_word PARAMS((const char *, int)); +static int bash_glob_completion_internal PARAMS((int)); +static int bash_glob_complete_word PARAMS((int, int)); +static int bash_glob_expand_word PARAMS((int, int)); +static int bash_glob_list_expansions PARAMS((int, int)); + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + +static int edit_and_execute_command PARAMS((int, int, int, char *)); +#if defined (VI_MODE) +static int vi_edit_and_execute_command PARAMS((int, int)); +static int bash_vi_complete PARAMS((int, int)); +#endif +static int emacs_edit_and_execute_command PARAMS((int, int)); + +/* Non-zero once initialize_readline () has been called. */ +int bash_readline_initialized = 0; + +/* If non-zero, we do hostname completion, breaking words at `@' and + trying to complete the stuff after the `@' from our own internal + host list. */ +int perform_hostname_completion = 1; + +/* If non-zero, we don't do command completion on an empty line. */ +int no_empty_command_completion; + +/* Set FORCE_FIGNORE if you want to honor FIGNORE even if it ignores the + only possible matches. Set to 0 if you want to match filenames if they + are the only possible matches, even if FIGNORE says to. */ +int force_fignore = 1; + +/* Perform spelling correction on directory names during word completion */ +int dircomplete_spelling = 0; + +/* Expand directory names during word/filename completion. */ +#if DIRCOMPLETE_EXPAND_DEFAULT +int dircomplete_expand = 1; +int dircomplete_expand_relpath = 1; +#else +int dircomplete_expand = 0; +int dircomplete_expand_relpath = 0; +#endif + +/* When non-zero, perform `normal' shell quoting on completed filenames + even when the completed name contains a directory name with a shell + variable reference, so dollar signs in a filename get quoted appropriately. + Set to zero to remove dollar sign (and braces or parens as needed) from + the set of characters that will be quoted. */ +int complete_fullquote = 1; + +static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:"; +static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:"; +/* )) */ + +static const char *default_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{~"; /*}*/ +static char *custom_filename_quote_characters = 0; +static char filename_bstab[256]; + +static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL; + +static int dot_in_path = 0; + +/* Set to non-zero when dabbrev-expand is running */ +static int dabbrev_expand_active = 0; + +/* What kind of quoting is performed by bash_quote_filename: + COMPLETE_DQUOTE = double-quoting the filename + COMPLETE_SQUOTE = single_quoting the filename + COMPLETE_BSQUOTE = backslash-quoting special chars in the filename +*/ +#define COMPLETE_DQUOTE 1 +#define COMPLETE_SQUOTE 2 +#define COMPLETE_BSQUOTE 3 +static int completion_quoting_style = COMPLETE_BSQUOTE; + +/* Flag values for the final argument to bash_default_completion */ +#define DEFCOMP_CMDPOS 1 + +static rl_command_func_t *vi_tab_binding = rl_complete; + +/* Change the readline VI-mode keymaps into or out of Posix.2 compliance. + Called when the shell is put into or out of `posix' mode. */ +void +posix_readline_initialize (on_or_off) + int on_or_off; +{ + static char kseq[2] = { CTRL ('I'), 0 }; /* TAB */ + + if (on_or_off) + rl_variable_bind ("comment-begin", "#"); +#if defined (VI_MODE) + if (on_or_off) + { + vi_tab_binding = rl_function_of_keyseq (kseq, vi_insertion_keymap, (int *)NULL); + rl_bind_key_in_map (CTRL ('I'), rl_insert, vi_insertion_keymap); + } + else + { + if (rl_function_of_keyseq (kseq, vi_insertion_keymap, (int *)NULL) == rl_insert) + rl_bind_key_in_map (CTRL ('I'), vi_tab_binding, vi_insertion_keymap); + } +#endif +} + +void +reset_completer_word_break_chars () +{ + rl_completer_word_break_characters = perform_hostname_completion ? savestring (bash_completer_word_break_characters) : savestring (bash_nohostname_word_break_characters); +} + +/* When this function returns, rl_completer_word_break_characters points to + dynamically allocated memory. */ +int +enable_hostname_completion (on_or_off) + int on_or_off; +{ + int old_value; + char *nv, *nval; + const char *at; + + old_value = perform_hostname_completion; + + if (on_or_off) + { + perform_hostname_completion = 1; + rl_special_prefixes = "$@"; + } + else + { + perform_hostname_completion = 0; + rl_special_prefixes = "$"; + } + + /* Now we need to figure out how to appropriately modify and assign + rl_completer_word_break_characters depending on whether we want + hostname completion on or off. */ + + /* If this is the first time this has been called + (bash_readline_initialized == 0), use the sames values as before, but + allocate new memory for rl_completer_word_break_characters. */ + + if (bash_readline_initialized == 0 && + (rl_completer_word_break_characters == 0 || + rl_completer_word_break_characters == rl_basic_word_break_characters)) + { + if (on_or_off) + rl_completer_word_break_characters = savestring (bash_completer_word_break_characters); + else + rl_completer_word_break_characters = savestring (bash_nohostname_word_break_characters); + } + else + { + /* See if we have anything to do. */ + at = strchr (rl_completer_word_break_characters, '@'); + if ((at == 0 && on_or_off == 0) || (at != 0 && on_or_off != 0)) + return old_value; + + /* We have something to do. Do it. */ + nval = (char *)xmalloc (strlen (rl_completer_word_break_characters) + 1 + on_or_off); + + if (on_or_off == 0) + { + /* Turn it off -- just remove `@' from word break chars. We want + to remove all occurrences of `@' from the char list, so we loop + rather than just copy the rest of the list over AT. */ + for (nv = nval, at = rl_completer_word_break_characters; *at; ) + if (*at != '@') + *nv++ = *at++; + else + at++; + *nv = '\0'; + } + else + { + nval[0] = '@'; + strcpy (nval + 1, rl_completer_word_break_characters); + } + + free ((void *)rl_completer_word_break_characters); + rl_completer_word_break_characters = nval; + } + + return (old_value); +} + +/* Called once from parse.y if we are going to use readline. */ +void +initialize_readline () +{ + rl_command_func_t *func; + char kseq[2]; + + if (bash_readline_initialized) + return; + + rl_terminal_name = get_string_value ("TERM"); + rl_instream = stdin; + rl_outstream = stderr; + + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = "Bash"; + + /* Add bindable names before calling rl_initialize so they may be + referenced in the various inputrc files. */ + rl_add_defun ("shell-expand-line", shell_expand_line, -1); +#ifdef BANG_HISTORY + rl_add_defun ("history-expand-line", history_expand_line, -1); + rl_add_defun ("magic-space", tcsh_magic_space, -1); +#endif + + rl_add_defun ("shell-forward-word", bash_forward_shellword, -1); + rl_add_defun ("shell-backward-word", bash_backward_shellword, -1); + rl_add_defun ("shell-kill-word", bash_kill_shellword, -1); + rl_add_defun ("shell-backward-kill-word", bash_backward_kill_shellword, -1); + rl_add_defun ("shell-transpose-words", bash_transpose_shellwords, -1); + + rl_add_defun ("spell-correct-word", bash_spell_correct_shellword, -1); + rl_bind_key_if_unbound_in_map ('s', bash_spell_correct_shellword, emacs_ctlx_keymap); + +#ifdef ALIAS + rl_add_defun ("alias-expand-line", alias_expand_line, -1); +# ifdef BANG_HISTORY + rl_add_defun ("history-and-alias-expand-line", history_and_alias_expand_line, -1); +# endif +#endif + + /* Backwards compatibility. */ + rl_add_defun ("insert-last-argument", rl_yank_last_arg, -1); + + rl_add_defun ("display-shell-version", display_shell_version, -1); + rl_add_defun ("edit-and-execute-command", emacs_edit_and_execute_command, -1); +#if defined (VI_MODE) + rl_add_defun ("vi-edit-and-execute-command", vi_edit_and_execute_command, -1); +#endif + +#if defined (BRACE_COMPLETION) + rl_add_defun ("complete-into-braces", bash_brace_completion, -1); +#endif + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + rl_add_defun ("complete-filename", bash_complete_filename, -1); + rl_add_defun ("possible-filename-completions", bash_possible_filename_completions, -1); + rl_add_defun ("complete-username", bash_complete_username, -1); + rl_add_defun ("possible-username-completions", bash_possible_username_completions, -1); + rl_add_defun ("complete-hostname", bash_complete_hostname, -1); + rl_add_defun ("possible-hostname-completions", bash_possible_hostname_completions, -1); + rl_add_defun ("complete-variable", bash_complete_variable, -1); + rl_add_defun ("possible-variable-completions", bash_possible_variable_completions, -1); + rl_add_defun ("complete-command", bash_complete_command, -1); + rl_add_defun ("possible-command-completions", bash_possible_command_completions, -1); + rl_add_defun ("glob-complete-word", bash_glob_complete_word, -1); + rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1); + rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1); +#endif + + rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1); + rl_add_defun ("dabbrev-expand", bash_dabbrev_expand, -1); + + /* Bind defaults before binding our custom shell keybindings. */ + if (RL_ISSTATE(RL_STATE_INITIALIZED) == 0) + rl_initialize (); + + /* Bind up our special shell functions. */ + rl_bind_key_if_unbound_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap); + +#ifdef BANG_HISTORY + rl_bind_key_if_unbound_in_map ('^', history_expand_line, emacs_meta_keymap); +#endif + + rl_bind_key_if_unbound_in_map (CTRL ('V'), display_shell_version, emacs_ctlx_keymap); + + /* In Bash, the user can switch editing modes with "set -o [vi emacs]", + so it is not necessary to allow C-M-j for context switching. Turn + off this occasionally confusing behaviour. */ + kseq[0] = CTRL('J'); + kseq[1] = '\0'; + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == rl_vi_editing_mode) + rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap); + kseq[0] = CTRL('M'); + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == rl_vi_editing_mode) + rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap); +#if defined (VI_MODE) + kseq[0] = CTRL('E'); + func = rl_function_of_keyseq (kseq, vi_movement_keymap, (int *)NULL); + if (func == rl_emacs_editing_mode) + rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap); +#endif + +#if defined (BRACE_COMPLETION) + rl_bind_key_if_unbound_in_map ('{', bash_brace_completion, emacs_meta_keymap); /*}*/ +#endif /* BRACE_COMPLETION */ + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + rl_bind_key_if_unbound_in_map ('/', bash_complete_filename, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('/', bash_possible_filename_completions, emacs_ctlx_keymap); + + /* Have to jump through hoops here because there is a default binding for + M-~ (rl_tilde_expand) */ + kseq[0] = '~'; + kseq[1] = '\0'; + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == 0 || func == rl_tilde_expand) + rl_bind_keyseq_in_map (kseq, bash_complete_username, emacs_meta_keymap); + + rl_bind_key_if_unbound_in_map ('~', bash_possible_username_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('@', bash_complete_hostname, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('@', bash_possible_hostname_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('$', bash_complete_variable, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('$', bash_possible_variable_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('!', bash_complete_command, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('!', bash_possible_command_completions, emacs_ctlx_keymap); + + rl_bind_key_if_unbound_in_map ('g', bash_glob_complete_word, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap); + rl_bind_key_if_unbound_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap); + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + + kseq[0] = TAB; + kseq[1] = '\0'; + func = rl_function_of_keyseq (kseq, emacs_meta_keymap, (int *)NULL); + if (func == 0 || func == rl_tab_insert) + rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap); + + /* Tell the completer that we want a crack first. */ + rl_attempted_completion_function = attempt_shell_completion; + + /* Tell the completer that we might want to follow symbolic links or + do other expansion on directory names. */ + set_directory_hook (); + + rl_filename_rewrite_hook = bash_filename_rewrite_hook; + + rl_filename_stat_hook = bash_filename_stat_hook; + + /* Tell the filename completer we want a chance to ignore some names. */ + rl_ignore_some_completions_function = filename_completion_ignore; + + /* Bind C-xC-e to invoke emacs and run result as commands. */ + rl_bind_key_if_unbound_in_map (CTRL ('E'), emacs_edit_and_execute_command, emacs_ctlx_keymap); +#if defined (VI_MODE) + rl_bind_key_if_unbound_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap); +# if defined (ALIAS) + rl_bind_key_if_unbound_in_map ('@', posix_edit_macros, vi_movement_keymap); +# endif + + rl_bind_key_in_map ('\\', bash_vi_complete, vi_movement_keymap); + rl_bind_key_in_map ('*', bash_vi_complete, vi_movement_keymap); + rl_bind_key_in_map ('=', bash_vi_complete, vi_movement_keymap); +#endif + + rl_completer_quote_characters = "'\""; + + /* This sets rl_completer_word_break_characters and rl_special_prefixes + to the appropriate values, depending on whether or not hostname + completion is enabled. */ + enable_hostname_completion (perform_hostname_completion); + + /* characters that need to be quoted when appearing in filenames. */ + rl_filename_quote_characters = default_filename_quote_characters; + set_filename_bstab (rl_filename_quote_characters); + + rl_filename_quoting_function = bash_quote_filename; + rl_filename_dequoting_function = bash_dequote_filename; + rl_char_is_quoted_p = char_is_quoted; + + /* Add some default bindings for the "shellwords" functions, roughly + parallelling the default word bindings in emacs mode. */ + rl_bind_key_if_unbound_in_map (CTRL('B'), bash_backward_shellword, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map (CTRL('D'), bash_kill_shellword, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map (CTRL('F'), bash_forward_shellword, emacs_meta_keymap); + rl_bind_key_if_unbound_in_map (CTRL('T'), bash_transpose_shellwords, emacs_meta_keymap); + +#if 0 + /* This is superfluous and makes it impossible to use tab completion in + vi mode even when explicitly binding it in ~/.inputrc. sv_strict_posix() + should already have called posix_readline_initialize() when + posixly_correct was set. */ + if (posixly_correct) + posix_readline_initialize (1); +#endif + + bash_readline_initialized = 1; +} + +void +bashline_reinitialize () +{ + bash_readline_initialized = 0; +} + +void +bashline_set_event_hook () +{ + rl_signal_event_hook = bash_event_hook; +} + +void +bashline_reset_event_hook () +{ + rl_signal_event_hook = 0; +} + +/* On Sun systems at least, rl_attempted_completion_function can end up + getting set to NULL, and rl_completion_entry_function set to do command + word completion if Bash is interrupted while trying to complete a command + word. This just resets all the completion functions to the right thing. + It's called from throw_to_top_level(). */ +void +bashline_reset () +{ + tilde_initialize (); + rl_attempted_completion_function = attempt_shell_completion; + rl_completion_entry_function = NULL; + rl_ignore_some_completions_function = filename_completion_ignore; + + complete_fullquote = 1; + rl_filename_quote_characters = default_filename_quote_characters; + set_filename_bstab (rl_filename_quote_characters); + + set_directory_hook (); + rl_filename_stat_hook = bash_filename_stat_hook; + + bashline_reset_event_hook (); + + rl_sort_completion_matches = 1; +} + +/* Contains the line to push into readline. */ +static char *push_to_readline = (char *)NULL; + +/* Push the contents of push_to_readline into the + readline buffer. */ +static int +bash_push_line () +{ + if (push_to_readline) + { + rl_insert_text (push_to_readline); + free (push_to_readline); + push_to_readline = (char *)NULL; + rl_startup_hook = old_rl_startup_hook; + } + return 0; +} + +/* Call this to set the initial text for the next line to read + from readline. */ +int +bash_re_edit (line) + char *line; +{ + FREE (push_to_readline); + + push_to_readline = savestring (line); + old_rl_startup_hook = rl_startup_hook; + rl_startup_hook = bash_push_line; + + return (0); +} + +static int +display_shell_version (count, c) + int count, c; +{ + rl_crlf (); + show_shell_version (0); + putc ('\r', rl_outstream); + fflush (rl_outstream); + rl_on_new_line (); + rl_redisplay (); + return 0; +} + +/* **************************************************************** */ +/* */ +/* Readline Stuff */ +/* */ +/* **************************************************************** */ + +/* If the user requests hostname completion, then simply build a list + of hosts, and complete from that forever more, or at least until + HOSTFILE is unset. */ + +/* THIS SHOULD BE A STRINGLIST. */ +/* The kept list of hostnames. */ +static char **hostname_list = (char **)NULL; + +/* The physical size of the above list. */ +static int hostname_list_size; + +/* The number of hostnames in the above list. */ +static int hostname_list_length; + +/* Whether or not HOSTNAME_LIST has been initialized. */ +int hostname_list_initialized = 0; + +/* Initialize the hostname completion table. */ +static void +initialize_hostname_list () +{ + char *temp; + + temp = get_string_value ("HOSTFILE"); + if (temp == 0) + temp = get_string_value ("hostname_completion_file"); + if (temp == 0) + temp = DEFAULT_HOSTS_FILE; + + snarf_hosts_from_file (temp); + + if (hostname_list) + hostname_list_initialized++; +} + +/* Add NAME to the list of hosts. */ +static void +add_host_name (name) + char *name; +{ + if (hostname_list_length + 2 > hostname_list_size) + { + hostname_list_size = (hostname_list_size + 32) - (hostname_list_size % 32); + hostname_list = strvec_resize (hostname_list, hostname_list_size); + } + + hostname_list[hostname_list_length++] = savestring (name); + hostname_list[hostname_list_length] = (char *)NULL; +} + +#define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c)) + +static void +snarf_hosts_from_file (filename) + char *filename; +{ + FILE *file; + char *temp, buffer[256], name[256]; + register int i, start; + + file = fopen (filename, "r"); + if (file == 0) + return; + + while (temp = fgets (buffer, 255, file)) + { + /* Skip to first character. */ + for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++) + ; + + /* If comment or blank line, ignore. */ + if (buffer[i] == '\0' || buffer[i] == '#') + continue; + + /* If `preprocessor' directive, do the include. */ + if (strncmp (buffer + i, "$include ", 9) == 0) + { + char *incfile, *t; + + /* Find start of filename. */ + for (incfile = buffer + i + 9; *incfile && whitespace (*incfile); incfile++) + ; + + /* Find end of filename. */ + for (t = incfile; *t && cr_whitespace (*t) == 0; t++) + ; + + *t = '\0'; + + snarf_hosts_from_file (incfile); + continue; + } + + /* Skip internet address if present. */ + if (DIGIT (buffer[i])) + for (; buffer[i] && cr_whitespace (buffer[i]) == 0; i++); + + /* Gobble up names. Each name is separated with whitespace. */ + while (buffer[i]) + { + for (; cr_whitespace (buffer[i]); i++) + ; + if (buffer[i] == '\0' || buffer[i] == '#') + break; + + /* Isolate the current word. */ + for (start = i; buffer[i] && cr_whitespace (buffer[i]) == 0; i++) + ; + if (i == start) + continue; + strncpy (name, buffer + start, i - start); + name[i - start] = '\0'; + add_host_name (name); + } + } + fclose (file); +} + +/* Return the hostname list. */ +char ** +get_hostname_list () +{ + if (hostname_list_initialized == 0) + initialize_hostname_list (); + return (hostname_list); +} + +void +clear_hostname_list () +{ + register int i; + + if (hostname_list_initialized == 0) + return; + for (i = 0; i < hostname_list_length; i++) + free (hostname_list[i]); + hostname_list_length = hostname_list_initialized = 0; +} + +/* Return a NULL terminated list of hostnames which begin with TEXT. + Initialize the hostname list the first time if necessary. + The array is malloc ()'ed, but not the individual strings. */ +static char ** +hostnames_matching (text) + char *text; +{ + register int i, len, nmatch, rsize; + char **result; + + if (hostname_list_initialized == 0) + initialize_hostname_list (); + + if (hostname_list_initialized == 0) + return ((char **)NULL); + + /* Special case. If TEXT consists of nothing, then the whole list is + what is desired. */ + if (*text == '\0') + { + result = strvec_create (1 + hostname_list_length); + for (i = 0; i < hostname_list_length; i++) + result[i] = hostname_list[i]; + result[i] = (char *)NULL; + return (result); + } + + /* Scan until found, or failure. */ + len = strlen (text); + result = (char **)NULL; + for (i = nmatch = rsize = 0; i < hostname_list_length; i++) + { + if (STREQN (text, hostname_list[i], len) == 0) + continue; + + /* OK, it matches. Add it to the list. */ + if (nmatch >= (rsize - 1)) + { + rsize = (rsize + 16) - (rsize % 16); + result = strvec_resize (result, rsize); + } + + result[nmatch++] = hostname_list[i]; + } + if (nmatch) + result[nmatch] = (char *)NULL; + return (result); +} + +/* This vi mode command causes VI_EDIT_COMMAND to be run on the current + command being entered (if no explicit argument is given), otherwise on + a command from the history file. */ + +#define VI_EDIT_COMMAND "fc -e \"${VISUAL:-${EDITOR:-vi}}\"" +#define EMACS_EDIT_COMMAND "fc -e \"${VISUAL:-${EDITOR:-emacs}}\"" +#define POSIX_VI_EDIT_COMMAND "fc -e vi" + +static int +edit_and_execute_command (count, c, editing_mode, edit_command) + int count, c, editing_mode; + char *edit_command; +{ + char *command, *metaval; + int r, rrs, metaflag; + sh_parser_state_t ps; + + rrs = rl_readline_state; + saved_command_line_count = current_command_line_count; + + /* Accept the current line. */ + rl_newline (1, c); + + if (rl_explicit_arg) + { + command = (char *)xmalloc (strlen (edit_command) + 8); + sprintf (command, "%s %d", edit_command, count); + } + else + { + /* Take the command we were just editing, add it to the history file, + then call fc to operate on it. We have to add a dummy command to + the end of the history because fc ignores the last command (assumes + it's supposed to deal with the command before the `fc'). */ + /* This breaks down when using command-oriented history and are not + finished with the command, so we should not ignore the last command */ + using_history (); + current_command_line_count++; /* for rl_newline above */ + bash_add_history (rl_line_buffer); + current_command_line_count = 0; /* for dummy history entry */ + bash_add_history (""); + history_lines_this_session++; + using_history (); + command = savestring (edit_command); + } + + metaval = rl_variable_value ("input-meta"); + metaflag = RL_BOOLEAN_VARIABLE_VALUE (metaval); + + if (rl_deprep_term_function) + (*rl_deprep_term_function) (); + rl_clear_signals (); + save_parser_state (&ps); + r = parse_and_execute (command, (editing_mode == VI_EDITING_MODE) ? "v" : "C-xC-e", SEVAL_NOHIST); + restore_parser_state (&ps); + + /* if some kind of reset_parser was called, undo it. */ + reset_readahead_token (); + + if (rl_prep_term_function) + (*rl_prep_term_function) (metaflag); + rl_set_signals (); + + current_command_line_count = saved_command_line_count; + + /* Now erase the contents of the current line and undo the effects of the + rl_accept_line() above. We don't even want to make the text we just + executed available for undoing. */ + rl_line_buffer[0] = '\0'; /* XXX */ + rl_point = rl_end = 0; + rl_done = 0; + rl_readline_state = rrs; + +#if defined (VI_MODE) + if (editing_mode == VI_EDITING_MODE) + rl_vi_insertion_mode (1, c); +#endif + + rl_forced_update_display (); + + return r; +} + +#if defined (VI_MODE) +static int +vi_edit_and_execute_command (count, c) + int count, c; +{ + if (posixly_correct) + return (edit_and_execute_command (count, c, VI_EDITING_MODE, POSIX_VI_EDIT_COMMAND)); + else + return (edit_and_execute_command (count, c, VI_EDITING_MODE, VI_EDIT_COMMAND)); +} +#endif /* VI_MODE */ + +static int +emacs_edit_and_execute_command (count, c) + int count, c; +{ + return (edit_and_execute_command (count, c, EMACS_EDITING_MODE, EMACS_EDIT_COMMAND)); +} + +#if defined (ALIAS) +static int +posix_edit_macros (count, key) + int count, key; +{ + int c; + char alias_name[3], *alias_value, *macro; + + c = rl_read_key (); + if (c <= 0) + return 0; + alias_name[0] = '_'; + alias_name[1] = c; + alias_name[2] = '\0'; + + alias_value = get_alias_value (alias_name); + if (alias_value && *alias_value) + { + macro = savestring (alias_value); + rl_push_macro_input (macro); + } + return 0; +} +#endif + +/* Bindable commands that move `shell-words': that is, sequences of + non-unquoted-metacharacters. */ + +#define WORDDELIM(c) (shellmeta(c) || shellblank(c)) + +static int +bash_forward_shellword (count, key) + int count, key; +{ + size_t slen; + int c, p; + DECLARE_MBSTATE; + + if (count < 0) + return (bash_backward_shellword (-count, key)); + + /* The tricky part of this is deciding whether or not the first character + we're on is an unquoted metacharacter. Not completely handled yet. */ + /* XXX - need to test this stuff with backslash-escaped shell + metacharacters and unclosed single- and double-quoted strings. */ + + p = rl_point; + slen = rl_end; + + while (count) + { + if (p == rl_end) + { + rl_point = rl_end; + return 0; + } + + /* Are we in a quoted string? If we are, move to the end of the quoted + string and continue the outer loop. We only want quoted strings, not + backslash-escaped characters, but char_is_quoted doesn't + differentiate. */ + if (char_is_quoted (rl_line_buffer, p) && p > 0 && rl_line_buffer[p-1] != '\\') + { + do + ADVANCE_CHAR (rl_line_buffer, slen, p); + while (p < rl_end && char_is_quoted (rl_line_buffer, p)); + count--; + continue; + } + + /* Rest of code assumes we are not in a quoted string. */ + /* Move forward until we hit a non-metacharacter. */ + while (p < rl_end && (c = rl_line_buffer[p]) && WORDDELIM (c)) + { + switch (c) + { + default: + ADVANCE_CHAR (rl_line_buffer, slen, p); + continue; /* straight back to loop, don't increment p */ + case '\\': + if (p < rl_end && rl_line_buffer[p]) + ADVANCE_CHAR (rl_line_buffer, slen, p); + break; + case '\'': + p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP); + break; + case '"': + p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP); + break; + } + + if (p < rl_end) + p++; + } + + if (rl_line_buffer[p] == 0 || p == rl_end) + { + rl_point = rl_end; + rl_ding (); + return 0; + } + + /* Now move forward until we hit a non-quoted metacharacter or EOL */ + while (p < rl_end && (c = rl_line_buffer[p]) && WORDDELIM (c) == 0) + { + switch (c) + { + default: + ADVANCE_CHAR (rl_line_buffer, slen, p); + continue; /* straight back to loop, don't increment p */ + case '\\': + if (p < rl_end && rl_line_buffer[p]) + ADVANCE_CHAR (rl_line_buffer, slen, p); + break; + case '\'': + p = skip_to_delim (rl_line_buffer, ++p, "'", SD_NOJMP); + break; + case '"': + p = skip_to_delim (rl_line_buffer, ++p, "\"", SD_NOJMP); + break; + } + + if (p < rl_end) + p++; + } + + if (p == rl_end || rl_line_buffer[p] == 0) + { + rl_point = rl_end; + return (0); + } + + count--; + } + + rl_point = p; + return (0); +} + +static int +bash_backward_shellword (count, key) + int count, key; +{ + size_t slen; + int c, p, prev_p; + DECLARE_MBSTATE; + + if (count < 0) + return (bash_forward_shellword (-count, key)); + + p = rl_point; + slen = rl_end; + + while (count) + { + if (p == 0) + { + rl_point = 0; + return 0; + } + + /* Move backward until we hit a non-metacharacter. We want to deal + with the characters before point, so we move off a word if we're + at its first character. */ + BACKUP_CHAR (rl_line_buffer, slen, p); + while (p > 0) + { + c = rl_line_buffer[p]; + if (WORDDELIM (c) == 0 || char_is_quoted (rl_line_buffer, p)) + break; + BACKUP_CHAR (rl_line_buffer, slen, p); + } + + if (p == 0) + { + rl_point = 0; + return 0; + } + + /* Now move backward until we hit a metacharacter or BOL. Leave point + at the start of the shellword or at BOL. */ + prev_p = p; + while (p > 0) + { + c = rl_line_buffer[p]; + if (WORDDELIM (c) && char_is_quoted (rl_line_buffer, p) == 0) + { + p = prev_p; + break; + } + prev_p = p; + BACKUP_CHAR (rl_line_buffer, slen, p); + } + + count--; + } + + rl_point = p; + return 0; +} + +static int +bash_kill_shellword (count, key) + int count, key; +{ + int p; + + if (count < 0) + return (bash_backward_kill_shellword (-count, key)); + + p = rl_point; + bash_forward_shellword (count, key); + + if (rl_point != p) + rl_kill_text (p, rl_point); + + rl_point = p; + if (rl_editing_mode == EMACS_EDITING_MODE) /* 1 == emacs_mode */ + rl_mark = rl_point; + + return 0; +} + +static int +bash_backward_kill_shellword (count, key) + int count, key; +{ + int p; + + if (count < 0) + return (bash_kill_shellword (-count, key)); + + p = rl_point; + bash_backward_shellword (count, key); + + if (rl_point != p) + rl_kill_text (p, rl_point); + + if (rl_editing_mode == EMACS_EDITING_MODE) /* 1 == emacs_mode */ + rl_mark = rl_point; + + return 0; +} + +static int +bash_transpose_shellwords (count, key) + int count, key; +{ + char *word1, *word2; + int w1_beg, w1_end, w2_beg, w2_end; + int orig_point = rl_point; + + if (count == 0) + return 0; + + /* Find the two shell words. */ + bash_forward_shellword (count, key); + w2_end = rl_point; + bash_backward_shellword (1, key); + w2_beg = rl_point; + bash_backward_shellword (count, key); + w1_beg = rl_point; + bash_forward_shellword (1, key); + w1_end = rl_point; + + /* check that there really are two words. */ + if ((w1_beg == w2_beg) || (w2_beg < w1_end)) + { + rl_ding (); + rl_point = orig_point; + return 1; + } + + /* Get the text of the words. */ + word1 = rl_copy_text (w1_beg, w1_end); + word2 = rl_copy_text (w2_beg, w2_end); + + /* We are about to do many insertions and deletions. Remember them + as one operation. */ + rl_begin_undo_group (); + + /* Do the stuff at word2 first, so that we don't have to worry + about word1 moving. */ + rl_point = w2_beg; + rl_delete_text (w2_beg, w2_end); + rl_insert_text (word1); + + rl_point = w1_beg; + rl_delete_text (w1_beg, w1_end); + rl_insert_text (word2); + + /* This is exactly correct since the text before this point has not + changed in length. */ + rl_point = w2_end; + + /* I think that does it. */ + rl_end_undo_group (); + xfree (word1); + xfree (word2); + + return 0; +} + +/* Directory name spelling correction on the current word (not shellword). + COUNT > 1 is not exactly correct yet. */ +static int +bash_spell_correct_shellword (count, key) + int count, key; +{ + int opoint, wbeg, wend; + char *text, *newdir; + + opoint = rl_point; + while (count) + { + bash_backward_shellword (1, key); + wbeg = rl_point; + bash_forward_shellword (1, key); + wend = rl_point; + + if (wbeg > wend) + break; + + text = rl_copy_text (wbeg, wend); + + newdir = dirspell (text); + if (newdir) + { + rl_begin_undo_group (); + rl_delete_text (wbeg, wend); + rl_point = wbeg; + if (*newdir) + rl_insert_text (newdir); + rl_mark = wbeg; + rl_end_undo_group (); + } + + free (text); + free (newdir); + + if (rl_point >= rl_end) + break; + + count--; + + if (count) + bash_forward_shellword (1, key); /* XXX */ + } + + return 0; +} + +/* **************************************************************** */ +/* */ +/* How To Do Shell Completion */ +/* */ +/* **************************************************************** */ + +#define COMMAND_SEPARATORS ";|&{(`" +/* )} */ +#define COMMAND_SEPARATORS_PLUS_WS ";|&{(` \t" +/* )} */ + +/* check for redirections and other character combinations that are not + command separators */ +static int +check_redir (ti) + int ti; +{ + register int this_char, prev_char; + + /* Handle the two character tokens `>&', `<&', and `>|'. + We are not in a command position after one of these. */ + this_char = rl_line_buffer[ti]; + prev_char = (ti > 0) ? rl_line_buffer[ti - 1] : 0; + + if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) || + (this_char == '|' && prev_char == '>')) + return (1); + else if (this_char == '{' && prev_char == '$') /*}*/ + return (1); +#if 0 /* Not yet */ + else if (this_char == '(' && prev_char == '$') /*)*/ + return (1); + else if (this_char == '(' && prev_char == '<') /*)*/ + return (1); +#if defined (EXTENDED_GLOB) + else if (extended_glob && this_char == '(' && prev_char == '!') /*)*/ + return (1); +#endif +#endif + else if (char_is_quoted (rl_line_buffer, ti)) + return (1); + return (0); +} + +#if defined (PROGRAMMABLE_COMPLETION) +/* + * XXX - because of the <= start test, and setting os = s+1, this can + * potentially return os > start. This is probably not what we want to + * happen, but fix later after 2.05a-release. + */ +static int +find_cmd_start (start) + int start; +{ + register int s, os, ns; + + os = 0; + /* Flags == SD_NOJMP only because we want to skip over command substitutions + in assignment statements. Have to test whether this affects `standalone' + command substitutions as individual words. */ + while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS, SD_NOJMP|SD_COMPLETE/*|SD_NOSKIPCMD*/)) <= start) && + rl_line_buffer[s]) + { + /* Handle >| token crudely; treat as > not | */ + if (s > 0 && rl_line_buffer[s] == '|' && rl_line_buffer[s-1] == '>') + { + ns = skip_to_delim (rl_line_buffer, s+1, COMMAND_SEPARATORS, SD_NOJMP|SD_COMPLETE/*|SD_NOSKIPCMD*/); + if (ns > start || rl_line_buffer[ns] == 0) + return os; + os = ns+1; + continue; + } + /* The only reserved word in COMMAND_SEPARATORS is `{', so handle that + specially, making sure it's in a spot acceptable for reserved words */ + if (s >= os && rl_line_buffer[s] == '{') + { + int pc, nc; /* index of previous non-whitespace, next char */ + for (pc = (s > os) ? s - 1 : os; pc > os && whitespace(rl_line_buffer[pc]); pc--) + ; + nc = rl_line_buffer[s+1]; + /* must be preceded by a command separator or be the first non- + whitespace character since the last command separator, and + followed by a shell break character (not another `{') to be a reserved word. */ + if ((pc > os && (rl_line_buffer[s-1] == '{' || strchr (COMMAND_SEPARATORS, rl_line_buffer[pc]) == 0)) || + (shellbreak(nc) == 0)) /* }} */ + { + /* Not a reserved word, look for another delim */ + ns = skip_to_delim (rl_line_buffer, s+1, COMMAND_SEPARATORS, SD_NOJMP|SD_COMPLETE/*|SD_NOSKIPCMD*/); + if (ns > start || rl_line_buffer[ns] == 0) + return os; + os = ns+1; + continue; + } + } + os = s+1; + } + return os; +} + +static int +find_cmd_end (end) + int end; +{ + register int e; + + e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS, SD_NOJMP|SD_COMPLETE); + return e; +} + +static char * +find_cmd_name (start, sp, ep) + int start; + int *sp, *ep; +{ + char *name; + register int s, e; + + for (s = start; whitespace (rl_line_buffer[s]); s++) + ; + + /* skip until a shell break character */ + e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n", SD_NOJMP|SD_COMPLETE); + + name = substring (rl_line_buffer, s, e); + + if (sp) + *sp = s; + if (ep) + *ep = e; + + return (name); +} + +static char * +prog_complete_return (text, matchnum) + const char *text; + int matchnum; +{ + static int ind; + + if (matchnum == 0) + ind = 0; + + if (prog_complete_matches == 0 || prog_complete_matches[ind] == 0) + return (char *)NULL; + return (prog_complete_matches[ind++]); +} + +#endif /* PROGRAMMABLE_COMPLETION */ + +/* Try and catch completion attempts that are syntax errors or otherwise + invalid. */ +static int +invalid_completion (text, ind) + const char *text; + int ind; +{ + int pind; + + /* If we don't catch these here, the next clause will */ + if (ind > 0 && rl_line_buffer[ind] == '(' && /*)*/ + member (rl_line_buffer[ind-1], "$<>")) + return 0; + + pind = ind - 1; + while (pind > 0 && whitespace (rl_line_buffer[pind])) + pind--; + /* If we have only whitespace preceding a paren, it's valid */ + if (ind >= 0 && pind <= 0 && rl_line_buffer[ind] == '(') /*)*/ + return 0; + /* Flag the invalid completions, which are mostly syntax errors */ + if (ind > 0 && rl_line_buffer[ind] == '(' && /*)*/ + member (rl_line_buffer[pind], COMMAND_SEPARATORS) == 0) + return 1; + + return 0; +} + +/* Do some completion on TEXT. The indices of TEXT in RL_LINE_BUFFER are + at START and END. Return an array of matches, or NULL if none. */ +static char ** +attempt_shell_completion (text, start, end) + const char *text; + int start, end; +{ + int in_command_position, ti, qc, dflags; + char **matches, *command_separator_chars; +#if defined (PROGRAMMABLE_COMPLETION) + int have_progcomps, was_assignment; + COMPSPEC *iw_compspec; +#endif + + command_separator_chars = COMMAND_SEPARATORS; + matches = (char **)NULL; + rl_ignore_some_completions_function = filename_completion_ignore; + + complete_fullquote = 1; /* full filename quoting by default */ + rl_filename_quote_characters = default_filename_quote_characters; + set_filename_bstab (rl_filename_quote_characters); + set_directory_hook (); + rl_filename_stat_hook = bash_filename_stat_hook; + + rl_sort_completion_matches = 1; /* sort by default */ + + /* Determine if this could be a command word. It is if it appears at + the start of the line (ignoring preceding whitespace), or if it + appears after a character that separates commands. It cannot be a + command word if we aren't at the top-level prompt. */ + ti = start - 1; + qc = -1; + + while ((ti > -1) && (whitespace (rl_line_buffer[ti]))) + ti--; + +#if 1 + /* If this is an open quote, maybe we're trying to complete a quoted + command name. */ + if (ti >= 0 && (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\'')) + { + qc = rl_line_buffer[ti]; + ti--; + while (ti > -1 && (whitespace (rl_line_buffer[ti]))) + ti--; + } +#endif + + in_command_position = 0; + if (ti < 0) + { + /* Only do command completion at the start of a line when we + are prompting at the top level. */ + if (current_prompt_string == ps1_prompt) + in_command_position++; + else if (parser_in_command_position ()) + in_command_position++; + } + else if (member (rl_line_buffer[ti], command_separator_chars)) + { + in_command_position++; + + if (check_redir (ti) == 1) + in_command_position = -1; /* sentinel that we're not the first word on the line */ + } + else + { + /* This still could be in command position. It is possible + that all of the previous words on the line are variable + assignments. */ + } + + if (in_command_position > 0 && invalid_completion (text, ti)) + { + rl_attempted_completion_over = 1; + return ((char **)NULL); + } + + /* Check that we haven't incorrectly flagged a closed command substitution + as indicating we're in a command position. */ + if (in_command_position > 0 && ti >= 0 && rl_line_buffer[ti] == '`' && + *text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0) + in_command_position = -1; /* not following a command separator */ + + /* Special handling for command substitution. If *TEXT is a backquote, + it can be the start or end of an old-style command substitution, or + unmatched. If it's unmatched, both calls to unclosed_pair will + succeed. Don't bother if readline found a single quote and we are + completing on the substring. */ + if (*text == '`' && rl_completion_quote_character != '\'' && + (in_command_position > 0 || (unclosed_pair (rl_line_buffer, start, "`") && + unclosed_pair (rl_line_buffer, end, "`")))) + matches = rl_completion_matches (text, command_subst_completion_function); + +#if defined (PROGRAMMABLE_COMPLETION) + /* Attempt programmable completion. */ + have_progcomps = prog_completion_enabled && (progcomp_size () > 0); + iw_compspec = progcomp_search (INITIALWORD); + if (matches == 0 && + (in_command_position == 0 || text[0] == '\0' || (in_command_position > 0 && iw_compspec)) && + current_prompt_string == ps1_prompt) + { + int s, e, s1, e1, os, foundcs; + char *n; + + /* XXX - don't free the members */ + if (prog_complete_matches) + free (prog_complete_matches); + prog_complete_matches = (char **)NULL; + + os = start; + n = 0; + was_assignment = 0; + s = find_cmd_start (os); + e = find_cmd_end (end); + do + { + /* Don't read past the end of rl_line_buffer */ + if (s > rl_end) + { + s1 = s = e1; + break; + } + /* Or past point if point is within an assignment statement */ + else if (was_assignment && s > rl_point) + { + s1 = s = e1; + break; + } + /* Skip over assignment statements preceding a command name. If we + don't find a command name at all, we can perform command name + completion. If we find a partial command name, we should perform + command name completion on it. */ + FREE (n); + n = find_cmd_name (s, &s1, &e1); + s = e1 + 1; + } + while (was_assignment = assignment (n, 0)); + s = s1; /* reset to index where name begins */ + + /* s == index of where command name begins (reset above) + e == end of current command, may be end of line + s1 = index of where command name begins + e1 == index of where command name ends + start == index of where word to be completed begins + end == index of where word to be completed ends + if (s == start) we are doing command word completion for sure + if (e1 == end) we are at the end of the command name and completing it */ + if (start == 0 && end == 0 && e != 0 && text[0] == '\0') /* beginning of non-empty line */ + foundcs = 0; + else if (start == end && start == s1 && e != 0 && e1 > end) /* beginning of command name, leading whitespace */ + foundcs = 0; + else if (e == 0 && e == s && text[0] == '\0' && have_progcomps) /* beginning of empty line */ + prog_complete_matches = programmable_completions (EMPTYCMD, text, s, e, &foundcs); + else if (start == end && text[0] == '\0' && s1 > start && whitespace (rl_line_buffer[start])) + foundcs = 0; /* whitespace before command name */ + else if (e > s && was_assignment == 0 && e1 == end && rl_line_buffer[e] == 0 && whitespace (rl_line_buffer[e-1]) == 0) + { + /* not assignment statement, but still want to perform command + completion if we are composing command word. */ + foundcs = 0; + in_command_position = s == start && STREQ (n, text); /* XXX */ + } + else if (e > s && was_assignment == 0 && have_progcomps) + { + prog_complete_matches = programmable_completions (n, text, s, e, &foundcs); + /* command completion if programmable completion fails */ + /* If we have a completion for the initial word, we can prefer that */ + in_command_position = s == start && (iw_compspec || STREQ (n, text)); /* XXX */ + if (iw_compspec && in_command_position) + foundcs = 0; + } + /* empty command name following command separator */ + else if (s >= e && n[0] == '\0' && text[0] == '\0' && start > 0 && + was_assignment == 0 && member (rl_line_buffer[start-1], COMMAND_SEPARATORS)) + { + foundcs = 0; + in_command_position = 1; + } + else if (s >= e && n[0] == '\0' && text[0] == '\0' && start > 0) + { + foundcs = 0; /* empty command name following optional assignments */ + in_command_position += was_assignment; + } + else if (s == start && e == end && STREQ (n, text) && start > 0) + { + foundcs = 0; /* partial command name following assignments */ + in_command_position = 1; + } + else + foundcs = 0; + + /* If we have defined a compspec for the initial (command) word, call + it and process the results like any other programmable completion. */ + if (in_command_position && have_progcomps && foundcs == 0 && iw_compspec) + prog_complete_matches = programmable_completions (INITIALWORD, text, s, e, &foundcs); + + FREE (n); + /* XXX - if we found a COMPSPEC for the command, just return whatever + the programmable completion code returns, and disable the default + filename completion that readline will do unless the COPT_DEFAULT + option has been set with the `-o default' option to complete or + compopt. */ + if (foundcs) + { + pcomp_set_readline_variables (foundcs, 1); + /* Turn what the programmable completion code returns into what + readline wants. I should have made compute_lcd_of_matches + external... */ + matches = rl_completion_matches (text, prog_complete_return); + if ((foundcs & COPT_DEFAULT) == 0) + rl_attempted_completion_over = 1; /* no default */ + if (matches || ((foundcs & COPT_BASHDEFAULT) == 0)) + return (matches); + } + } +#endif + + if (matches == 0) + { + dflags = 0; + if (in_command_position > 0) + dflags |= DEFCOMP_CMDPOS; + matches = bash_default_completion (text, start, end, qc, dflags); + } + + return matches; +} + +char ** +bash_default_completion (text, start, end, qc, compflags) + const char *text; + int start, end, qc, compflags; +{ + char **matches, *t; + + matches = (char **)NULL; + + /* New posix-style command substitution or variable name? */ + if (*text == '$') + { + if (qc != '\'' && text[1] == '(') /* ) */ + matches = rl_completion_matches (text, command_subst_completion_function); + else + { + matches = rl_completion_matches (text, variable_completion_function); + /* If a single match, see if it expands to a directory name and append + a slash if it does. This requires us to expand the variable name, + so we don't want to display errors if the variable is unset. This + can happen with dynamic variables whose value has never been + requested. */ + if (matches && matches[0] && matches[1] == 0) + { + t = savestring (matches[0]); + bash_filename_stat_hook (&t); + /* doesn't use test_for_directory because that performs tilde + expansion */ + if (file_isdir (t)) + rl_completion_append_character = '/'; + free (t); + } + } + } + + /* If the word starts in `~', and there is no slash in the word, then + try completing this word as a username. */ + if (matches == 0 && *text == '~' && mbschr (text, '/') == 0) + matches = rl_completion_matches (text, rl_username_completion_function); + + /* Another one. Why not? If the word starts in '@', then look through + the world of known hostnames for completion first. */ + if (matches == 0 && perform_hostname_completion && *text == '@') + matches = rl_completion_matches (text, hostname_completion_function); + + /* And last, (but not least) if this word is in a command position, then + complete over possible command names, including aliases, functions, + and command names. */ + if (matches == 0 && (compflags & DEFCOMP_CMDPOS)) + { + /* If END == START and text[0] == 0, we are trying to complete an empty + command word. */ + if (no_empty_command_completion && end == start && text[0] == '\0') + { + matches = (char **)NULL; + rl_ignore_some_completions_function = bash_ignore_everything; + } + else + { +#define CMD_IS_DIR(x) (absolute_pathname(x) == 0 && absolute_program(x) == 0 && *(x) != '~' && test_for_directory (x)) + + dot_in_path = 0; + matches = rl_completion_matches (text, command_word_completion_function); + + /* If we are attempting command completion and nothing matches, we + do not want readline to perform filename completion for us. We + still want to be able to complete partial pathnames, so set the + completion ignore function to something which will remove + filenames and leave directories in the match list. */ + if (matches == (char **)NULL) + rl_ignore_some_completions_function = bash_ignore_filenames; + else if (matches[1] == 0 && CMD_IS_DIR(matches[0]) && dot_in_path == 0) + /* If we found a single match, without looking in the current + directory (because it's not in $PATH), but the found name is + also a command in the current directory, suppress appending any + terminating character, since it's ambiguous. */ + { + rl_completion_suppress_append = 1; + rl_filename_completion_desired = 0; + } + else if (matches[0] && matches[1] && STREQ (matches[0], matches[1]) && CMD_IS_DIR (matches[0])) + /* There are multiple instances of the same match (duplicate + completions haven't yet been removed). In this case, all of + the matches will be the same, and the duplicate removal code + will distill them all down to one. We turn on + rl_completion_suppress_append for the same reason as above. + Remember: we only care if there's eventually a single unique + completion. If there are multiple completions this won't + make a difference and the problem won't occur. */ + { + rl_completion_suppress_append = 1; + rl_filename_completion_desired = 0; + } + } + } + + /* This could be a globbing pattern, so try to expand it using pathname + expansion. */ + if (!matches && completion_glob_pattern ((char *)text)) + { + matches = rl_completion_matches (text, glob_complete_word); + /* A glob expression that matches more than one filename is problematic. + If we match more than one filename, punt. */ + if (matches && matches[1] && rl_completion_type == TAB) + { + strvec_dispose (matches); + matches = (char **)0; + } + else if (matches && matches[1] && rl_completion_type == '!') + { + rl_completion_suppress_append = 1; + rl_filename_completion_desired = 0; + } + } + + return (matches); +} + +static int +bash_command_name_stat_hook (name) + char **name; +{ + char *cname, *result; + + /* If it's not something we're going to look up in $PATH, just call the + normal filename stat hook. */ + if (absolute_program (*name)) + return (bash_filename_stat_hook (name)); + + cname = *name; + /* XXX - we could do something here with converting aliases, builtins, + and functions into something that came out as executable, but we don't. */ + result = search_for_command (cname, 0); + if (result) + { + *name = result; + return 1; + } + return 0; +} + +static int +executable_completion (filename, searching_path) + const char *filename; + int searching_path; +{ + char *f, c; + int r; + + /* This gets an unquoted filename, so we need to quote special characters + in the filename before the completion hook gets it. */ +#if 0 + f = savestring (filename); +#else + c = 0; + f = bash_quote_filename ((char *)filename, SINGLE_MATCH, &c); +#endif + bash_directory_completion_hook (&f); + + r = searching_path ? executable_file (f) : executable_or_directory (f); + free (f); + return r; +} + +/* This is the function to call when the word to complete is in a position + where a command word can be found. It grovels $PATH, looking for commands + that match. It also scans aliases, function names, and the shell_builtin + table. */ +char * +command_word_completion_function (hint_text, state) + const char *hint_text; + int state; +{ + static char *hint = (char *)NULL; + static char *path = (char *)NULL; + static char *val = (char *)NULL; + static char *filename_hint = (char *)NULL; + static char *fnhint = (char *)NULL; + static char *dequoted_hint = (char *)NULL; + static char *directory_part = (char *)NULL; + static char **glob_matches = (char **)NULL; + static int path_index, hint_len, istate, igncase; + static int mapping_over, local_index, searching_path, hint_is_dir; + static int old_glob_ignore_case, globpat; + static SHELL_VAR **varlist = (SHELL_VAR **)NULL; +#if defined (ALIAS) + static alias_t **alias_list = (alias_t **)NULL; +#endif /* ALIAS */ + char *temp, *cval; + + /* We have to map over the possibilities for command words. If we have + no state, then make one just for that purpose. */ + if (state == 0) + { + rl_filename_stat_hook = bash_command_name_stat_hook; + + if (dequoted_hint && dequoted_hint != hint) + free (dequoted_hint); + if (hint) + free (hint); + + mapping_over = searching_path = 0; + hint_is_dir = CMD_IS_DIR (hint_text); + val = (char *)NULL; + + temp = rl_variable_value ("completion-ignore-case"); + igncase = RL_BOOLEAN_VARIABLE_VALUE (temp); + + old_glob_ignore_case = glob_ignore_case; + + if (glob_matches) + { + free (glob_matches); + glob_matches = (char **)NULL; + } + + globpat = completion_glob_pattern ((char *)hint_text); + + /* If this is an absolute program name, do not check it against + aliases, reserved words, functions or builtins. We must check + whether or not it is unique, and, if so, whether that filename + is executable. */ + if (globpat || absolute_program (hint_text)) + { + /* Perform tilde expansion on what's passed, so we don't end up + passing filenames with tildes directly to stat(). The rest of + the shell doesn't do variable expansion on the word following + the tilde, so we don't do it here even if direxpand is set. */ + if (*hint_text == '~') + { + hint = bash_tilde_expand (hint_text, 0); + directory_part = savestring (hint_text); + temp = strchr (directory_part, '/'); + if (temp) + *temp = 0; + else + { + free (directory_part); + directory_part = (char *)NULL; + } + } + else if (dircomplete_expand) + { + hint = savestring (hint_text); + bash_directory_completion_hook (&hint); + } + else + hint = savestring (hint_text); + + dequoted_hint = hint; + /* If readline's completer found a quote character somewhere, but + didn't set the quote character, there must have been a quote + character embedded in the filename. It can't be at the start of + the filename, so we need to dequote the filename before we look + in the file system for it. */ + if (rl_completion_found_quote && rl_completion_quote_character == 0) + { + dequoted_hint = bash_dequote_filename (hint, 0); + free (hint); + hint = dequoted_hint; + } + hint_len = strlen (hint); + + if (filename_hint) + free (filename_hint); + + fnhint = filename_hint = savestring (hint); + + istate = 0; + + if (globpat) + { + mapping_over = 5; + goto globword; + } + else + { + if (dircomplete_expand && path_dot_or_dotdot (filename_hint)) + { + dircomplete_expand = 0; + set_directory_hook (); + dircomplete_expand = 1; + } + mapping_over = 4; + goto inner; + } + } + + dequoted_hint = hint = savestring (hint_text); + hint_len = strlen (hint); + + if (rl_completion_found_quote && rl_completion_quote_character == 0) + dequoted_hint = bash_dequote_filename (hint, 0); + + path = get_string_value ("PATH"); + path_index = dot_in_path = 0; + + /* Initialize the variables for each type of command word. */ + local_index = 0; + + if (varlist) + free (varlist); + + varlist = all_visible_functions (); + +#if defined (ALIAS) + if (alias_list) + free (alias_list); + + alias_list = all_aliases (); +#endif /* ALIAS */ + } + + /* mapping_over says what we are currently hacking. Note that every case + in this list must fall through when there are no more possibilities. */ + + switch (mapping_over) + { + case 0: /* Aliases come first. */ +#if defined (ALIAS) + while (alias_list && alias_list[local_index]) + { + register char *alias; + + alias = alias_list[local_index++]->name; + + if (igncase == 0 && (STREQN (alias, hint, hint_len))) + return (savestring (alias)); + else if (igncase && strncasecmp (alias, hint, hint_len) == 0) + return (savestring (alias)); + } +#endif /* ALIAS */ + local_index = 0; + mapping_over++; + + case 1: /* Then shell reserved words. */ + { + while (word_token_alist[local_index].word) + { + register char *reserved_word; + + reserved_word = word_token_alist[local_index++].word; + + if (STREQN (reserved_word, hint, hint_len)) + return (savestring (reserved_word)); + } + local_index = 0; + mapping_over++; + } + + case 2: /* Then function names. */ + while (varlist && varlist[local_index]) + { + register char *varname; + + varname = varlist[local_index++]->name; + + /* Honor completion-ignore-case for shell function names. */ + if (igncase == 0 && (STREQN (varname, hint, hint_len))) + return (savestring (varname)); + else if (igncase && strncasecmp (varname, hint, hint_len) == 0) + return (savestring (varname)); + } + local_index = 0; + mapping_over++; + + case 3: /* Then shell builtins. */ + for (; local_index < num_shell_builtins; local_index++) + { + /* Ignore it if it doesn't have a function pointer or if it + is not currently enabled. */ + if (!shell_builtins[local_index].function || + (shell_builtins[local_index].flags & BUILTIN_ENABLED) == 0) + continue; + + if (STREQN (shell_builtins[local_index].name, hint, hint_len)) + { + int i = local_index++; + + return (savestring (shell_builtins[i].name)); + } + } + local_index = 0; + mapping_over++; + } + +globword: + /* Limited support for completing command words with globbing chars. Only + a single match (multiple matches that end up reducing the number of + characters in the common prefix are bad) will ever be returned on + regular completion. */ + if (globpat) + { + if (state == 0) + { + rl_filename_completion_desired = 1; + + glob_ignore_case = igncase; + glob_matches = shell_glob_filename (hint, 0); + glob_ignore_case = old_glob_ignore_case; + + if (GLOB_FAILED (glob_matches) || glob_matches == 0) + { + glob_matches = (char **)NULL; + return ((char *)NULL); + } + + local_index = 0; + + if (glob_matches[1] && rl_completion_type == TAB) /* multiple matches are bad */ + return ((char *)NULL); + } + + while (val = glob_matches[local_index++]) + { + if (executable_or_directory (val)) + { + if (*hint_text == '~' && directory_part) + { + temp = maybe_restore_tilde (val, directory_part); + free (val); + val = temp; + } + return (val); + } + free (val); + } + + glob_ignore_case = old_glob_ignore_case; + return ((char *)NULL); + } + + /* If the text passed is a directory in the current directory, return it + as a possible match. Executables in directories in the current + directory can be specified using relative pathnames and successfully + executed even when `.' is not in $PATH. */ + if (hint_is_dir) + { + hint_is_dir = 0; /* only return the hint text once */ + return (savestring (hint_text)); + } + + /* Repeatedly call filename_completion_function while we have + members of PATH left. Question: should we stat each file? + Answer: we call executable_file () on each file. */ + outer: + + istate = (val != (char *)NULL); + + if (istate == 0) + { + char *current_path; + + /* Get the next directory from the path. If there is none, then we + are all done. */ + if (path == 0 || path[path_index] == 0 || + (current_path = extract_colon_unit (path, &path_index)) == 0) + return ((char *)NULL); + + searching_path = 1; + if (*current_path == 0) + { + free (current_path); + current_path = savestring ("."); + } + + if (*current_path == '~') + { + char *t; + + t = bash_tilde_expand (current_path, 0); + free (current_path); + current_path = t; + } + + if (current_path[0] == '.' && current_path[1] == '\0') + dot_in_path = 1; + + if (fnhint && fnhint != filename_hint) + free (fnhint); + if (filename_hint) + free (filename_hint); + + filename_hint = sh_makepath (current_path, hint, 0); + /* Need a quoted version (though it doesn't matter much in most + cases) because rl_filename_completion_function dequotes the + filename it gets, assuming that it's been quoted as part of + the input line buffer. */ + if (strpbrk (filename_hint, "\"'\\")) + fnhint = sh_backslash_quote (filename_hint, filename_bstab, 0); + else + fnhint = filename_hint; + free (current_path); /* XXX */ + } + + inner: + val = rl_filename_completion_function (fnhint, istate); + if (mapping_over == 4 && dircomplete_expand) + set_directory_hook (); + + istate = 1; + + if (val == 0) + { + /* If the hint text is an absolute program, then don't bother + searching through PATH. */ + if (absolute_program (hint)) + return ((char *)NULL); + + goto outer; + } + else + { + int match, freetemp; + + if (absolute_program (hint)) + { +#if 0 + if (igncase == 0) + match = strncmp (val, hint, hint_len) == 0; + else + match = strncasecmp (val, hint, hint_len) == 0; +#else + /* Why duplicate the comparison rl_filename_completion_function + already performs? */ + match = 1; +#endif + + /* If we performed tilde expansion, restore the original + filename. */ + if (*hint_text == '~') + temp = maybe_restore_tilde (val, directory_part); + else + temp = savestring (val); + freetemp = 1; + } + else + { + temp = strrchr (val, '/'); + + if (temp) + { + temp++; + if (igncase == 0) + freetemp = match = strncmp (temp, hint, hint_len) == 0; + else + freetemp = match = strncasecmp (temp, hint, hint_len) == 0; + if (match) + temp = savestring (temp); + } + else + freetemp = match = 0; + } + + /* If we have found a match, and it is an executable file, return it. + We don't return directory names when searching $PATH, since the + bash execution code won't find executables in directories which + appear in directories in $PATH when they're specified using + relative pathnames. */ +#if 0 + /* If we're not searching $PATH and we have a relative pathname, we + need to re-canonicalize it before testing whether or not it's an + executable or a directory so the shell treats .. relative to $PWD + according to the physical/logical option. The shell already + canonicalizes the directory name in order to tell readline where + to look, so not doing it here will be inconsistent. */ + /* XXX -- currently not used -- will introduce more inconsistency, + since shell does not canonicalize ../foo before passing it to + shell_execve(). */ + if (match && searching_path == 0 && *val == '.') + { + char *t, *t1; + + t = get_working_directory ("command-word-completion"); + t1 = make_absolute (val, t); + free (t); + cval = sh_canonpath (t1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + } + else +#endif + cval = val; + + if (match && executable_completion ((searching_path ? val : cval), searching_path)) + { + if (cval != val) + free (cval); + free (val); + val = ""; /* So it won't be NULL. */ + return (temp); + } + else + { + if (freetemp) + free (temp); + if (cval != val) + free (cval); + free (val); + goto inner; + } + } +} + +/* Completion inside an unterminated command substitution. */ +static char * +command_subst_completion_function (text, state) + const char *text; + int state; +{ + static char **matches = (char **)NULL; + static const char *orig_start; + static char *filename_text = (char *)NULL; + static int cmd_index, start_len; + char *value; + + if (state == 0) + { + if (filename_text) + free (filename_text); + orig_start = text; + if (*text == '`') + text++; + else if (*text == '$' && text[1] == '(') /* ) */ + text += 2; + /* If the text was quoted, suppress any quote character that the + readline completion code would insert. */ + rl_completion_suppress_quote = 1; + start_len = text - orig_start; + filename_text = savestring (text); + if (matches) + free (matches); + + /* + * At this point we can entertain the idea of re-parsing + * `filename_text' into a (possibly incomplete) command name and + * arguments, and doing completion based on that. This is + * currently very rudimentary, but it is a small improvement. + */ + for (value = filename_text + strlen (filename_text) - 1; value > filename_text; value--) + if (whitespace (*value) || member (*value, COMMAND_SEPARATORS)) + break; + if (value <= filename_text) + matches = rl_completion_matches (filename_text, command_word_completion_function); + else + { + value++; + start_len += value - filename_text; + if (whitespace (value[-1])) + matches = rl_completion_matches (value, rl_filename_completion_function); + else + matches = rl_completion_matches (value, command_word_completion_function); + } + + /* If there is more than one match, rl_completion_matches has already + put the lcd in matches[0]. Skip over it. */ + cmd_index = matches && matches[0] && matches[1]; + + /* If there's a single match and it's a directory, set the append char + to the expected `/'. Otherwise, don't append anything. */ + if (matches && matches[0] && matches[1] == 0 && test_for_directory (matches[0])) + rl_completion_append_character = '/'; + else + rl_completion_suppress_append = 1; + } + + if (matches == 0 || matches[cmd_index] == 0) + { + rl_filename_quoting_desired = 0; /* disable quoting */ + return ((char *)NULL); + } + else + { + value = (char *)xmalloc (1 + start_len + strlen (matches[cmd_index])); + + if (start_len == 1) + value[0] = *orig_start; + else + strncpy (value, orig_start, start_len); + + strcpy (value + start_len, matches[cmd_index]); + + cmd_index++; + return (value); + } +} + +/* Okay, now we write the entry_function for variable completion. */ +static char * +variable_completion_function (text, state) + const char *text; + int state; +{ + static char **varlist = (char **)NULL; + static int varlist_index; + static char *varname = (char *)NULL; + static int first_char, first_char_loc; + + if (!state) + { + if (varname) + free (varname); + + first_char_loc = 0; + first_char = text[0]; + + if (first_char == '$') + first_char_loc++; + + if (text[first_char_loc] == '{') + first_char_loc++; + + varname = savestring (text + first_char_loc); + + if (varlist) + strvec_dispose (varlist); + + varlist = all_variables_matching_prefix (varname); + varlist_index = 0; + } + + if (!varlist || !varlist[varlist_index]) + { + return ((char *)NULL); + } + else + { + char *value; + + value = (char *)xmalloc (4 + strlen (varlist[varlist_index])); + + if (first_char_loc) + { + value[0] = first_char; + if (first_char_loc == 2) + value[1] = '{'; + } + + strcpy (value + first_char_loc, varlist[varlist_index]); + if (first_char_loc == 2) + strcat (value, "}"); + + varlist_index++; + return (value); + } +} + +/* How about a completion function for hostnames? */ +static char * +hostname_completion_function (text, state) + const char *text; + int state; +{ + static char **list = (char **)NULL; + static int list_index = 0; + static int first_char, first_char_loc; + + /* If we don't have any state, make some. */ + if (state == 0) + { + FREE (list); + + list = (char **)NULL; + + first_char_loc = 0; + first_char = *text; + + if (first_char == '@') + first_char_loc++; + + list = hostnames_matching ((char *)text+first_char_loc); + list_index = 0; + } + + if (list && list[list_index]) + { + char *t; + + t = (char *)xmalloc (2 + strlen (list[list_index])); + *t = first_char; + strcpy (t + first_char_loc, list[list_index]); + list_index++; + return (t); + } + + return ((char *)NULL); +} + +/* + * A completion function for service names from /etc/services (or wherever). + */ +char * +bash_servicename_completion_function (text, state) + const char *text; + int state; +{ +#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GETSERVENT) + return ((char *)NULL); +#else + static char *sname = (char *)NULL; + static struct servent *srvent; + static int snamelen; + char *value; + char **alist, *aentry; + int afound; + + if (state == 0) + { + FREE (sname); + + sname = savestring (text); + snamelen = strlen (sname); + setservent (0); + } + + while (srvent = getservent ()) + { + afound = 0; + if (snamelen == 0 || (STREQN (sname, srvent->s_name, snamelen))) + break; + /* Not primary, check aliases */ + for (alist = srvent->s_aliases; *alist; alist++) + { + aentry = *alist; + if (STREQN (sname, aentry, snamelen)) + { + afound = 1; + break; + } + } + + if (afound) + break; + } + + if (srvent == 0) + { + endservent (); + return ((char *)NULL); + } + + value = afound ? savestring (aentry) : savestring (srvent->s_name); + return value; +#endif +} + +/* + * A completion function for group names from /etc/group (or wherever). + */ +char * +bash_groupname_completion_function (text, state) + const char *text; + int state; +{ +#if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GRP_H) + return ((char *)NULL); +#else + static char *gname = (char *)NULL; + static struct group *grent; + static int gnamelen; + char *value; + + if (state == 0) + { + FREE (gname); + gname = savestring (text); + gnamelen = strlen (gname); + + setgrent (); + } + + while (grent = getgrent ()) + { + if (gnamelen == 0 || (STREQN (gname, grent->gr_name, gnamelen))) + break; + } + + if (grent == 0) + { + endgrent (); + return ((char *)NULL); + } + + value = savestring (grent->gr_name); + return (value); +#endif +} + +/* Functions to perform history and alias expansions on the current line. */ + +#if defined (BANG_HISTORY) +/* Perform history expansion on the current line. If no history expansion + is done, pre_process_line() returns what it was passed, so we need to + allocate a new line here. */ +static char * +history_expand_line_internal (line) + char *line; +{ + char *new_line; + int old_verify; + + old_verify = hist_verify; + hist_verify = 0; + new_line = pre_process_line (line, 0, 0); + hist_verify = old_verify; + + return (new_line == line) ? savestring (line) : new_line; +} +#endif + +/* There was an error in expansion. Let the preprocessor print + the error here. */ +static void +cleanup_expansion_error () +{ + char *to_free; +#if defined (BANG_HISTORY) + int old_verify; + + old_verify = hist_verify; + hist_verify = 0; +#endif + + fprintf (rl_outstream, "\r\n"); + to_free = pre_process_line (rl_line_buffer, 1, 0); +#if defined (BANG_HISTORY) + hist_verify = old_verify; +#endif + if (to_free != rl_line_buffer) + FREE (to_free); + putc ('\r', rl_outstream); + rl_forced_update_display (); +} + +/* If NEW_LINE differs from what is in the readline line buffer, add an + undo record to get from the readline line buffer contents to the new + line and make NEW_LINE the current readline line. */ +static void +maybe_make_readline_line (new_line) + char *new_line; +{ + if (new_line && strcmp (new_line, rl_line_buffer) != 0) + { + rl_point = rl_end; + + rl_add_undo (UNDO_BEGIN, 0, 0, 0); + rl_delete_text (0, rl_point); + rl_point = rl_end = rl_mark = 0; + rl_insert_text (new_line); + rl_add_undo (UNDO_END, 0, 0, 0); + } +} + +/* Make NEW_LINE be the current readline line. This frees NEW_LINE. */ +static void +set_up_new_line (new_line) + char *new_line; +{ + int old_point, at_end; + + old_point = rl_point; + at_end = rl_point == rl_end; + + /* If the line was history and alias expanded, then make that + be one thing to undo. */ + maybe_make_readline_line (new_line); + free (new_line); + + /* Place rl_point where we think it should go. */ + if (at_end) + rl_point = rl_end; + else if (old_point < rl_end) + { + rl_point = old_point; + if (!whitespace (rl_line_buffer[rl_point])) + rl_forward_word (1, 0); + } +} + +#if defined (ALIAS) +/* Expand aliases in the current readline line. */ +static int +alias_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + + new_line = alias_expand (rl_line_buffer); + + if (new_line) + { + set_up_new_line (new_line); + return (0); + } + else + { + cleanup_expansion_error (); + return (1); + } +} +#endif + +#if defined (BANG_HISTORY) +/* History expand the line. */ +static int +history_expand_line (count, ignore) + int count, ignore; +{ + char *new_line; + + new_line = history_expand_line_internal (rl_line_buffer); + + if (new_line) + { + set_up_new_line (new_line); + return (0); + } + else + { + cleanup_expansion_error (); + return (1); + } +} + +/* Expand history substitutions in the current line and then insert a + space (hopefully close to where we were before). */ +static int +tcsh_magic_space (count, ignore) + int count, ignore; +{ + int dist_from_end, old_point; + + old_point = rl_point; + dist_from_end = rl_end - rl_point; + if (history_expand_line (count, ignore) == 0) + { + /* Try a simple heuristic from Stephen Gildea . + This works if all expansions were before rl_point or if no expansions + were performed. */ + rl_point = (old_point == 0) ? old_point : rl_end - dist_from_end; + rl_insert (1, ' '); + return (0); + } + else + return (1); +} +#endif /* BANG_HISTORY */ + +/* History and alias expand the line. */ +static int +history_and_alias_expand_line (count, ignore) + int count, ignore; +{ + char *new_line, *t; + + new_line = 0; +#if defined (BANG_HISTORY) + new_line = history_expand_line_internal (rl_line_buffer); +#endif + +#if defined (ALIAS) + if (new_line) + { + char *alias_line; + + alias_line = alias_expand (new_line); + free (new_line); + new_line = alias_line; + } +#endif /* ALIAS */ + + if (new_line) + { + set_up_new_line (new_line); + return (0); + } + else + { + cleanup_expansion_error (); + return (1); + } +} + +/* History and alias expand the line, then perform the shell word + expansions by calling expand_string. This can't use set_up_new_line() + because we want the variable expansions as a separate undo'able + set of operations. */ +static int +shell_expand_line (count, ignore) + int count, ignore; +{ + char *new_line, *t; + WORD_LIST *expanded_string; + WORD_DESC *w; + + new_line = 0; +#if defined (BANG_HISTORY) + new_line = history_expand_line_internal (rl_line_buffer); +#endif + + t = expand_string_dollar_quote (new_line ? new_line : rl_line_buffer, 0); + FREE (new_line); + new_line = t; + +#if defined (ALIAS) + if (new_line) + { + char *alias_line; + + alias_line = alias_expand (new_line); + free (new_line); + new_line = alias_line; + } +#endif /* ALIAS */ + + if (new_line) + { + int old_point = rl_point; + int at_end = rl_point == rl_end; + + /* If the line was history and alias expanded, then make that + be one thing to undo. */ + maybe_make_readline_line (new_line); + free (new_line); + + /* If there is variable expansion to perform, do that as a separate + operation to be undone. */ + +#if 1 + w = alloc_word_desc (); + w->word = savestring (rl_line_buffer); + w->flags = rl_explicit_arg ? (W_NOPROCSUB|W_NOCOMSUB) : 0; + expanded_string = expand_word (w, rl_explicit_arg ? Q_HERE_DOCUMENT : 0); + dispose_word (w); +#else + new_line = savestring (rl_line_buffer); + expanded_string = expand_string (new_line, 0); + FREE (new_line); +#endif + + if (expanded_string == 0) + { + new_line = (char *)xmalloc (1); + new_line[0] = '\0'; + } + else + { + new_line = string_list (expanded_string); + dispose_words (expanded_string); + } + + maybe_make_readline_line (new_line); + free (new_line); + + /* Place rl_point where we think it should go. */ + if (at_end) + rl_point = rl_end; + else if (old_point < rl_end) + { + rl_point = old_point; + if (!whitespace (rl_line_buffer[rl_point])) + rl_forward_word (1, 0); + } + return 0; + } + else + { + cleanup_expansion_error (); + return 1; + } +} + +/* If FIGNORE is set, then don't match files with the given suffixes when + completing filenames. If only one of the possibilities has an acceptable + suffix, delete the others, else just return and let the completer + signal an error. It is called by the completer when real + completions are done on filenames by the completer's internal + function, not for completion lists (M-?) and not on "other" + completion types, such as hostnames or commands. */ + +static struct ignorevar fignore = +{ + "FIGNORE", + (struct ign *)0, + 0, + (char *)0, + (sh_iv_item_func_t *) 0, +}; + +static void +_ignore_completion_names (names, name_func) + char **names; + sh_ignore_func_t *name_func; +{ + char **newnames; + int idx, nidx; + char **oldnames; + int oidx; + + /* If there is only one completion, see if it is acceptable. If it is + not, free it up. In any case, short-circuit and return. This is a + special case because names[0] is not the prefix of the list of names + if there is only one completion; it is the completion itself. */ + if (names[1] == (char *)0) + { + if (force_fignore) + if ((*name_func) (names[0]) == 0) + { + free (names[0]); + names[0] = (char *)NULL; + } + + return; + } + + /* Allocate space for array to hold list of pointers to matching + filenames. The pointers are copied back to NAMES when done. */ + for (nidx = 1; names[nidx]; nidx++) + ; + newnames = strvec_create (nidx + 1); + + if (force_fignore == 0) + { + oldnames = strvec_create (nidx - 1); + oidx = 0; + } + + newnames[0] = names[0]; + for (idx = nidx = 1; names[idx]; idx++) + { + if ((*name_func) (names[idx])) + newnames[nidx++] = names[idx]; + else if (force_fignore == 0) + oldnames[oidx++] = names[idx]; + else + free (names[idx]); + } + + newnames[nidx] = (char *)NULL; + + /* If none are acceptable then let the completer handle it. */ + if (nidx == 1) + { + if (force_fignore) + { + free (names[0]); + names[0] = (char *)NULL; + } + else + free (oldnames); + + free (newnames); + return; + } + + if (force_fignore == 0) + { + while (oidx) + free (oldnames[--oidx]); + free (oldnames); + } + + /* If only one is acceptable, copy it to names[0] and return. */ + if (nidx == 2) + { + free (names[0]); + names[0] = newnames[1]; + names[1] = (char *)NULL; + free (newnames); + return; + } + + /* Copy the acceptable names back to NAMES, set the new array end, + and return. */ + for (nidx = 1; newnames[nidx]; nidx++) + names[nidx] = newnames[nidx]; + names[nidx] = (char *)NULL; + free (newnames); +} + +static int +name_is_acceptable (name) + const char *name; +{ + struct ign *p; + int nlen; + + for (nlen = strlen (name), p = fignore.ignores; p->val; p++) + { + if (nlen > p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len])) + return (0); + } + + return (1); +} + +#if 0 +static int +ignore_dot_names (name) + char *name; +{ + return (name[0] != '.'); +} +#endif + +static int +filename_completion_ignore (names) + char **names; +{ +#if 0 + if (glob_dot_filenames == 0) + _ignore_completion_names (names, ignore_dot_names); +#endif + + setup_ignore_patterns (&fignore); + + if (fignore.num_ignores == 0) + return 0; + + _ignore_completion_names (names, name_is_acceptable); + + return 0; +} + +/* Return 1 if NAME is a directory. NAME undergoes tilde expansion. */ +static int +test_for_directory (name) + const char *name; +{ + char *fn; + int r; + + fn = bash_tilde_expand (name, 0); + r = file_isdir (fn); + free (fn); + + return (r); +} + +static int +test_for_canon_directory (name) + const char *name; +{ + char *fn; + int r; + + fn = (*name == '~') ? bash_tilde_expand (name, 0) : savestring (name); + bash_filename_stat_hook (&fn); + r = file_isdir (fn); + free (fn); + + return (r); +} + +/* Remove files from NAMES, leaving directories. */ +static int +bash_ignore_filenames (names) + char **names; +{ + _ignore_completion_names (names, test_for_directory); + return 0; +} + +static int +bash_progcomp_ignore_filenames (names) + char **names; +{ + _ignore_completion_names (names, test_for_canon_directory); + return 0; +} + +static int +return_zero (name) + const char *name; +{ + return 0; +} + +static int +bash_ignore_everything (names) + char **names; +{ + _ignore_completion_names (names, return_zero); + return 0; +} + +/* Replace a tilde-prefix in VAL with a `~', assuming the user typed it. VAL + is an expanded filename. DIRECTORY_PART is the tilde-prefix portion + of the un-tilde-expanded version of VAL (what the user typed). */ +static char * +restore_tilde (val, directory_part) + char *val, *directory_part; +{ + int l, vl, dl2, xl; + char *dh2, *expdir, *ret, *v; + + vl = strlen (val); + + /* We need to duplicate the expansions readline performs on the directory + portion before passing it to our completion function. */ + dh2 = directory_part ? bash_dequote_filename (directory_part, 0) : 0; + bash_directory_expansion (&dh2); + dl2 = strlen (dh2); + + expdir = bash_tilde_expand (directory_part, 0); + xl = strlen (expdir); + if (*directory_part == '~' && STREQ (directory_part, expdir)) + { + /* tilde expansion failed, so what should we return? we use what the + user typed. */ + v = mbschr (val, '/'); + vl = STRLEN (v); + ret = (char *)xmalloc (xl + vl + 2); + strcpy (ret, directory_part); + if (v && *v) + strcpy (ret + xl, v); + + free (dh2); + free (expdir); + + return ret; + } + free (expdir); + + /* + dh2 = unexpanded but dequoted tilde-prefix + dl2 = length of tilde-prefix + expdir = tilde-expanded tilde-prefix + xl = length of expanded tilde-prefix + l = length of remainder after tilde-prefix + */ + l = (vl - xl) + 1; + if (l <= 0) + { + free (dh2); + return (savestring (val)); /* XXX - just punt */ + } + + ret = (char *)xmalloc (dl2 + 2 + l); + strcpy (ret, dh2); + strcpy (ret + dl2, val + xl); + + free (dh2); + return (ret); +} + +static char * +maybe_restore_tilde (val, directory_part) + char *val, *directory_part; +{ + rl_icppfunc_t *save; + char *ret; + + save = (dircomplete_expand == 0) ? save_directory_hook () : (rl_icppfunc_t *)0; + ret = restore_tilde (val, directory_part); + if (save) + restore_directory_hook (save); + return ret; +} + +/* Simulate the expansions that will be performed by + rl_filename_completion_function. This must be called with the address of + a pointer to malloc'd memory. */ +static void +bash_directory_expansion (dirname) + char **dirname; +{ + char *d, *nd; + + d = savestring (*dirname); + + if ((rl_directory_rewrite_hook) && (*rl_directory_rewrite_hook) (&d)) + { + free (*dirname); + *dirname = d; + } + else if (rl_directory_completion_hook && (*rl_directory_completion_hook) (&d)) + { + free (*dirname); + *dirname = d; + } + else if (rl_completion_found_quote) + { + nd = bash_dequote_filename (d, rl_completion_quote_character); + free (*dirname); + free (d); + *dirname = nd; + } + else + free (d); +} + +/* If necessary, rewrite directory entry */ +static char * +bash_filename_rewrite_hook (fname, fnlen) + char *fname; + int fnlen; +{ + char *conv; + + conv = fnx_fromfs (fname, fnlen); + if (conv != fname) + conv = savestring (conv); + return conv; +} + +/* Functions to save and restore the appropriate directory hook */ +/* This is not static so the shopt code can call it */ +void +set_directory_hook () +{ + if (dircomplete_expand) + { + rl_directory_completion_hook = bash_directory_completion_hook; + rl_directory_rewrite_hook = (rl_icppfunc_t *)0; + } + else + { + rl_directory_rewrite_hook = bash_directory_completion_hook; + rl_directory_completion_hook = (rl_icppfunc_t *)0; + } +} + +static rl_icppfunc_t * +save_directory_hook () +{ + rl_icppfunc_t *ret; + + if (dircomplete_expand) + { + ret = rl_directory_completion_hook; + rl_directory_completion_hook = (rl_icppfunc_t *)NULL; + } + else + { + ret = rl_directory_rewrite_hook; + rl_directory_rewrite_hook = (rl_icppfunc_t *)NULL; + } + + return ret; +} + +static void +restore_directory_hook (hookf) + rl_icppfunc_t *hookf; +{ + if (dircomplete_expand) + rl_directory_completion_hook = hookf; + else + rl_directory_rewrite_hook = hookf; +} + +/* Check whether not DIRNAME, with any trailing slash removed, exists. If + SHOULD_DEQUOTE is non-zero, we dequote the directory name first. */ +static int +directory_exists (dirname, should_dequote) + const char *dirname; + int should_dequote; +{ + char *new_dirname; + int dirlen, r; + struct stat sb; + + /* We save the string and chop the trailing slash because stat/lstat behave + inconsistently if one is present. */ + new_dirname = should_dequote ? bash_dequote_filename ((char *)dirname, rl_completion_quote_character) : savestring (dirname); + dirlen = STRLEN (new_dirname); + if (new_dirname[dirlen - 1] == '/') + new_dirname[dirlen - 1] = '\0'; +#if defined (HAVE_LSTAT) + r = lstat (new_dirname, &sb) == 0; +#else + r = stat (new_dirname, &sb) == 0; +#endif + free (new_dirname); + return (r); +} + +/* Expand a filename before the readline completion code passes it to stat(2). + The filename will already have had tilde expansion performed. */ +static int +bash_filename_stat_hook (dirname) + char **dirname; +{ + char *local_dirname, *new_dirname, *t; + int should_expand_dirname, return_value; + int global_nounset; + WORD_LIST *wl; + + local_dirname = *dirname; + should_expand_dirname = return_value = 0; + if (t = mbschr (local_dirname, '$')) + should_expand_dirname = '$'; + else if (t = mbschr (local_dirname, '`')) /* XXX */ + should_expand_dirname = '`'; + + if (should_expand_dirname && directory_exists (local_dirname, 0)) + should_expand_dirname = 0; + + if (should_expand_dirname) + { + new_dirname = savestring (local_dirname); + /* no error messages, and expand_prompt_string doesn't longjmp so we don't + have to worry about restoring this setting. */ + global_nounset = unbound_vars_is_error; + unbound_vars_is_error = 0; + wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB|W_NOPROCSUB|W_COMPLETE); /* does the right thing */ + unbound_vars_is_error = global_nounset; + if (wl) + { + free (new_dirname); + new_dirname = string_list (wl); + /* Tell the completer we actually expanded something and change + *dirname only if we expanded to something non-null -- stat + behaves unpredictably when passed null or empty strings */ + if (new_dirname && *new_dirname) + { + free (local_dirname); /* XXX */ + local_dirname = *dirname = new_dirname; + return_value = STREQ (local_dirname, *dirname) == 0; + } + else + free (new_dirname); + dispose_words (wl); + } + else + free (new_dirname); + } + + /* This is very similar to the code in bash_directory_completion_hook below, + but without spelling correction and not worrying about whether or not + we change relative pathnames. */ + if (no_symbolic_links == 0 && (local_dirname[0] != '.' || local_dirname[1])) + { + char *temp1, *temp2; + + t = get_working_directory ("symlink-hook"); + temp1 = make_absolute (local_dirname, t); + free (t); + temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + + /* If we can't canonicalize, bail. */ + if (temp2 == 0) + { + free (temp1); + return return_value; + } + + free (local_dirname); + *dirname = temp2; + free (temp1); + } + + return (return_value); +} + +/* Handle symbolic link references and other directory name + expansions while hacking completion. This should return 1 if it modifies + the DIRNAME argument, 0 otherwise. It should make sure not to modify + DIRNAME if it returns 0. */ +static int +bash_directory_completion_hook (dirname) + char **dirname; +{ + char *local_dirname, *new_dirname, *t; + int return_value, should_expand_dirname, nextch, closer; + WORD_LIST *wl; + + return_value = should_expand_dirname = nextch = closer = 0; + local_dirname = *dirname; + + should_expand_dirname = bash_check_expchar (local_dirname, 1, &nextch, &closer); + + if (should_expand_dirname && directory_exists (local_dirname, 1)) + should_expand_dirname = 0; + + if (should_expand_dirname) + { + new_dirname = savestring (local_dirname); + wl = expand_prompt_string (new_dirname, 0, W_NOCOMSUB|W_NOPROCSUB|W_COMPLETE); /* does the right thing */ + if (wl) + { + *dirname = string_list (wl); + /* Tell the completer to replace the directory name only if we + actually expanded something. */ + return_value = STREQ (local_dirname, *dirname) == 0; + free (local_dirname); + free (new_dirname); + dispose_words (wl); + local_dirname = *dirname; + + set_filename_quote_chars (should_expand_dirname, nextch, closer); + } + else + { + free (new_dirname); + free (local_dirname); + *dirname = (char *)xmalloc (1); + **dirname = '\0'; + return 1; + } + } + else + { + /* Dequote the filename even if we don't expand it. */ + new_dirname = bash_dequote_filename (local_dirname, rl_completion_quote_character); + return_value = STREQ (local_dirname, new_dirname) == 0; + free (local_dirname); + local_dirname = *dirname = new_dirname; + } + + /* no_symbolic_links == 0 -> use (default) logical view of the file system. + local_dirname[0] == '.' && local_dirname[1] == '/' means files in the + current directory (./). + local_dirname[0] == '.' && local_dirname[1] == 0 means relative pathnames + in the current directory (e.g., lib/sh). + XXX - should we do spelling correction on these? */ + + /* This is test as it was in bash-4.2: skip relative pathnames in current + directory. Change test to + (local_dirname[0] != '.' || (local_dirname[1] && local_dirname[1] != '/')) + if we want to skip paths beginning with ./ also. */ + if (no_symbolic_links == 0 && (local_dirname[0] != '.' || local_dirname[1])) + { + char *temp1, *temp2; + int len1, len2; + + /* If we have a relative path + (local_dirname[0] != '/' && local_dirname[0] != '.') + that is canonical after appending it to the current directory, then + temp1 = temp2+'/' + That is, + strcmp (temp1, temp2) == 0 + after adding a slash to temp2 below. It should be safe to not + change those. + */ + t = get_working_directory ("symlink-hook"); + temp1 = make_absolute (local_dirname, t); + free (t); + temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + + /* Try spelling correction if initial canonicalization fails. Make + sure we are set to replace the directory name with the results so + subsequent directory checks don't fail. */ + if (temp2 == 0 && dircomplete_spelling && dircomplete_expand) + { + size_t l1, l2; + + temp2 = dirspell (temp1); + l2 = STRLEN (temp2); + /* Don't take matches if they are shorter than the original path */ + if (temp2 && l2 < strlen (temp1) && STREQN (temp1, temp2, l2)) + { + free (temp2); + temp2 = 0; + } + if (temp2) + { + free (temp1); + temp1 = temp2; + temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + return_value |= temp2 != 0; + } + } + /* If we can't canonicalize, bail. */ + if (temp2 == 0) + { + free (temp1); + return return_value; + } + len1 = strlen (temp1); + if (temp1[len1 - 1] == '/') + { + len2 = strlen (temp2); + if (len2 > 2) /* don't append `/' to `/' or `//' */ + { + temp2 = (char *)xrealloc (temp2, len2 + 2); + temp2[len2] = '/'; + temp2[len2 + 1] = '\0'; + } + } + + /* dircomplete_expand_relpath == 0 means we want to leave relative + pathnames that are unchanged by canonicalization alone. + *local_dirname != '/' && *local_dirname != '.' == relative pathname + (consistent with general.c:absolute_pathname()) + temp1 == temp2 (after appending a slash to temp2) means the pathname + is not changed by canonicalization as described above. */ + if (dircomplete_expand_relpath || ((local_dirname[0] != '/' && local_dirname[0] != '.') && STREQ (temp1, temp2) == 0)) + return_value |= STREQ (local_dirname, temp2) == 0; + free (local_dirname); + *dirname = temp2; + free (temp1); + } + + return (return_value); +} + +static char **history_completion_array = (char **)NULL; +static int harry_size; +static int harry_len; + +static void +build_history_completion_array () +{ + register int i, j; + HIST_ENTRY **hlist; + char **tokens; + + /* First, clear out the current dynamic history completion list. */ + if (harry_size) + { + strvec_dispose (history_completion_array); + history_completion_array = (char **)NULL; + harry_size = 0; + harry_len = 0; + } + + /* Next, grovel each line of history, making each shell-sized token + a separate entry in the history_completion_array. */ + hlist = history_list (); + + if (hlist) + { + for (i = 0; hlist[i]; i++) + ; + for ( --i; i >= 0; i--) + { + /* Separate each token, and place into an array. */ + tokens = history_tokenize (hlist[i]->line); + + for (j = 0; tokens && tokens[j]; j++) + { + if (harry_len + 2 > harry_size) + history_completion_array = strvec_resize (history_completion_array, harry_size += 10); + + history_completion_array[harry_len++] = tokens[j]; + history_completion_array[harry_len] = (char *)NULL; + } + free (tokens); + } + + /* Sort the complete list of tokens. */ + if (dabbrev_expand_active == 0) + qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)strvec_strcmp); + } +} + +static char * +history_completion_generator (hint_text, state) + const char *hint_text; + int state; +{ + static int local_index, len; + static const char *text; + + /* If this is the first call to the generator, then initialize the + list of strings to complete over. */ + if (state == 0) + { + if (dabbrev_expand_active) /* This is kind of messy */ + rl_completion_suppress_append = 1; + local_index = 0; + build_history_completion_array (); + text = hint_text; + len = strlen (text); + } + + while (history_completion_array && history_completion_array[local_index]) + { + /* XXX - should this use completion-ignore-case? */ + if (strncmp (text, history_completion_array[local_index++], len) == 0) + return (savestring (history_completion_array[local_index - 1])); + } + return ((char *)NULL); +} + +static int +dynamic_complete_history (count, key) + int count, key; +{ + int r; + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + rl_compignore_func_t *orig_ignore_func; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + orig_ignore_func = rl_ignore_some_completions_function; + + rl_completion_entry_function = history_completion_generator; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + rl_ignore_some_completions_function = filename_completion_ignore; + + /* XXX - use rl_completion_mode here? */ + if (rl_last_func == dynamic_complete_history) + r = rl_complete_internal ('?'); + else + r = rl_complete_internal (TAB); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + rl_ignore_some_completions_function = orig_ignore_func; + + return r; +} + +static int +bash_dabbrev_expand (count, key) + int count, key; +{ + int r, orig_suppress, orig_sort; + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + rl_compignore_func_t *orig_ignore_func; + + orig_func = rl_menu_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + orig_ignore_func = rl_ignore_some_completions_function; + orig_suppress = rl_completion_suppress_append; + orig_sort = rl_sort_completion_matches; + + rl_menu_completion_entry_function = history_completion_generator; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + rl_ignore_some_completions_function = filename_completion_ignore; + rl_filename_completion_desired = 0; + rl_completion_suppress_append = 1; + rl_sort_completion_matches = 0; + + /* XXX - use rl_completion_mode here? */ + dabbrev_expand_active = 1; + if (rl_last_func == bash_dabbrev_expand) + rl_last_func = rl_menu_complete; + r = rl_menu_complete (count, key); + dabbrev_expand_active = 0; + + rl_last_func = bash_dabbrev_expand; + rl_menu_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + rl_ignore_some_completions_function = orig_ignore_func; + rl_completion_suppress_append = orig_suppress; + rl_sort_completion_matches = orig_sort; + + return r; +} + +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) +static int +bash_complete_username (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_username_internal (rl_completion_mode (bash_complete_username)); +} + +static int +bash_possible_username_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_username_internal ('?'); +} + +static int +bash_complete_username_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, rl_username_completion_function); +} + +static int +bash_complete_filename (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_filename_internal (rl_completion_mode (bash_complete_filename)); +} + +static int +bash_possible_filename_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_filename_internal ('?'); +} + +static int +bash_complete_filename_internal (what_to_do) + int what_to_do; +{ + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + rl_icppfunc_t *orig_dir_func; + rl_compignore_func_t *orig_ignore_func; + const char *orig_rl_completer_word_break_characters; + int r; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + orig_ignore_func = rl_ignore_some_completions_function; + orig_rl_completer_word_break_characters = rl_completer_word_break_characters; + + orig_dir_func = save_directory_hook (); + + rl_completion_entry_function = rl_filename_completion_function; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + rl_ignore_some_completions_function = filename_completion_ignore; + rl_completer_word_break_characters = " \t\n\"\'"; + + r = rl_complete_internal (what_to_do); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + rl_ignore_some_completions_function = orig_ignore_func; + rl_completer_word_break_characters = orig_rl_completer_word_break_characters; + + restore_directory_hook (orig_dir_func); + + return r; +} + +static int +bash_complete_hostname (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_hostname_internal (rl_completion_mode (bash_complete_hostname)); +} + +static int +bash_possible_hostname_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_hostname_internal ('?'); +} + +static int +bash_complete_variable (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_variable_internal (rl_completion_mode (bash_complete_variable)); +} + +static int +bash_possible_variable_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_variable_internal ('?'); +} + +static int +bash_complete_command (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_command_internal (rl_completion_mode (bash_complete_command)); +} + +static int +bash_possible_command_completions (ignore, ignore2) + int ignore, ignore2; +{ + return bash_complete_command_internal ('?'); +} + +static int +bash_complete_hostname_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, hostname_completion_function); +} + +static int +bash_complete_variable_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, variable_completion_function); +} + +static int +bash_complete_command_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, command_word_completion_function); +} + +static int +completion_glob_pattern (string) + char *string; +{ + return (glob_pattern_p (string) == 1); +} + +static char *globtext; +static char *globorig; + +static char * +glob_complete_word (text, state) + const char *text; + int state; +{ + static char **matches = (char **)NULL; + static int ind; + int glen; + char *ret, *ttext; + + if (state == 0) + { + rl_filename_completion_desired = 1; + FREE (matches); + if (globorig != globtext) + FREE (globorig); + FREE (globtext); + + ttext = bash_tilde_expand (text, 0); + + if (rl_explicit_arg) + { + globorig = savestring (ttext); + glen = strlen (ttext); + globtext = (char *)xmalloc (glen + 2); + strcpy (globtext, ttext); + globtext[glen] = '*'; + globtext[glen+1] = '\0'; + } + else + globtext = globorig = savestring (ttext); + + if (ttext != text) + free (ttext); + + matches = shell_glob_filename (globtext, 0); + if (GLOB_FAILED (matches)) + matches = (char **)NULL; + ind = 0; + } + + ret = matches ? matches[ind] : (char *)NULL; + ind++; + return ret; +} + +static int +bash_glob_completion_internal (what_to_do) + int what_to_do; +{ + return bash_specific_completion (what_to_do, glob_complete_word); +} + +/* A special quoting function so we don't end up quoting globbing characters + in the word if there are no matches or multiple matches. */ +static char * +bash_glob_quote_filename (s, rtype, qcp) + char *s; + int rtype; + char *qcp; +{ + if (globorig && qcp && *qcp == '\0' && STREQ (s, globorig)) + return (savestring (s)); + else + return (bash_quote_filename (s, rtype, qcp)); +} + +static int +bash_glob_complete_word (count, key) + int count, key; +{ + int r; + rl_quote_func_t *orig_quoting_function; + + if (rl_editing_mode == EMACS_EDITING_MODE) + rl_explicit_arg = 1; /* force `*' append */ + orig_quoting_function = rl_filename_quoting_function; + rl_filename_quoting_function = bash_glob_quote_filename; + + r = bash_glob_completion_internal (rl_completion_mode (bash_glob_complete_word)); + + rl_filename_quoting_function = orig_quoting_function; + return r; +} + +static int +bash_glob_expand_word (count, key) + int count, key; +{ + return bash_glob_completion_internal ('*'); +} + +static int +bash_glob_list_expansions (count, key) + int count, key; +{ + return bash_glob_completion_internal ('?'); +} + +static int +bash_specific_completion (what_to_do, generator) + int what_to_do; + rl_compentry_func_t *generator; +{ + rl_compentry_func_t *orig_func; + rl_completion_func_t *orig_attempt_func; + rl_compignore_func_t *orig_ignore_func; + int r; + + orig_func = rl_completion_entry_function; + orig_attempt_func = rl_attempted_completion_function; + orig_ignore_func = rl_ignore_some_completions_function; + rl_completion_entry_function = generator; + rl_attempted_completion_function = NULL; + rl_ignore_some_completions_function = orig_ignore_func; + + r = rl_complete_internal (what_to_do); + + rl_completion_entry_function = orig_func; + rl_attempted_completion_function = orig_attempt_func; + rl_ignore_some_completions_function = orig_ignore_func; + + return r; +} + +#endif /* SPECIFIC_COMPLETION_FUNCTIONS */ + +#if defined (VI_MODE) +/* Completion, from vi mode's point of view. This is a modified version of + rl_vi_complete which uses the bash globbing code to implement what POSIX + specifies, which is to append a `*' and attempt filename generation (which + has the side effect of expanding any globbing characters in the word). */ +static int +bash_vi_complete (count, key) + int count, key; +{ +#if defined (SPECIFIC_COMPLETION_FUNCTIONS) + int p, r; + char *t; + + if ((rl_point < rl_end) && (!whitespace (rl_line_buffer[rl_point]))) + { + if (!whitespace (rl_line_buffer[rl_point + 1])) + rl_vi_end_word (1, 'E'); + rl_point++; + } + + /* Find boundaries of current word, according to vi definition of a + `bigword'. */ + t = 0; + if (rl_point > 0) + { + p = rl_point; + rl_vi_bWord (1, 'B'); + r = rl_point; + rl_point = p; + p = r; + + t = substring (rl_line_buffer, p, rl_point); + } + + if (t && completion_glob_pattern (t) == 0) + rl_explicit_arg = 1; /* XXX - force glob_complete_word to append `*' */ + FREE (t); + + if (key == '*') /* Expansion and replacement. */ + r = bash_glob_expand_word (count, key); + else if (key == '=') /* List possible completions. */ + r = bash_glob_list_expansions (count, key); + else if (key == '\\') /* Standard completion */ + r = bash_glob_complete_word (count, key); + else + r = rl_complete (0, key); + + if (key == '*' || key == '\\') + rl_vi_start_inserting (key, 1, 1); + + return (r); +#else + return rl_vi_complete (count, key); +#endif /* !SPECIFIC_COMPLETION_FUNCTIONS */ +} +#endif /* VI_MODE */ + +/* Filename quoting for completion. */ +/* A function to strip unquoted quote characters (single quotes, double + quotes, and backslashes). It allows single quotes to appear + within double quotes, and vice versa. It should be smarter. */ +static char * +bash_dequote_filename (text, quote_char) + char *text; + int quote_char; +{ + char *ret, *p, *r; + int l, quoted; + + l = strlen (text); + ret = (char *)xmalloc (l + 1); + for (quoted = quote_char, p = text, r = ret; p && *p; p++) + { + /* Allow backslash-escaped characters to pass through unscathed. */ + if (*p == '\\') + { + /* Backslashes are preserved within single quotes. */ + if (quoted == '\'') + *r++ = *p; + /* Backslashes are preserved within double quotes unless the + character is one that is defined to be escaped */ + else if (quoted == '"' && ((sh_syntaxtab[(unsigned char)p[1]] & CBSDQUOTE) == 0)) + *r++ = *p; + + *r++ = *++p; + if (*p == '\0') + return ret; /* XXX - was break; */ + continue; + } + /* Close quote. */ + if (quoted && *p == quoted) + { + quoted = 0; + continue; + } + /* Open quote. */ + if (quoted == 0 && (*p == '\'' || *p == '"')) + { + quoted = *p; + continue; + } + *r++ = *p; + } + *r = '\0'; + return ret; +} + +/* Quote characters that the readline completion code would treat as + word break characters with backslashes. Pass backslash-quoted + characters through without examination. */ +static char * +quote_word_break_chars (text) + char *text; +{ + char *ret, *r, *s; + int l; + + l = strlen (text); + ret = (char *)xmalloc ((2 * l) + 1); + for (s = text, r = ret; *s; s++) + { + /* Pass backslash-quoted characters through, including the backslash. */ + if (*s == '\\') + { + *r++ = '\\'; + *r++ = *++s; + if (*s == '\0') + break; + continue; + } + /* OK, we have an unquoted character. Check its presence in + rl_completer_word_break_characters. */ + if (mbschr (rl_completer_word_break_characters, *s)) + *r++ = '\\'; + /* XXX -- check for standalone tildes here and backslash-quote them */ + if (s == text && *s == '~' && file_exists (text)) + *r++ = '\\'; + *r++ = *s; + } + *r = '\0'; + return ret; +} + +/* Return a character in DIRNAME that will cause shell expansion to be + performed. If NEXTP is non-null, *NEXTP gets the expansion character that + follows RET (e.g., '{' or `(' for `$'). If CLOSERP is non-null, *CLOSERP + gets the character that should close . If NEED_CLOSER is non- + zero, any expansion pair that isn't closed causes this function to + return 0, which indicates that we didn't find an expansion character. It's + used in case DIRNAME is going to be expanded. If DIRNAME is just going to + be quoted, NEED_CLOSER will be 0. */ +static int +bash_check_expchar (dirname, need_closer, nextp, closerp) + char *dirname; + int need_closer; + int *nextp, *closerp; +{ + char *t; + int ret, n, c; + + ret = n = c = 0; + if (t = mbschr (dirname, '$')) + { + ret = '$'; + n = t[1]; + /* Deliberately does not handle the deprecated $[...] arithmetic + expansion syntax */ + if (n == '(') + c = ')'; + else if (n == '{') + c = '}'; + else + n = 0; + + if (c && need_closer) /* XXX */ + { + int p; + char delims[2]; + + delims[0] = c; delims[1] = 0; + p = skip_to_delim (t, 1, delims, SD_NOJMP|SD_COMPLETE); + if (t[p] != c) + ret = 0; + } + } + else if (dirname[0] == '~') + ret = '~'; + else + { + t = mbschr (dirname, '`'); + if (t) + { + if (need_closer == 0) + ret = '`'; + else if (unclosed_pair (dirname, strlen (dirname), "`") == 0) + ret = '`'; + } + } + + if (nextp) + *nextp = n; + if (closerp) + *closerp = c; + + return ret; +} + +/* Make sure EXPCHAR and, if non-zero, NEXTCH and CLOSER are not in the set + of characters to be backslash-escaped. This is the only place + custom_filename_quote_characters is modified. */ +static void +set_filename_quote_chars (expchar, nextch, closer) + int expchar, nextch, closer; +{ + int i, j, c; + + if (rl_filename_quote_characters && *rl_filename_quote_characters) + { + i = strlen (default_filename_quote_characters); + custom_filename_quote_characters = xrealloc (custom_filename_quote_characters, i+1); + for (i = j = 0; c = default_filename_quote_characters[i]; i++) + { + if (c == expchar || c == nextch || c == closer) + continue; + custom_filename_quote_characters[j++] = c; + } + custom_filename_quote_characters[j] = '\0'; + rl_filename_quote_characters = custom_filename_quote_characters; + set_filename_bstab (rl_filename_quote_characters); + } +} + +/* Use characters in STRING to populate the table of characters that should + be backslash-quoted. The table will be used for sh_backslash_quote from + this file. */ +static void +set_filename_bstab (string) + const char *string; +{ + const char *s; + + memset (filename_bstab, 0, sizeof (filename_bstab)); + for (s = string; s && *s; s++) + filename_bstab[(unsigned char)*s] = 1; +} + +/* Quote a filename using double quotes, single quotes, or backslashes + depending on the value of completion_quoting_style. If we're + completing using backslashes, we need to quote some additional + characters (those that readline treats as word breaks), so we call + quote_word_break_chars on the result. This returns newly-allocated + memory. */ +static char * +bash_quote_filename (s, rtype, qcp) + char *s; + int rtype; + char *qcp; +{ + char *rtext, *mtext, *ret; + int rlen, cs; + int expchar, nextch, closer; + + rtext = (char *)NULL; + + /* If RTYPE == MULT_MATCH, it means that there is + more than one match. In this case, we do not add + the closing quote or attempt to perform tilde + expansion. If RTYPE == SINGLE_MATCH, we try + to perform tilde expansion, because single and double + quotes inhibit tilde expansion by the shell. */ + + cs = completion_quoting_style; + /* Might need to modify the default completion style based on *qcp, + since it's set to any user-provided opening quote. We also change + to single-quoting if there is no user-provided opening quote and + the word being completed contains newlines, since those are not + quoted correctly using backslashes (a backslash-newline pair is + special to the shell parser). */ + expchar = nextch = closer = 0; + if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && dircomplete_expand == 0 && + (expchar = bash_check_expchar (s, 0, &nextch, &closer)) && + file_exists (s) == 0) + { + /* Usually this will have been set by bash_directory_completion_hook, + but there are cases where it will not be. */ + if (rl_filename_quote_characters != custom_filename_quote_characters) + set_filename_quote_chars (expchar, nextch, closer); + complete_fullquote = 0; + } + else if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && mbschr (s, '\n')) + cs = COMPLETE_SQUOTE; + else if (*qcp == '"') + cs = COMPLETE_DQUOTE; + else if (*qcp == '\'') + cs = COMPLETE_SQUOTE; +#if defined (BANG_HISTORY) + else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE && + history_expansion_inhibited == 0 && mbschr (s, '!')) + cs = COMPLETE_BSQUOTE; + + if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE && + history_expansion_inhibited == 0 && mbschr (s, '!')) + { + cs = COMPLETE_BSQUOTE; + *qcp = '\0'; + } +#endif + + /* Don't tilde-expand backslash-quoted filenames, since only single and + double quotes inhibit tilde expansion. */ + mtext = s; + if (mtext[0] == '~' && rtype == SINGLE_MATCH && cs != COMPLETE_BSQUOTE) + mtext = bash_tilde_expand (s, 0); + + switch (cs) + { + case COMPLETE_DQUOTE: + rtext = sh_double_quote (mtext); + break; + case COMPLETE_SQUOTE: + rtext = sh_single_quote (mtext); + break; + case COMPLETE_BSQUOTE: + rtext = sh_backslash_quote (mtext, complete_fullquote ? 0 : filename_bstab, 0); + break; + } + + if (mtext != s) + free (mtext); + + /* We may need to quote additional characters: those that readline treats + as word breaks that are not quoted by backslash_quote. */ + /* XXX - test complete_fullquote here? */ + if (rtext && cs == COMPLETE_BSQUOTE && rl_completer_word_break_characters) + { + mtext = quote_word_break_chars (rtext); + free (rtext); + rtext = mtext; + } + + /* Leave the opening quote intact. The readline completion code takes + care of avoiding doubled opening quotes. */ + if (rtext) + { + rlen = strlen (rtext); + ret = (char *)xmalloc (rlen + 1); + strcpy (ret, rtext); + } + else + { + ret = (char *)xmalloc (rlen = 1); + ret[0] = '\0'; + } + + /* If there are multiple matches, cut off the closing quote. */ + if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE) + ret[rlen - 1] = '\0'; + free (rtext); + return ret; +} + +/* Support for binding readline key sequences to Unix commands. Each editing + mode has a separate Unix command keymap. */ + +static Keymap emacs_std_cmd_xmap; +#if defined (VI_MODE) +static Keymap vi_insert_cmd_xmap; +static Keymap vi_movement_cmd_xmap; +#endif + +#ifdef _MINIX +static void +#else +static int +#endif +putx(c) + int c; +{ + int x; + x = putc (c, rl_outstream); +#ifndef _MINIX + return x; +#endif +} + +static int +readline_get_char_offset (ind) + int ind; +{ + int r, old_ch; + + r = ind; +#if defined (HANDLE_MULTIBYTE) + if (locale_mb_cur_max > 1) + { + old_ch = rl_line_buffer[ind]; + rl_line_buffer[ind] = '\0'; + r = MB_STRLEN (rl_line_buffer); + rl_line_buffer[ind] = old_ch; + } +#endif + return r; +} + +static void +readline_set_char_offset (ind, varp) + int ind; + int *varp; +{ + int i; + + i = ind; + +#if defined (HANDLE_MULTIBYTE) + if (i > 0 && locale_mb_cur_max > 1) + i = _rl_find_next_mbchar (rl_line_buffer, 0, i, 0); /* XXX */ +#endif + if (i != *varp) + { + if (i > rl_end) + i = rl_end; + else if (i < 0) + i = 0; + *varp = i; + } +} + +int +bash_execute_unix_command (count, key) + int count; /* ignored */ + int key; +{ + int type; + register int i, r; + intmax_t mi; + sh_parser_state_t ps; + char *cmd, *value, *ce, old_ch; + SHELL_VAR *v; + char ibuf[INT_STRLEN_BOUND(int) + 1]; + Keymap cmd_xmap; + const char *kseq; + size_t kslen; + + kseq = rl_executing_keyseq; + kslen = rl_key_sequence_length; + + /* If we have a numeric argument, chop it off the front of the key sequence */ + if (count > 1 || rl_explicit_arg) + { + i = rl_trim_arg_from_keyseq (rl_executing_keyseq, rl_key_sequence_length, rl_get_keymap ()); + if (i > 0) + { + kseq = rl_executing_keyseq + i; + kslen = rl_key_sequence_length - i; + } + } + + /* First, we need to find the right command to execute. This is tricky, + because we might have already indirected into another keymap, so we + have to walk cmd_xmap using the entire key sequence. */ + cmd_xmap = get_cmd_xmap_from_keymap (rl_get_keymap ()); + cmd = (char *)rl_function_of_keyseq_len (kseq, kslen, cmd_xmap, &type); + + if (type == ISKMAP && (type = ((Keymap) cmd)[ANYOTHERKEY].type) == ISMACR) + cmd = (char*)((Keymap) cmd)[ANYOTHERKEY].function; + + if (cmd == 0 || type != ISMACR) + { + rl_crlf (); + internal_error (_("bash_execute_unix_command: cannot find keymap for command")); + rl_forced_update_display (); + return 1; + } + + ce = rl_get_termcap ("ce"); + if (ce) /* clear current line */ + { + rl_clear_visible_line (); + fflush (rl_outstream); + } + else + rl_crlf (); /* move to a new line */ + + v = bind_variable ("READLINE_LINE", rl_line_buffer, 0); + if (v) + VSETATTR (v, att_exported); + + i = readline_get_char_offset (rl_point); + value = inttostr (i, ibuf, sizeof (ibuf)); + v = bind_int_variable ("READLINE_POINT", value, 0); + if (v) + VSETATTR (v, att_exported); + + i = readline_get_char_offset (rl_mark); + value = inttostr (i, ibuf, sizeof (ibuf)); + v = bind_int_variable ("READLINE_MARK", value, 0); + if (v) + VSETATTR (v, att_exported); + + if (count > 1 || rl_explicit_arg) + { + value = inttostr (count, ibuf, sizeof (ibuf)); + v = bind_int_variable ("READLINE_ARGUMENT", value, 0); + if (v) + VSETATTR (v, att_exported); + } + array_needs_making = 1; + + save_parser_state (&ps); + rl_clear_signals (); + r = parse_and_execute (savestring (cmd), "bash_execute_unix_command", SEVAL_NOHIST); + rl_set_signals (); + restore_parser_state (&ps); + + v = find_variable ("READLINE_LINE"); + maybe_make_readline_line (v ? value_cell (v) : 0); + + v = find_variable ("READLINE_POINT"); + if (v && legal_number (value_cell (v), &mi)) + readline_set_char_offset (mi, &rl_point); + + v = find_variable ("READLINE_MARK"); + if (v && legal_number (value_cell (v), &mi)) + readline_set_char_offset (mi, &rl_mark); + + check_unbind_variable ("READLINE_LINE"); + check_unbind_variable ("READLINE_POINT"); + check_unbind_variable ("READLINE_MARK"); + check_unbind_variable ("READLINE_ARGUMENT"); + array_needs_making = 1; + + /* and restore the readline buffer and display after command execution. */ + /* If we clear the last line of the prompt above, redraw only that last + line. If the command returns 124, we redraw unconditionally as in + previous versions. */ + if (ce && r != 124) + rl_redraw_prompt_last_line (); + else + rl_forced_update_display (); + + return 0; +} + +int +print_unix_command_map () +{ + Keymap save, cmd_xmap; + + save = rl_get_keymap (); + cmd_xmap = get_cmd_xmap_from_keymap (save); + rl_set_keymap (cmd_xmap); + rl_macro_dumper (1); + rl_set_keymap (save); + return 0; +} + +static void +init_unix_command_map () +{ + emacs_std_cmd_xmap = rl_make_bare_keymap (); + + emacs_std_cmd_xmap[CTRL('X')].type = ISKMAP; + emacs_std_cmd_xmap[CTRL('X')].function = KEYMAP_TO_FUNCTION (rl_make_bare_keymap ()); + emacs_std_cmd_xmap[ESC].type = ISKMAP; + emacs_std_cmd_xmap[ESC].function = KEYMAP_TO_FUNCTION (rl_make_bare_keymap ()); + +#if defined (VI_MODE) + vi_insert_cmd_xmap = rl_make_bare_keymap (); + vi_movement_cmd_xmap = rl_make_bare_keymap (); +#endif +} + +static Keymap +get_cmd_xmap_from_edit_mode () +{ + if (emacs_std_cmd_xmap == 0) + init_unix_command_map (); + + switch (rl_editing_mode) + { + case EMACS_EDITING_MODE: + return emacs_std_cmd_xmap; +#if defined (VI_MODE) + case VI_EDITING_MODE: + return (get_cmd_xmap_from_keymap (rl_get_keymap ())); +#endif + default: + return (Keymap)NULL; + } +} + +static Keymap +get_cmd_xmap_from_keymap (kmap) + Keymap kmap; +{ + if (emacs_std_cmd_xmap == 0) + init_unix_command_map (); + + if (kmap == emacs_standard_keymap) + return emacs_std_cmd_xmap; + else if (kmap == emacs_meta_keymap) + return (FUNCTION_TO_KEYMAP (emacs_std_cmd_xmap, ESC)); + else if (kmap == emacs_ctlx_keymap) + return (FUNCTION_TO_KEYMAP (emacs_std_cmd_xmap, CTRL('X'))); +#if defined (VI_MODE) + else if (kmap == vi_insertion_keymap) + return vi_insert_cmd_xmap; + else if (kmap == vi_movement_keymap) + return vi_movement_cmd_xmap; +#endif + else + return (Keymap)NULL; +} + +static int +isolate_sequence (string, ind, need_dquote, startp) + char *string; + int ind, need_dquote, *startp; +{ + register int i; + int c, passc, delim; + + for (i = ind; string[i] && whitespace (string[i]); i++) + ; + /* NEED_DQUOTE means that the first non-white character *must* be `"'. */ + if (need_dquote && string[i] != '"') + { + builtin_error (_("%s: first non-whitespace character is not `\"'"), string); + return -1; + } + + /* We can have delimited strings even if NEED_DQUOTE == 0, like the command + string to bind the key sequence to. */ + delim = (string[i] == '"' || string[i] == '\'') ? string[i] : 0; + + if (startp) + *startp = delim ? ++i : i; + + for (passc = 0; c = string[i]; i++) + { + if (passc) + { + passc = 0; + continue; + } + if (c == '\\') + { + passc++; + continue; + } + if (c == delim) + break; + } + + if (delim && string[i] != delim) + { + builtin_error (_("no closing `%c' in %s"), delim, string); + return -1; + } + + return i; +} + +int +bind_keyseq_to_unix_command (line) + char *line; +{ + Keymap kmap, cmd_xmap; + char *kseq, *value; + int i, kstart; + + kmap = rl_get_keymap (); + + /* We duplicate some of the work done by rl_parse_and_bind here, but + this code only has to handle `"keyseq": ["]command["]' and can + generate an error for anything else. */ + i = isolate_sequence (line, 0, 1, &kstart); + if (i < 0) + return -1; + + /* Create the key sequence string to pass to rl_generic_bind */ + kseq = substring (line, kstart, i); + + for ( ; line[i] && line[i] != ':'; i++) + ; + if (line[i] != ':') + { + builtin_error (_("%s: missing colon separator"), line); + FREE (kseq); + return -1; + } + + i = isolate_sequence (line, i + 1, 0, &kstart); + if (i < 0) + { + FREE (kseq); + return -1; + } + + /* Create the value string containing the command to execute. */ + value = substring (line, kstart, i); + + /* Save the command to execute and the key sequence in the CMD_XMAP */ + cmd_xmap = get_cmd_xmap_from_keymap (kmap); + rl_generic_bind (ISMACR, kseq, value, cmd_xmap); + + /* and bind the key sequence in the current keymap to a function that + understands how to execute from CMD_XMAP */ + rl_bind_keyseq_in_map (kseq, bash_execute_unix_command, kmap); + + free (kseq); + return 0; +} + +int +unbind_unix_command (kseq) + char *kseq; +{ + Keymap cmd_xmap; + + cmd_xmap = get_cmd_xmap_from_keymap (rl_get_keymap ()); + if (rl_bind_keyseq_in_map (kseq, (rl_command_func_t *)NULL, cmd_xmap) != 0) + { + builtin_error (_("`%s': cannot unbind in command keymap"), kseq); + return 0; + } + return 1; +} + +/* Used by the programmable completion code. Complete TEXT as a filename, + but return only directories as matches. Dequotes the filename before + attempting to find matches. */ +char ** +bash_directory_completion_matches (text) + const char *text; +{ + char **m1; + char *dfn; + int qc; + + qc = rl_dispatching ? rl_completion_quote_character : 0; + /* If rl_completion_found_quote != 0, rl_completion_matches will call the + filename dequoting function, causing the directory name to be dequoted + twice. */ + if (rl_dispatching && rl_completion_found_quote == 0) + dfn = bash_dequote_filename ((char *)text, qc); + else + dfn = (char *)text; + m1 = rl_completion_matches (dfn, rl_filename_completion_function); + if (dfn != text) + free (dfn); + + if (m1 == 0 || m1[0] == 0) + return m1; + /* We don't bother recomputing the lcd of the matches, because it will just + get thrown away by the programmable completion code and recomputed + later. */ + (void)bash_progcomp_ignore_filenames (m1); + return m1; +} + +char * +bash_dequote_text (text) + const char *text; +{ + char *dtxt; + int qc; + + qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0; + dtxt = bash_dequote_filename ((char *)text, qc); + return (dtxt); +} + +/* This event hook is designed to be called after readline receives a signal + that interrupts read(2). It gives reasonable responsiveness to interrupts + and fatal signals without executing too much code in a signal handler + context. */ +static int +bash_event_hook () +{ + int sig; + + /* XXX - see if we need to do anything here if sigterm_received == 1, + we probably don't want to reset the event hook since we will not be + jumping to the top level */ + if (sigterm_received) + { + /* RESET_SIGTERM; */ + return 0; + } + + sig = 0; + if (terminating_signal) + sig = terminating_signal; + else if (interrupt_state) + sig = SIGINT; + else if (read_timeout && read_timeout->alrmflag) + sig = SIGALRM; + else if (RL_ISSTATE (RL_STATE_TIMEOUT)) /* just in case */ + { + sig = SIGALRM; + if (read_timeout) + read_timeout->alrmflag = 1; + } + else + sig = first_pending_trap (); + + /* If we're going to longjmp to top_level, make sure we clean up readline. + check_signals will call QUIT, which will eventually longjmp to top_level, + calling run_interrupt_trap along the way. The check against read_timeout + is so we can clean up the read builtin's state. */ + if (terminating_signal || interrupt_state || (read_timeout && read_timeout->alrmflag)) + rl_cleanup_after_signal (); + bashline_reset_event_hook (); + + RL_UNSETSTATE (RL_STATE_TIMEOUT); /* XXX */ + + /* posix mode SIGINT during read -e. We only get here if SIGINT is trapped. */ + if (posixly_correct && this_shell_builtin == read_builtin && sig == SIGINT) + { + last_command_exit_value = 128|SIGINT; + throw_to_top_level (); + } + + check_signals_and_traps (); /* XXX */ + return 0; +} + +#endif /* READLINE */ diff --git a/third_party/bash/bashline.h b/third_party/bash/bashline.h new file mode 100644 index 000000000..d40228e29 --- /dev/null +++ b/third_party/bash/bashline.h @@ -0,0 +1,69 @@ +/* bashline.h -- interface to the bash readline functions in bashline.c. */ + +/* Copyright (C) 1993-2019 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_BASHLINE_H_) +#define _BASHLINE_H_ + +#include "stdc.h" + +extern int bash_readline_initialized; +extern int hostname_list_initialized; + +/* these are controlled via shopt */ +extern int perform_hostname_completion; +extern int no_empty_command_completion; +extern int force_fignore; +extern int dircomplete_spelling; +extern int dircomplete_expand; +extern int dircomplete_expand_relpath; +extern int complete_fullquote; + +extern void posix_readline_initialize PARAMS((int)); +extern void reset_completer_word_break_chars PARAMS((void)); +extern int enable_hostname_completion PARAMS((int)); +extern void initialize_readline PARAMS((void)); +extern void bashline_reset PARAMS((void)); +extern void bashline_reinitialize PARAMS((void)); +extern int bash_re_edit PARAMS((char *)); + +extern void bashline_set_event_hook PARAMS((void)); +extern void bashline_reset_event_hook PARAMS((void)); + +extern int bind_keyseq_to_unix_command PARAMS((char *)); +extern int bash_execute_unix_command PARAMS((int, int)); +extern int print_unix_command_map PARAMS((void)); +extern int unbind_unix_command PARAMS((char *)); + +extern char **bash_default_completion PARAMS((const char *, int, int, int, int)); + +extern void set_directory_hook PARAMS((void)); + +/* Used by programmable completion code. */ +extern char *command_word_completion_function PARAMS((const char *, int)); +extern char *bash_groupname_completion_function PARAMS((const char *, int)); +extern char *bash_servicename_completion_function PARAMS((const char *, int)); + +extern char **get_hostname_list PARAMS((void)); +extern void clear_hostname_list PARAMS((void)); + +extern char **bash_directory_completion_matches PARAMS((const char *)); +extern char *bash_dequote_text PARAMS((const char *)); + +#endif /* _BASHLINE_H_ */ diff --git a/third_party/bash/bashtypes.h b/third_party/bash/bashtypes.h new file mode 100644 index 000000000..01afef4b4 --- /dev/null +++ b/third_party/bash/bashtypes.h @@ -0,0 +1,42 @@ +/* bashtypes.h -- Bash system types. */ + +/* Copyright (C) 1993-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_BASHTYPES_H_) +# define _BASHTYPES_H_ + +#if defined (CRAY) +# define word __word +#endif + +#include + +#if defined (CRAY) +# undef word +#endif + +#if defined (HAVE_INTTYPES_H) +# include +#endif + +#if HAVE_STDINT_H +# include +#endif + +#endif /* _BASHTYPES_H_ */ diff --git a/third_party/bash/bracecomp.c b/third_party/bash/bracecomp.c new file mode 100644 index 000000000..1e67640e6 --- /dev/null +++ b/third_party/bash/bracecomp.c @@ -0,0 +1,221 @@ +/* bracecomp.c -- Complete a filename with the possible completions enclosed + in csh-style braces such that the list of completions is available to the + shell. */ + +/* Original version by tromey@cns.caltech.edu, Fri Feb 7 1992. */ + +/* Copyright (C) 1993-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (BRACE_EXPANSION) && defined (READLINE) + +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" +#include "shmbutil.h" + +#include "shell.h" +#include "third_party/readline/readline.h" + +static int _strcompare PARAMS((char **, char **)); + +/* Find greatest common prefix of two strings. */ +static int +string_gcd (s1, s2) + char *s1, *s2; +{ + register int i; + + if (s1 == NULL || s2 == NULL) + return (0); + + for (i = 0; *s1 && *s2; ++s1, ++s2, ++i) + { + if (*s1 != *s2) + break; + } + + return (i); +} + +static char * +really_munge_braces (array, real_start, real_end, gcd_zero) + char **array; + int real_start, real_end, gcd_zero; +{ + int start, end, gcd; + char *result, *subterm, *x; + int result_size, flag, tlen; + + flag = 0; + + if (real_start == real_end) + { + x = array[real_start] ? sh_backslash_quote (array[real_start] + gcd_zero, 0, 0) + : sh_backslash_quote (array[0], 0, 0); + return x; + } + + result = (char *)xmalloc (result_size = 16); + *result = '\0'; + + for (start = real_start; start < real_end; start = end + 1) + { + gcd = strlen (array[start]); + for (end = start + 1; end < real_end; end++) + { + int temp; + + temp = string_gcd (array[start], array[end]); + + if (temp <= gcd_zero) + break; + + gcd = temp; + } + end--; + + if (gcd_zero == 0 && start == real_start && end != (real_end - 1)) + { + /* In this case, add in a leading '{', because we are at + top level, and there isn't a consistent prefix. */ + result_size += 1; + result = (char *)xrealloc (result, result_size); + result[0] = '{'; result[1] = '\0'; + flag++; + } + + /* Make sure we backslash quote every substring we insert into the + resultant brace expression. This is so the default filename + quoting function won't inappropriately quote the braces. */ + if (start == end) + { + x = savestring (array[start] + gcd_zero); + subterm = sh_backslash_quote (x, 0, 0); + free (x); + } + else + { + /* If there is more than one element in the subarray, + insert the (quoted) prefix and an opening brace. */ + tlen = gcd - gcd_zero; + x = (char *)xmalloc (tlen + 1); + strncpy (x, array[start] + gcd_zero, tlen); + x[tlen] = '\0'; + subterm = sh_backslash_quote (x, 0, 0); + free (x); + result_size += strlen (subterm) + 1; + result = (char *)xrealloc (result, result_size); + strcat (result, subterm); + free (subterm); + strcat (result, "{"); + subterm = really_munge_braces (array, start, end + 1, gcd); + subterm[strlen (subterm) - 1] = '}'; + } + + result_size += strlen (subterm) + 1; + result = (char *)xrealloc (result, result_size); + strcat (result, subterm); + strcat (result, ","); + free (subterm); + } + + if (gcd_zero == 0) + result[strlen (result) - 1] = flag ? '}' : '\0'; + return (result); +} + +static int +_strcompare (s1, s2) + char **s1, **s2; +{ + int result; + + result = **s1 - **s2; + if (result == 0) + result = strcmp (*s1, *s2); + + return result; +} + +static int +hack_braces_completion (names) + char **names; +{ + register int i; + char *temp; + + i = strvec_len (names); + if (MB_CUR_MAX > 1 && i > 2) + qsort (names+1, i-1, sizeof (char *), (QSFUNC *)_strcompare); + + temp = really_munge_braces (names, 1, i, 0); + + for (i = 0; names[i]; ++i) + { + free (names[i]); + names[i] = NULL; + } + names[0] = temp; + return 0; +} + +/* We handle quoting ourselves within hack_braces_completion, so we turn off + rl_filename_quoting_desired and rl_filename_quoting_function. */ +int +bash_brace_completion (count, ignore) + int count, ignore; +{ + rl_compignore_func_t *orig_ignore_func; + rl_compentry_func_t *orig_entry_func; + rl_quote_func_t *orig_quoting_func; + rl_completion_func_t *orig_attempt_func; + int orig_quoting_desired, r; + + orig_ignore_func = rl_ignore_some_completions_function; + orig_attempt_func = rl_attempted_completion_function; + orig_entry_func = rl_completion_entry_function; + orig_quoting_func = rl_filename_quoting_function; + orig_quoting_desired = rl_filename_quoting_desired; + + rl_completion_entry_function = rl_filename_completion_function; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + rl_ignore_some_completions_function = hack_braces_completion; + rl_filename_quoting_function = (rl_quote_func_t *)NULL; + rl_filename_quoting_desired = 0; + + r = rl_complete_internal (TAB); + + rl_ignore_some_completions_function = orig_ignore_func; + rl_attempted_completion_function = orig_attempt_func; + rl_completion_entry_function = orig_entry_func; + rl_filename_quoting_function = orig_quoting_func; + rl_filename_quoting_desired = orig_quoting_desired; + + return r; +} +#endif /* BRACE_EXPANSION && READLINE */ diff --git a/third_party/bash/braces.c b/third_party/bash/braces.c new file mode 100644 index 000000000..e91d326ea --- /dev/null +++ b/third_party/bash/braces.c @@ -0,0 +1,843 @@ +/* braces.c -- code for doing word expansion in curly braces. */ + +/* Copyright (C) 1987-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* Stuff in curly braces gets expanded before all other shell expansions. */ + +#include "config.h" + +#if defined (BRACE_EXPANSION) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include + +#include "bashansi.h" +#include "bashintl.h" + +#if defined (SHELL) +# include "shell.h" +#else +# if defined (TEST) +typedef char *WORD_DESC; +typedef char **WORD_LIST; +#define _(X) X +# endif /* TEST */ +#endif /* SHELL */ + +#include "typemax.h" /* INTMAX_MIN, INTMAX_MAX */ +#include "general.h" +#include "shmbutil.h" +#include "chartypes.h" + +#ifndef errno +extern int errno; +#endif + +#define brace_whitespace(c) (!(c) || (c) == ' ' || (c) == '\t' || (c) == '\n') + +#define BRACE_SEQ_SPECIFIER ".." + +extern int asprintf PARAMS((char **, const char *, ...)) __attribute__((__format__ (printf, 2, 3))); + +/* Basic idea: + + Segregate the text into 3 sections: preamble (stuff before an open brace), + postamble (stuff after the matching close brace) and amble (stuff after + preamble, and before postamble). Expand amble, and then tack on the + expansions to preamble. Expand postamble, and tack on the expansions to + the result so far. + */ + +/* The character which is used to separate arguments. */ +static const int brace_arg_separator = ','; + +#if defined (PARAMS) +static int brace_gobbler PARAMS((char *, size_t, int *, int)); +static char **expand_amble PARAMS((char *, size_t, int)); +static char **expand_seqterm PARAMS((char *, size_t)); +static char **mkseq PARAMS((intmax_t, intmax_t, intmax_t, int, int)); +static char **array_concat PARAMS((char **, char **)); +#else +static int brace_gobbler (); +static char **expand_amble (); +static char **expand_seqterm (); +static char **mkseq(); +static char **array_concat (); +#endif + +#if 0 +static void +dump_result (a) + char **a; +{ + int i; + + for (i = 0; a[i]; i++) + printf ("dump_result: a[%d] = -%s-\n", i, a[i]); +} +#endif + +/* Return an array of strings; the brace expansion of TEXT. */ +char ** +brace_expand (text) + char *text; +{ + register int start; + size_t tlen; + char *preamble, *postamble, *amble; + size_t alen; + char **tack, **result; + int i, j, c, c1; + + DECLARE_MBSTATE; + + /* Find the text of the preamble. */ + tlen = strlen (text); + i = 0; +#if defined (CSH_BRACE_COMPAT) + c = brace_gobbler (text, tlen, &i, '{'); /* } */ +#else + /* Make sure that when we exit this loop, c == 0 or text[i] begins a + valid brace expansion sequence. */ + do + { + c = brace_gobbler (text, tlen, &i, '{'); /* } */ + c1 = c; + /* Verify that c begins a valid brace expansion word. If it doesn't, we + go on. Loop stops when there are no more open braces in the word. */ + if (c) + { + start = j = i + 1; /* { */ + c = brace_gobbler (text, tlen, &j, '}'); + if (c == 0) /* it's not */ + { + i++; + c = c1; + continue; + } + else /* it is */ + { + c = c1; + break; + } + } + else + break; + } + while (c); +#endif /* !CSH_BRACE_COMPAT */ + + preamble = (char *)xmalloc (i + 1); + if (i > 0) + strncpy (preamble, text, i); + preamble[i] = '\0'; + + result = (char **)xmalloc (2 * sizeof (char *)); + result[0] = preamble; + result[1] = (char *)NULL; + + /* Special case. If we never found an exciting character, then + the preamble is all of the text, so just return that. */ + if (c != '{') + return (result); + + /* Find the amble. This is the stuff inside this set of braces. */ + start = ++i; + c = brace_gobbler (text, tlen, &i, '}'); + + /* What if there isn't a matching close brace? */ + if (c == 0) + { +#if defined (NOTDEF) + /* Well, if we found an unquoted BRACE_ARG_SEPARATOR between START + and I, then this should be an error. Otherwise, it isn't. */ + j = start; + while (j < i) + { + if (text[j] == '\\') + { + j++; + ADVANCE_CHAR (text, tlen, j); + continue; + } + + if (text[j] == brace_arg_separator) + { /* { */ + strvec_dispose (result); + set_exit_status (EXECUTION_FAILURE); + report_error ("no closing `%c' in %s", '}', text); + throw_to_top_level (); + } + ADVANCE_CHAR (text, tlen, j); + } +#endif + free (preamble); /* Same as result[0]; see initialization. */ + result[0] = savestring (text); + return (result); + } + +#if defined (SHELL) + amble = substring (text, start, i); + alen = i - start; +#else + amble = (char *)xmalloc (1 + (i - start)); + strncpy (amble, &text[start], (i - start)); + alen = i - start; + amble[alen] = '\0'; +#endif + +#if defined (SHELL) + INITIALIZE_MBSTATE; + + /* If the amble does not contain an unquoted BRACE_ARG_SEPARATOR, then + just return without doing any expansion. */ + j = 0; + while (amble[j]) + { + if (amble[j] == '\\') + { + j++; + ADVANCE_CHAR (amble, alen, j); + continue; + } + + if (amble[j] == brace_arg_separator) + break; + + ADVANCE_CHAR (amble, alen, j); + } + + if (amble[j] == 0) + { + tack = expand_seqterm (amble, alen); + if (tack) + goto add_tack; + else if (text[i + 1]) + { + /* If the sequence expansion fails (e.g., because the integers + overflow), but there is more in the string, try and process + the rest of the string, which may contain additional brace + expansions. Treat the unexpanded sequence term as a simple + string (including the braces). */ + tack = strvec_create (2); + tack[0] = savestring (text+start-1); + tack[0][i-start+2] = '\0'; + tack[1] = (char *)0; + goto add_tack; + } + else + { + free (amble); + free (preamble); + result[0] = savestring (text); + return (result); + } + } +#endif /* SHELL */ + + tack = expand_amble (amble, alen, 0); +add_tack: + result = array_concat (result, tack); + free (amble); + if (tack != result) + strvec_dispose (tack); + + postamble = text + i + 1; + + if (postamble && *postamble) + { + tack = brace_expand (postamble); + result = array_concat (result, tack); + if (tack != result) + strvec_dispose (tack); + } + + return (result); +} + +/* Expand the text found inside of braces. We simply try to split the + text at BRACE_ARG_SEPARATORs into separate strings. We then brace + expand each slot which needs it, until there are no more slots which + need it. */ +static char ** +expand_amble (text, tlen, flags) + char *text; + size_t tlen; + int flags; +{ + char **result, **partial, **tresult; + char *tem; + int start, i, c; + +#if defined (SHELL) + DECLARE_MBSTATE; +#endif + + result = (char **)NULL; + + start = i = 0; + c = 1; + while (c) + { + c = brace_gobbler (text, tlen, &i, brace_arg_separator); +#if defined (SHELL) + tem = substring (text, start, i); +#else + tem = (char *)xmalloc (1 + (i - start)); + strncpy (tem, &text[start], (i - start)); + tem[i - start] = '\0'; +#endif + + partial = brace_expand (tem); + + if (!result) + result = partial; + else + { + register int lr, lp, j; + + lr = strvec_len (result); + lp = strvec_len (partial); + + tresult = strvec_mresize (result, lp + lr + 1); + if (tresult == 0) + { + internal_error (_("brace expansion: cannot allocate memory for %s"), tem); + free (tem); + strvec_dispose (partial); + strvec_dispose (result); + result = (char **)NULL; + return result; + } + else + result = tresult; + + for (j = 0; j < lp; j++) + result[lr + j] = partial[j]; + + result[lr + j] = (char *)NULL; + free (partial); + } + free (tem); +#if defined (SHELL) + ADVANCE_CHAR (text, tlen, i); +#else + i++; +#endif + start = i; + } + return (result); +} + +#define ST_BAD 0 +#define ST_INT 1 +#define ST_CHAR 2 +#define ST_ZINT 3 + +static char ** +mkseq (start, end, incr, type, width) + intmax_t start, end, incr; + int type, width; +{ + intmax_t n, prevn; + int i, nelem; + char **result, *t; + + if (incr == 0) + incr = 1; + + if (start > end && incr > 0) + incr = -incr; + else if (start < end && incr < 0) + { + if (incr == INTMAX_MIN) /* Don't use -INTMAX_MIN */ + return ((char **)NULL); + incr = -incr; + } + + /* Check that end-start will not overflow INTMAX_MIN, INTMAX_MAX. The +3 + and -2, not strictly necessary, are there because of the way the number + of elements and value passed to strvec_create() are calculated below. */ + if (SUBOVERFLOW (end, start, INTMAX_MIN+3, INTMAX_MAX-2)) + return ((char **)NULL); + + prevn = sh_imaxabs (end - start); + /* Need to check this way in case INT_MAX == INTMAX_MAX */ + if (INT_MAX == INTMAX_MAX && (ADDOVERFLOW (prevn, 2, INT_MIN, INT_MAX))) + return ((char **)NULL); + /* Make sure the assignment to nelem below doesn't end up <= 0 due to + intmax_t overflow */ + else if (ADDOVERFLOW ((prevn/sh_imaxabs(incr)), 1, INTMAX_MIN, INTMAX_MAX)) + return ((char **)NULL); + + /* XXX - TOFIX: potentially allocating a lot of extra memory if + imaxabs(incr) != 1 */ + /* Instead of a simple nelem = prevn + 1, something like: + nelem = (prevn / imaxabs(incr)) + 1; + would work */ + if ((prevn / sh_imaxabs (incr)) > INT_MAX - 3) /* check int overflow */ + return ((char **)NULL); + nelem = (prevn / sh_imaxabs(incr)) + 1; + result = strvec_mcreate (nelem + 1); + if (result == 0) + { + internal_error (_("brace expansion: failed to allocate memory for %u elements"), (unsigned int)nelem); + return ((char **)NULL); + } + + /* Make sure we go through the loop at least once, so {3..3} prints `3' */ + i = 0; + n = start; + do + { +#if defined (SHELL) + if (ISINTERRUPT) + { + result[i] = (char *)NULL; + strvec_dispose (result); + result = (char **)NULL; + } + QUIT; +#endif + if (type == ST_INT) + result[i++] = t = itos (n); + else if (type == ST_ZINT) + { + int len, arg; + arg = n; + len = asprintf (&t, "%0*d", width, arg); + result[i++] = t; + } + else + { + if (t = (char *)malloc (2)) + { + t[0] = n; + t[1] = '\0'; + } + result[i++] = t; + } + + /* We failed to allocate memory for this number, so we bail. */ + if (t == 0) + { + char *p, lbuf[INT_STRLEN_BOUND(intmax_t) + 1]; + + /* Easier to do this than mess around with various intmax_t printf + formats (%ld? %lld? %jd?) and PRIdMAX. */ + p = inttostr (n, lbuf, sizeof (lbuf)); + internal_error (_("brace expansion: failed to allocate memory for `%s'"), p); + strvec_dispose (result); + return ((char **)NULL); + } + + /* Handle overflow and underflow of n+incr */ + if (ADDOVERFLOW (n, incr, INTMAX_MIN, INTMAX_MAX)) + break; + + n += incr; + + if ((incr < 0 && n < end) || (incr > 0 && n > end)) + break; + } + while (1); + + result[i] = (char *)0; + return (result); +} + +static char ** +expand_seqterm (text, tlen) + char *text; + size_t tlen; +{ + char *t, *lhs, *rhs; + int lhs_t, rhs_t, lhs_l, rhs_l, width; + intmax_t lhs_v, rhs_v, incr; + intmax_t tl, tr; + char **result, *ep, *oep; + + t = strstr (text, BRACE_SEQ_SPECIFIER); + if (t == 0) + return ((char **)NULL); + + lhs_l = t - text; /* index of start of BRACE_SEQ_SPECIFIER */ + lhs = substring (text, 0, lhs_l); + rhs = substring (text, lhs_l + sizeof(BRACE_SEQ_SPECIFIER) - 1, tlen); + + if (lhs[0] == 0 || rhs[0] == 0) + { + free (lhs); + free (rhs); + return ((char **)NULL); + } + + /* Now figure out whether LHS and RHS are integers or letters. Both + sides have to match. */ + lhs_t = (legal_number (lhs, &tl)) ? ST_INT : + ((ISALPHA (lhs[0]) && lhs[1] == 0) ? ST_CHAR : ST_BAD); + + /* Decide on rhs and whether or not it looks like the user specified + an increment */ + ep = 0; + if (ISDIGIT (rhs[0]) || ((rhs[0] == '+' || rhs[0] == '-') && ISDIGIT (rhs[1]))) + { + rhs_t = ST_INT; + errno = 0; + tr = strtoimax (rhs, &ep, 10); + if (errno == ERANGE || (ep && *ep != 0 && *ep != '.')) + rhs_t = ST_BAD; /* invalid */ + } + else if (ISALPHA (rhs[0]) && (rhs[1] == 0 || rhs[1] == '.')) + { + rhs_t = ST_CHAR; + ep = rhs + 1; + } + else + { + rhs_t = ST_BAD; + ep = 0; + } + + incr = 1; + if (rhs_t != ST_BAD) + { + oep = ep; + errno = 0; + if (ep && *ep == '.' && ep[1] == '.' && ep[2]) + incr = strtoimax (ep + 2, &ep, 10); + if (*ep != 0 || errno == ERANGE) + rhs_t = ST_BAD; /* invalid incr or overflow */ + tlen -= ep - oep; + } + + if (lhs_t != rhs_t || lhs_t == ST_BAD || rhs_t == ST_BAD) + { + free (lhs); + free (rhs); + return ((char **)NULL); + } + + /* OK, we have something. It's either a sequence of integers, ascending + or descending, or a sequence or letters, ditto. Generate the sequence, + put it into a string vector, and return it. */ + + if (lhs_t == ST_CHAR) + { + lhs_v = (unsigned char)lhs[0]; + rhs_v = (unsigned char)rhs[0]; + width = 1; + } + else + { + lhs_v = tl; /* integer truncation */ + rhs_v = tr; + + /* Decide whether or not the terms need zero-padding */ + rhs_l = tlen - lhs_l - sizeof (BRACE_SEQ_SPECIFIER) + 1; + width = 0; + if (lhs_l > 1 && lhs[0] == '0') + width = lhs_l, lhs_t = ST_ZINT; + if (lhs_l > 2 && lhs[0] == '-' && lhs[1] == '0') + width = lhs_l, lhs_t = ST_ZINT; + if (rhs_l > 1 && rhs[0] == '0' && width < rhs_l) + width = rhs_l, lhs_t = ST_ZINT; + if (rhs_l > 2 && rhs[0] == '-' && rhs[1] == '0' && width < rhs_l) + width = rhs_l, lhs_t = ST_ZINT; + + if (width < lhs_l && lhs_t == ST_ZINT) + width = lhs_l; + if (width < rhs_l && lhs_t == ST_ZINT) + width = rhs_l; + } + + result = mkseq (lhs_v, rhs_v, incr, lhs_t, width); + + free (lhs); + free (rhs); + + return (result); +} + +/* Start at INDEX, and skip characters in TEXT. Set INDEX to the + index of the character matching SATISFY. This understands about + quoting. Return the character that caused us to stop searching; + this is either the same as SATISFY, or 0. */ +/* If SATISFY is `}', we are looking for a brace expression, so we + should enforce the rules that govern valid brace expansions: + 1) to count as an arg separator, a comma or `..' has to be outside + an inner set of braces. +*/ +static int +brace_gobbler (text, tlen, indx, satisfy) + char *text; + size_t tlen; + int *indx; + int satisfy; +{ + register int i, c, quoted, level, commas, pass_next; +#if defined (SHELL) + int si; + char *t; +#endif + DECLARE_MBSTATE; + + level = quoted = pass_next = 0; +#if defined (CSH_BRACE_COMPAT) + commas = 1; +#else + commas = (satisfy == '}') ? 0 : 1; +#endif + + i = *indx; + while (c = text[i]) + { + if (pass_next) + { + pass_next = 0; +#if defined (SHELL) + ADVANCE_CHAR (text, tlen, i); +#else + i++; +#endif + continue; + } + + /* A backslash escapes the next character. This allows backslash to + escape the quote character in a double-quoted string. */ + if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`')) + { + pass_next = 1; + i++; + continue; + } + +#if defined (SHELL) + /* If compiling for the shell, treat ${...} like \{...} */ + if (c == '$' && text[i+1] == '{' && quoted != '\'') /* } */ + { + pass_next = 1; + i++; + if (quoted == 0) + level++; + continue; + } +#endif + + if (quoted) + { + if (c == quoted) + quoted = 0; +#if defined (SHELL) + /* The shell allows quoted command substitutions */ + if (quoted == '"' && c == '$' && text[i+1] == '(') /*)*/ + goto comsub; +#endif +#if defined (SHELL) + ADVANCE_CHAR (text, tlen, i); +#else + i++; +#endif + continue; + } + + if (c == '"' || c == '\'' || c == '`') + { + quoted = c; + i++; + continue; + } + +#if defined (SHELL) + /* Pass new-style command and process substitutions through unchanged. */ + if ((c == '$' || c == '<' || c == '>') && text[i+1] == '(') /* ) */ + { +comsub: + si = i + 2; + t = extract_command_subst (text, &si, 0); + i = si; + free (t); + i++; + continue; + } +#endif + + if (c == satisfy && level == 0 && quoted == 0 && commas > 0) + { + /* We ignore an open brace surrounded by whitespace, and also + an open brace followed immediately by a close brace preceded + by whitespace. */ + if (c == '{' && + ((!i || brace_whitespace (text[i - 1])) && + (brace_whitespace (text[i + 1]) || text[i + 1] == '}'))) + { + i++; + continue; + } + + break; + } + + if (c == '{') + level++; + else if (c == '}' && level) + level--; +#if !defined (CSH_BRACE_COMPAT) + else if (satisfy == '}' && c == brace_arg_separator && level == 0) + commas++; + else if (satisfy == '}' && STREQN (text+i, BRACE_SEQ_SPECIFIER, 2) && + text[i+2] != satisfy && level == 0) + commas++; +#endif + +#if defined (SHELL) + ADVANCE_CHAR (text, tlen, i); +#else + i++; +#endif + } + + *indx = i; + return (c); +} + +/* Return a new array of strings which is the result of appending each + string in ARR2 to each string in ARR1. The resultant array is + len (arr1) * len (arr2) long. For convenience, ARR1 (and its contents) + are free ()'ed. ARR1 can be NULL, in that case, a new version of ARR2 + is returned. */ +static char ** +array_concat (arr1, arr2) + char **arr1, **arr2; +{ + register int i, j, len, len1, len2; + register char **result; + + if (arr1 == 0) + return (arr2); /* XXX - see if we can get away without copying? */ + + if (arr2 == 0) + return (arr1); /* XXX - caller expects us to free arr1 */ + + /* We can only short-circuit if the array consists of a single null element; + otherwise we need to replicate the contents of the other array and + prefix (or append, below) an empty element to each one. */ + if (arr1[0] && arr1[0][0] == 0 && arr1[1] == 0) + { + strvec_dispose (arr1); + return (arr2); /* XXX - use flags to see if we can avoid copying here */ + } + + if (arr2[0] && arr2[0][0] == 0 && arr2[1] == 0) + return (arr1); /* XXX - rather than copying and freeing it */ + + len1 = strvec_len (arr1); + len2 = strvec_len (arr2); + + result = (char **)malloc ((1 + (len1 * len2)) * sizeof (char *)); + if (result == 0) + return (result); + + len = 0; + for (i = 0; i < len1; i++) + { + int strlen_1 = strlen (arr1[i]); + + for (j = 0; j < len2; j++) + { + result[len] = (char *)xmalloc (1 + strlen_1 + strlen (arr2[j])); + strcpy (result[len], arr1[i]); + strcpy (result[len] + strlen_1, arr2[j]); + len++; + } + free (arr1[i]); + } + free (arr1); + + result[len] = (char *)NULL; + return (result); +} + +#if defined (TEST) +#include + +void * +xmalloc(n) + size_t n; +{ + return (malloc (n)); +} + +void * +xrealloc(p, n) + void *p; + size_t n; +{ + return (realloc (p, n)); +} + +int +internal_error (format, arg1, arg2) + char *format, *arg1, *arg2; +{ + fprintf (stderr, format, arg1, arg2); + fprintf (stderr, "\n"); +} + +main () +{ + char example[256]; + + for (;;) + { + char **result; + int i; + + fprintf (stderr, "brace_expand> "); + + if ((!fgets (example, 256, stdin)) || + (strncmp (example, "quit", 4) == 0)) + break; + + if (strlen (example)) + example[strlen (example) - 1] = '\0'; + + result = brace_expand (example); + + for (i = 0; result[i]; i++) + printf ("%s\n", result[i]); + + strvec_dispose (result); + } +} + +/* + * Local variables: + * compile-command: "gcc -g -Bstatic -DTEST -o brace_expand braces.c general.o" + * end: + */ + +#endif /* TEST */ +#endif /* BRACE_EXPANSION */ diff --git a/third_party/bash/break.c b/third_party/bash/break.c new file mode 100644 index 000000000..827b1a556 --- /dev/null +++ b/third_party/bash/break.c @@ -0,0 +1,104 @@ +/* break.c, created from break.def. */ +#line 22 "./break.def" + +#line 34 "./break.def" +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "common.h" + +static int check_loop_level PARAMS((void)); + +/* The depth of while's and until's. */ +int loop_level = 0; + +/* Non-zero when a "break" instruction is encountered. */ +int breaking = 0; + +/* Non-zero when we have encountered a continue instruction. */ +int continuing = 0; + +/* Set up to break x levels, where x defaults to 1, but can be specified + as the first argument. */ +int +break_builtin (list) + WORD_LIST *list; +{ + intmax_t newbreak; + + CHECK_HELPOPT (list); + + if (check_loop_level () == 0) + return (EXECUTION_SUCCESS); + + (void)get_numeric_arg (list, 1, &newbreak); + + if (newbreak <= 0) + { + sh_erange (list->word->word, _("loop count")); + breaking = loop_level; + return (EXECUTION_FAILURE); + } + + if (newbreak > loop_level) + newbreak = loop_level; + + breaking = newbreak; + + return (EXECUTION_SUCCESS); +} + +#line 101 "./break.def" + +/* Set up to continue x levels, where x defaults to 1, but can be specified + as the first argument. */ +int +continue_builtin (list) + WORD_LIST *list; +{ + intmax_t newcont; + + CHECK_HELPOPT (list); + + if (check_loop_level () == 0) + return (EXECUTION_SUCCESS); + + (void)get_numeric_arg (list, 1, &newcont); + + if (newcont <= 0) + { + sh_erange (list->word->word, _("loop count")); + breaking = loop_level; + return (EXECUTION_FAILURE); + } + + if (newcont > loop_level) + newcont = loop_level; + + continuing = newcont; + + return (EXECUTION_SUCCESS); +} + +/* Return non-zero if a break or continue command would be okay. + Print an error message if break or continue is meaningless here. */ +static int +check_loop_level () +{ +#if defined (BREAK_COMPLAINS) + if (loop_level == 0 && posixly_correct == 0) + builtin_error (_("only meaningful in a `for', `while', or `until' loop")); +#endif /* BREAK_COMPLAINS */ + + return (loop_level); +} diff --git a/third_party/bash/builtext.h b/third_party/bash/builtext.h new file mode 100644 index 000000000..a7723fa10 --- /dev/null +++ b/third_party/bash/builtext.h @@ -0,0 +1,188 @@ +/* builtext.h - The list of builtins found in libbuiltins.a. */ +#if defined (ALIAS) +extern int alias_builtin PARAMS((WORD_LIST *)); +extern char * const alias_doc[]; +#endif /* ALIAS */ +#if defined (ALIAS) +extern int unalias_builtin PARAMS((WORD_LIST *)); +extern char * const unalias_doc[]; +#endif /* ALIAS */ +#if defined (READLINE) +extern int bind_builtin PARAMS((WORD_LIST *)); +extern char * const bind_doc[]; +#endif /* READLINE */ +extern int break_builtin PARAMS((WORD_LIST *)); +extern char * const break_doc[]; +extern int continue_builtin PARAMS((WORD_LIST *)); +extern char * const continue_doc[]; +extern int builtin_builtin PARAMS((WORD_LIST *)); +extern char * const builtin_doc[]; +#if defined (DEBUGGER) +extern int caller_builtin PARAMS((WORD_LIST *)); +extern char * const caller_doc[]; +#endif /* DEBUGGER */ +extern int cd_builtin PARAMS((WORD_LIST *)); +extern char * const cd_doc[]; +extern int pwd_builtin PARAMS((WORD_LIST *)); +extern char * const pwd_doc[]; +extern int colon_builtin PARAMS((WORD_LIST *)); +extern char * const colon_doc[]; +extern int colon_builtin PARAMS((WORD_LIST *)); +extern char * const true_doc[]; +extern int false_builtin PARAMS((WORD_LIST *)); +extern char * const false_doc[]; +extern int command_builtin PARAMS((WORD_LIST *)); +extern char * const command_doc[]; +extern int declare_builtin PARAMS((WORD_LIST *)); +extern char * const declare_doc[]; +extern int declare_builtin PARAMS((WORD_LIST *)); +extern char * const typeset_doc[]; +extern int local_builtin PARAMS((WORD_LIST *)); +extern char * const local_doc[]; +#if defined (V9_ECHO) +extern int echo_builtin PARAMS((WORD_LIST *)); +extern char * const echo_doc[]; +#endif /* V9_ECHO */ +#if !defined (V9_ECHO) +extern int echo_builtin PARAMS((WORD_LIST *)); +extern char * const echo_doc[]; +#endif /* !V9_ECHO */ +extern int enable_builtin PARAMS((WORD_LIST *)); +extern char * const enable_doc[]; +extern int eval_builtin PARAMS((WORD_LIST *)); +extern char * const eval_doc[]; +extern int getopts_builtin PARAMS((WORD_LIST *)); +extern char * const getopts_doc[]; +extern int exec_builtin PARAMS((WORD_LIST *)); +extern char * const exec_doc[]; +extern int exit_builtin PARAMS((WORD_LIST *)); +extern char * const exit_doc[]; +extern int logout_builtin PARAMS((WORD_LIST *)); +extern char * const logout_doc[]; +#if defined (HISTORY) +extern int fc_builtin PARAMS((WORD_LIST *)); +extern char * const fc_doc[]; +#endif /* HISTORY */ +#if defined (JOB_CONTROL) +extern int fg_builtin PARAMS((WORD_LIST *)); +extern char * const fg_doc[]; +#endif /* JOB_CONTROL */ +#if defined (JOB_CONTROL) +extern int bg_builtin PARAMS((WORD_LIST *)); +extern char * const bg_doc[]; +#endif /* JOB_CONTROL */ +extern int hash_builtin PARAMS((WORD_LIST *)); +extern char * const hash_doc[]; +#if defined (HELP_BUILTIN) +extern int help_builtin PARAMS((WORD_LIST *)); +extern char * const help_doc[]; +#endif /* HELP_BUILTIN */ +#if defined (HISTORY) +extern int history_builtin PARAMS((WORD_LIST *)); +extern char * const history_doc[]; +#endif /* HISTORY */ +#if defined (JOB_CONTROL) +extern int jobs_builtin PARAMS((WORD_LIST *)); +extern char * const jobs_doc[]; +#endif /* JOB_CONTROL */ +#if defined (JOB_CONTROL) +extern int disown_builtin PARAMS((WORD_LIST *)); +extern char * const disown_doc[]; +#endif /* JOB_CONTROL */ +extern int kill_builtin PARAMS((WORD_LIST *)); +extern char * const kill_doc[]; +extern int let_builtin PARAMS((WORD_LIST *)); +extern char * const let_doc[]; +extern int read_builtin PARAMS((WORD_LIST *)); +extern char * const read_doc[]; +extern int return_builtin PARAMS((WORD_LIST *)); +extern char * const return_doc[]; +extern int set_builtin PARAMS((WORD_LIST *)); +extern char * const set_doc[]; +extern int unset_builtin PARAMS((WORD_LIST *)); +extern char * const unset_doc[]; +extern int export_builtin PARAMS((WORD_LIST *)); +extern char * const export_doc[]; +extern int readonly_builtin PARAMS((WORD_LIST *)); +extern char * const readonly_doc[]; +extern int shift_builtin PARAMS((WORD_LIST *)); +extern char * const shift_doc[]; +extern int source_builtin PARAMS((WORD_LIST *)); +extern char * const source_doc[]; +extern int source_builtin PARAMS((WORD_LIST *)); +extern char * const dot_doc[]; +#if defined (JOB_CONTROL) +extern int suspend_builtin PARAMS((WORD_LIST *)); +extern char * const suspend_doc[]; +#endif /* JOB_CONTROL */ +extern int test_builtin PARAMS((WORD_LIST *)); +extern char * const test_doc[]; +extern int test_builtin PARAMS((WORD_LIST *)); +extern char * const test_bracket_doc[]; +extern int times_builtin PARAMS((WORD_LIST *)); +extern char * const times_doc[]; +extern int trap_builtin PARAMS((WORD_LIST *)); +extern char * const trap_doc[]; +extern int type_builtin PARAMS((WORD_LIST *)); +extern char * const type_doc[]; +#if !defined (_MINIX) +extern int ulimit_builtin PARAMS((WORD_LIST *)); +extern char * const ulimit_doc[]; +#endif /* !_MINIX */ +extern int umask_builtin PARAMS((WORD_LIST *)); +extern char * const umask_doc[]; +#if defined (JOB_CONTROL) +extern int wait_builtin PARAMS((WORD_LIST *)); +extern char * const wait_doc[]; +#endif /* JOB_CONTROL */ +#if !defined (JOB_CONTROL) +extern int wait_builtin PARAMS((WORD_LIST *)); +extern char * const wait_doc[]; +#endif /* !JOB_CONTROL */ +extern char * const for_doc[]; +extern char * const arith_for_doc[]; +extern char * const select_doc[]; +extern char * const time_doc[]; +extern char * const case_doc[]; +extern char * const if_doc[]; +extern char * const while_doc[]; +extern char * const until_doc[]; +extern char * const coproc_doc[]; +extern char * const function_doc[]; +extern char * const grouping_braces_doc[]; +extern char * const fg_percent_doc[]; +extern char * const arith_doc[]; +extern char * const conditional_doc[]; +extern char * const variable_help_doc[]; +#if defined (PUSHD_AND_POPD) +extern int pushd_builtin PARAMS((WORD_LIST *)); +extern char * const pushd_doc[]; +#endif /* PUSHD_AND_POPD */ +#if defined (PUSHD_AND_POPD) +extern int popd_builtin PARAMS((WORD_LIST *)); +extern char * const popd_doc[]; +#endif /* PUSHD_AND_POPD */ +#if defined (PUSHD_AND_POPD) +extern int dirs_builtin PARAMS((WORD_LIST *)); +extern char * const dirs_doc[]; +#endif /* PUSHD_AND_POPD */ +extern int shopt_builtin PARAMS((WORD_LIST *)); +extern char * const shopt_doc[]; +extern int printf_builtin PARAMS((WORD_LIST *)); +extern char * const printf_doc[]; +#if defined (PROGRAMMABLE_COMPLETION) +extern int complete_builtin PARAMS((WORD_LIST *)); +extern char * const complete_doc[]; +#endif /* PROGRAMMABLE_COMPLETION */ +#if defined (PROGRAMMABLE_COMPLETION) +extern int compgen_builtin PARAMS((WORD_LIST *)); +extern char * const compgen_doc[]; +#endif /* PROGRAMMABLE_COMPLETION */ +#if defined (PROGRAMMABLE_COMPLETION) +extern int compopt_builtin PARAMS((WORD_LIST *)); +extern char * const compopt_doc[]; +#endif /* PROGRAMMABLE_COMPLETION */ +extern int mapfile_builtin PARAMS((WORD_LIST *)); +extern char * const mapfile_doc[]; +extern int mapfile_builtin PARAMS((WORD_LIST *)); +extern char * const readarray_doc[]; diff --git a/third_party/bash/builtins.c b/third_party/bash/builtins.c new file mode 100644 index 000000000..3d5fffc26 --- /dev/null +++ b/third_party/bash/builtins.c @@ -0,0 +1,2093 @@ +/* builtins.c -- the built in shell commands. */ + +/* This file is manufactured by ./mkbuiltins, and should not be + edited by hand. See the source to mkbuiltins for details. */ + +/* Copyright (C) 1987-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* The list of shell builtins. Each element is name, function, flags, + long-doc, short-doc. The long-doc field contains a pointer to an array + of help lines. The function takes a WORD_LIST *; the first word in the + list is the first arg to the command. The list has already had word + expansion performed. + + Functions which need to look at only the simple commands (e.g. + the enable_builtin ()), should ignore entries where + (array[i].function == (sh_builtin_func_t *)NULL). Such entries are for + the list of shell reserved control structures, like `if' and `while'. + The end of the list is denoted with a NULL name field. */ + +/* TRANSLATORS: Please do not translate command names in descriptions */ + +#include "builtins.h" +#include "builtext.h" +#include "bashintl.h" + +struct builtin static_shell_builtins[] = { +#if defined (ALIAS) + { "alias", alias_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | ASSIGNMENT_BUILTIN | POSIX_BUILTIN, alias_doc, + N_("alias [-p] [name[=value] ... ]"), (char *)NULL }, +#endif /* ALIAS */ +#if defined (ALIAS) + { "unalias", unalias_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, unalias_doc, + N_("unalias [-a] name [name ...]"), (char *)NULL }, +#endif /* ALIAS */ +#if defined (READLINE) + { "bind", bind_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, bind_doc, + N_("bind [-lpsvPSVX] [-m keymap] [-f filename] [-q name] [-u name] [-r keyseq] [-x keyseq:shell-command] [keyseq:readline-function or readline-command]"), (char *)NULL }, +#endif /* READLINE */ + { "break", break_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, break_doc, + N_("break [n]"), (char *)NULL }, + { "continue", continue_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, continue_doc, + N_("continue [n]"), (char *)NULL }, + { "builtin", builtin_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, builtin_doc, + N_("builtin [shell-builtin [arg ...]]"), (char *)NULL }, +#if defined (DEBUGGER) + { "caller", caller_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, caller_doc, + N_("caller [expr]"), (char *)NULL }, +#endif /* DEBUGGER */ + { "cd", cd_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, cd_doc, + N_("cd [-L|[-P [-e]] [-@]] [dir]"), (char *)NULL }, + { "pwd", pwd_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, pwd_doc, + N_("pwd [-LP]"), (char *)NULL }, + { ":", colon_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, colon_doc, + ":", (char *)NULL }, + { "true", colon_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, true_doc, + "true", (char *)NULL }, + { "false", false_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, false_doc, + "false", (char *)NULL }, + { "command", command_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, command_doc, + N_("command [-pVv] command [arg ...]"), (char *)NULL }, + { "declare", declare_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | ASSIGNMENT_BUILTIN | LOCALVAR_BUILTIN | ARRAYREF_BUILTIN, declare_doc, + N_("declare [-aAfFgiIlnrtux] [name[=value] ...] or declare -p [-aAfFilnrtux] [name ...]"), (char *)NULL }, + { "typeset", declare_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | ASSIGNMENT_BUILTIN | LOCALVAR_BUILTIN | ARRAYREF_BUILTIN, typeset_doc, + N_("typeset [-aAfFgiIlnrtux] name[=value] ... or typeset -p [-aAfFilnrtux] [name ...]"), (char *)NULL }, + { "local", local_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | ASSIGNMENT_BUILTIN | LOCALVAR_BUILTIN | ARRAYREF_BUILTIN, local_doc, + N_("local [option] name[=value] ..."), (char *)NULL }, +#if defined (V9_ECHO) + { "echo", echo_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, echo_doc, + N_("echo [-neE] [arg ...]"), (char *)NULL }, +#endif /* V9_ECHO */ +#if !defined (V9_ECHO) + { "echo", echo_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, echo_doc, + N_("echo [-n] [arg ...]"), (char *)NULL }, +#endif /* !V9_ECHO */ + { "enable", enable_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, enable_doc, + N_("enable [-a] [-dnps] [-f filename] [name ...]"), (char *)NULL }, + { "eval", eval_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, eval_doc, + N_("eval [arg ...]"), (char *)NULL }, + { "getopts", getopts_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, getopts_doc, + N_("getopts optstring name [arg ...]"), (char *)NULL }, + { "exec", exec_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, exec_doc, + N_("exec [-cl] [-a name] [command [argument ...]] [redirection ...]"), (char *)NULL }, + { "exit", exit_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, exit_doc, + N_("exit [n]"), (char *)NULL }, + { "logout", logout_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, logout_doc, + N_("logout [n]"), (char *)NULL }, +#if defined (HISTORY) + { "fc", fc_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, fc_doc, + N_("fc [-e ename] [-lnr] [first] [last] or fc -s [pat=rep] [command]"), (char *)NULL }, +#endif /* HISTORY */ +#if defined (JOB_CONTROL) + { "fg", fg_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, fg_doc, + N_("fg [job_spec]"), (char *)NULL }, +#endif /* JOB_CONTROL */ +#if defined (JOB_CONTROL) + { "bg", bg_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, bg_doc, + N_("bg [job_spec ...]"), (char *)NULL }, +#endif /* JOB_CONTROL */ + { "hash", hash_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, hash_doc, + N_("hash [-lr] [-p pathname] [-dt] [name ...]"), (char *)NULL }, +#if defined (HELP_BUILTIN) + { "help", help_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, help_doc, + N_("help [-dms] [pattern ...]"), (char *)NULL }, +#endif /* HELP_BUILTIN */ +#if defined (HISTORY) + { "history", history_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, history_doc, + N_("history [-c] [-d offset] [n] or history -anrw [filename] or history -ps arg [arg...]"), (char *)NULL }, +#endif /* HISTORY */ +#if defined (JOB_CONTROL) + { "jobs", jobs_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, jobs_doc, + N_("jobs [-lnprs] [jobspec ...] or jobs -x command [args]"), (char *)NULL }, +#endif /* JOB_CONTROL */ +#if defined (JOB_CONTROL) + { "disown", disown_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, disown_doc, + N_("disown [-h] [-ar] [jobspec ... | pid ...]"), (char *)NULL }, +#endif /* JOB_CONTROL */ + { "kill", kill_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, kill_doc, + N_("kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]"), (char *)NULL }, + { "let", let_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | ARRAYREF_BUILTIN, let_doc, + N_("let arg [arg ...]"), (char *)NULL }, + { "read", read_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN | ARRAYREF_BUILTIN, read_doc, + N_("read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]"), (char *)NULL }, + { "return", return_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, return_doc, + N_("return [n]"), (char *)NULL }, + { "set", set_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, set_doc, + N_("set [-abefhkmnptuvxBCEHPT] [-o option-name] [--] [-] [arg ...]"), (char *)NULL }, + { "unset", unset_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN | ARRAYREF_BUILTIN, unset_doc, + N_("unset [-f] [-v] [-n] [name ...]"), (char *)NULL }, + { "export", export_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN | ASSIGNMENT_BUILTIN, export_doc, + N_("export [-fn] [name[=value] ...] or export -p"), (char *)NULL }, + { "readonly", readonly_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN | ASSIGNMENT_BUILTIN, readonly_doc, + N_("readonly [-aAf] [name[=value] ...] or readonly -p"), (char *)NULL }, + { "shift", shift_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, shift_doc, + N_("shift [n]"), (char *)NULL }, + { "source", source_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, source_doc, + N_("source filename [arguments]"), (char *)NULL }, + { ".", source_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, dot_doc, + N_(". filename [arguments]"), (char *)NULL }, +#if defined (JOB_CONTROL) + { "suspend", suspend_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, suspend_doc, + N_("suspend [-f]"), (char *)NULL }, +#endif /* JOB_CONTROL */ + { "test", test_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | ARRAYREF_BUILTIN, test_doc, + N_("test [expr]"), (char *)NULL }, + { "[", test_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | ARRAYREF_BUILTIN, test_bracket_doc, + N_("[ arg... ]"), (char *)NULL }, + { "times", times_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, times_doc, + "times", (char *)NULL }, + { "trap", trap_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | SPECIAL_BUILTIN, trap_doc, + N_("trap [-lp] [[arg] signal_spec ...]"), (char *)NULL }, + { "type", type_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, type_doc, + N_("type [-afptP] name [name ...]"), (char *)NULL }, +#if !defined (_MINIX) + { "ulimit", ulimit_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, ulimit_doc, + N_("ulimit [-SHabcdefiklmnpqrstuvxPRT] [limit]"), (char *)NULL }, +#endif /* !_MINIX */ + { "umask", umask_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN, umask_doc, + N_("umask [-p] [-S] [mode]"), (char *)NULL }, +#if defined (JOB_CONTROL) + { "wait", wait_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN | ARRAYREF_BUILTIN, wait_doc, + N_("wait [-fn] [-p var] [id ...]"), (char *)NULL }, +#endif /* JOB_CONTROL */ +#if !defined (JOB_CONTROL) + { "wait", wait_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | POSIX_BUILTIN | ARRAYREF_BUILTIN, wait_doc, + N_("wait [pid ...]"), (char *)NULL }, +#endif /* !JOB_CONTROL */ + { "for", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, for_doc, + N_("for NAME [in WORDS ... ] ; do COMMANDS; done"), (char *)NULL }, + { "for ((", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, arith_for_doc, + N_("for (( exp1; exp2; exp3 )); do COMMANDS; done"), (char *)NULL }, + { "select", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, select_doc, + N_("select NAME [in WORDS ... ;] do COMMANDS; done"), (char *)NULL }, + { "time", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, time_doc, + N_("time [-p] pipeline"), (char *)NULL }, + { "case", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, case_doc, + N_("case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac"), (char *)NULL }, + { "if", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, if_doc, + N_("if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi"), (char *)NULL }, + { "while", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, while_doc, + N_("while COMMANDS; do COMMANDS-2; done"), (char *)NULL }, + { "until", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, until_doc, + N_("until COMMANDS; do COMMANDS-2; done"), (char *)NULL }, + { "coproc", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, coproc_doc, + N_("coproc [NAME] command [redirections]"), (char *)NULL }, + { "function", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, function_doc, + N_("function name { COMMANDS ; } or name () { COMMANDS ; }"), (char *)NULL }, + { "{ ... }", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, grouping_braces_doc, + N_("{ COMMANDS ; }"), (char *)NULL }, + { "%", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, fg_percent_doc, + N_("job_spec [&]"), (char *)NULL }, + { "(( ... ))", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, arith_doc, + N_("(( expression ))"), (char *)NULL }, + { "[[ ... ]]", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, conditional_doc, + N_("[[ expression ]]"), (char *)NULL }, + { "variables", (sh_builtin_func_t *)0x0, BUILTIN_ENABLED | STATIC_BUILTIN, variable_help_doc, + N_("variables - Names and meanings of some shell variables"), (char *)NULL }, +#if defined (PUSHD_AND_POPD) + { "pushd", pushd_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, pushd_doc, + N_("pushd [-n] [+N | -N | dir]"), (char *)NULL }, +#endif /* PUSHD_AND_POPD */ +#if defined (PUSHD_AND_POPD) + { "popd", popd_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, popd_doc, + N_("popd [-n] [+N | -N]"), (char *)NULL }, +#endif /* PUSHD_AND_POPD */ +#if defined (PUSHD_AND_POPD) + { "dirs", dirs_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, dirs_doc, + N_("dirs [-clpv] [+N] [-N]"), (char *)NULL }, +#endif /* PUSHD_AND_POPD */ + { "shopt", shopt_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, shopt_doc, + N_("shopt [-pqsu] [-o] [optname ...]"), (char *)NULL }, + { "printf", printf_builtin, BUILTIN_ENABLED | STATIC_BUILTIN | ARRAYREF_BUILTIN, printf_doc, + N_("printf [-v var] format [arguments]"), (char *)NULL }, +#if defined (PROGRAMMABLE_COMPLETION) + { "complete", complete_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, complete_doc, + N_("complete [-abcdefgjksuv] [-pr] [-DEI] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name ...]"), (char *)NULL }, +#endif /* PROGRAMMABLE_COMPLETION */ +#if defined (PROGRAMMABLE_COMPLETION) + { "compgen", compgen_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, compgen_doc, + N_("compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [word]"), (char *)NULL }, +#endif /* PROGRAMMABLE_COMPLETION */ +#if defined (PROGRAMMABLE_COMPLETION) + { "compopt", compopt_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, compopt_doc, + N_("compopt [-o|+o option] [-DEI] [name ...]"), (char *)NULL }, +#endif /* PROGRAMMABLE_COMPLETION */ + { "mapfile", mapfile_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, mapfile_doc, + N_("mapfile [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]"), (char *)NULL }, + { "readarray", mapfile_builtin, BUILTIN_ENABLED | STATIC_BUILTIN, readarray_doc, + N_("readarray [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]"), (char *)NULL }, + { (char *)0x0, (sh_builtin_func_t *)0x0, 0, (char **)0x0, (char *)0x0, (char *)0x0 } +}; + +struct builtin *shell_builtins = static_shell_builtins; +struct builtin *current_builtin; + +int num_shell_builtins = + sizeof (static_shell_builtins) / sizeof (struct builtin) - 1; +#if defined (ALIAS) +char * const alias_doc[] = { +#if defined (HELP_BUILTIN) +N_("Define or display aliases.\n\ + \n\ + Without arguments, `alias' prints the list of aliases in the reusable\n\ + form `alias NAME=VALUE' on standard output.\n\ + \n\ + Otherwise, an alias is defined for each NAME whose VALUE is given.\n\ + A trailing space in VALUE causes the next word to be checked for\n\ + alias substitution when the alias is expanded.\n\ + \n\ + Options:\n\ + -p print all defined aliases in a reusable format\n\ + \n\ + Exit Status:\n\ + alias returns true unless a NAME is supplied for which no alias has been\n\ + defined."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* ALIAS */ +#if defined (ALIAS) +char * const unalias_doc[] = { +#if defined (HELP_BUILTIN) +N_("Remove each NAME from the list of defined aliases.\n\ + \n\ + Options:\n\ + -a remove all alias definitions\n\ + \n\ + Return success unless a NAME is not an existing alias."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* ALIAS */ +#if defined (READLINE) +char * const bind_doc[] = { +#if defined (HELP_BUILTIN) +N_("Set Readline key bindings and variables.\n\ + \n\ + Bind a key sequence to a Readline function or a macro, or set a\n\ + Readline variable. The non-option argument syntax is equivalent to\n\ + that found in ~/.inputrc, but must be passed as a single argument:\n\ + e.g., bind '\"\\C-x\\C-r\": re-read-init-file'.\n\ + \n\ + Options:\n\ + -m keymap Use KEYMAP as the keymap for the duration of this\n\ + command. Acceptable keymap names are emacs,\n\ + emacs-standard, emacs-meta, emacs-ctlx, vi, vi-move,\n\ + vi-command, and vi-insert.\n\ + -l List names of functions.\n\ + -P List function names and bindings.\n\ + -p List functions and bindings in a form that can be\n\ + reused as input.\n\ + -S List key sequences that invoke macros and their values\n\ + -s List key sequences that invoke macros and their values\n\ + in a form that can be reused as input.\n\ + -V List variable names and values\n\ + -v List variable names and values in a form that can\n\ + be reused as input.\n\ + -q function-name Query about which keys invoke the named function.\n\ + -u function-name Unbind all keys which are bound to the named function.\n\ + -r keyseq Remove the binding for KEYSEQ.\n\ + -f filename Read key bindings from FILENAME.\n\ + -x keyseq:shell-command Cause SHELL-COMMAND to be executed when\n\ + KEYSEQ is entered.\n\ + -X List key sequences bound with -x and associated commands\n\ + in a form that can be reused as input.\n\ + \n\ + Exit Status:\n\ + bind returns 0 unless an unrecognized option is given or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* READLINE */ +char * const break_doc[] = { +#if defined (HELP_BUILTIN) +N_("Exit for, while, or until loops.\n\ + \n\ + Exit a FOR, WHILE or UNTIL loop. If N is specified, break N enclosing\n\ + loops.\n\ + \n\ + Exit Status:\n\ + The exit status is 0 unless N is not greater than or equal to 1."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const continue_doc[] = { +#if defined (HELP_BUILTIN) +N_("Resume for, while, or until loops.\n\ + \n\ + Resumes the next iteration of the enclosing FOR, WHILE or UNTIL loop.\n\ + If N is specified, resumes the Nth enclosing loop.\n\ + \n\ + Exit Status:\n\ + The exit status is 0 unless N is not greater than or equal to 1."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const builtin_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute shell builtins.\n\ + \n\ + Execute SHELL-BUILTIN with arguments ARGs without performing command\n\ + lookup. This is useful when you wish to reimplement a shell builtin\n\ + as a shell function, but need to execute the builtin within the function.\n\ + \n\ + Exit Status:\n\ + Returns the exit status of SHELL-BUILTIN, or false if SHELL-BUILTIN is\n\ + not a shell builtin."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#if defined (DEBUGGER) +char * const caller_doc[] = { +#if defined (HELP_BUILTIN) +N_("Return the context of the current subroutine call.\n\ + \n\ + Without EXPR, returns \"$line $filename\". With EXPR, returns\n\ + \"$line $subroutine $filename\"; this extra information can be used to\n\ + provide a stack trace.\n\ + \n\ + The value of EXPR indicates how many call frames to go back before the\n\ + current one; the top frame is frame 0.\n\ + \n\ + Exit Status:\n\ + Returns 0 unless the shell is not executing a shell function or EXPR\n\ + is invalid."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* DEBUGGER */ +char * const cd_doc[] = { +#if defined (HELP_BUILTIN) +N_("Change the shell working directory.\n\ + \n\ + Change the current directory to DIR. The default DIR is the value of the\n\ + HOME shell variable. If DIR is \"-\", it is converted to $OLDPWD.\n\ + \n\ + The variable CDPATH defines the search path for the directory containing\n\ + DIR. Alternative directory names in CDPATH are separated by a colon (:).\n\ + A null directory name is the same as the current directory. If DIR begins\n\ + with a slash (/), then CDPATH is not used.\n\ + \n\ + If the directory is not found, and the shell option `cdable_vars' is set,\n\ + the word is assumed to be a variable name. If that variable has a value,\n\ + its value is used for DIR.\n\ + \n\ + Options:\n\ + -L force symbolic links to be followed: resolve symbolic\n\ + links in DIR after processing instances of `..'\n\ + -P use the physical directory structure without following\n\ + symbolic links: resolve symbolic links in DIR before\n\ + processing instances of `..'\n\ + -e if the -P option is supplied, and the current working\n\ + directory cannot be determined successfully, exit with\n\ + a non-zero status\n\ + -@ on systems that support it, present a file with extended\n\ + attributes as a directory containing the file attributes\n\ + \n\ + The default is to follow symbolic links, as if `-L' were specified.\n\ + `..' is processed by removing the immediately previous pathname component\n\ + back to a slash or the beginning of DIR.\n\ + \n\ + Exit Status:\n\ + Returns 0 if the directory is changed, and if $PWD is set successfully when\n\ + -P is used; non-zero otherwise."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const pwd_doc[] = { +#if defined (HELP_BUILTIN) +N_("Print the name of the current working directory.\n\ + \n\ + Options:\n\ + -L print the value of $PWD if it names the current working\n\ + directory\n\ + -P print the physical directory, without any symbolic links\n\ + \n\ + By default, `pwd' behaves as if `-L' were specified.\n\ + \n\ + Exit Status:\n\ + Returns 0 unless an invalid option is given or the current directory\n\ + cannot be read."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const colon_doc[] = { +#if defined (HELP_BUILTIN) +N_("Null command.\n\ + \n\ + No effect; the command does nothing.\n\ + \n\ + Exit Status:\n\ + Always succeeds."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const true_doc[] = { +#if defined (HELP_BUILTIN) +N_("Return a successful result.\n\ + \n\ + Exit Status:\n\ + Always succeeds."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const false_doc[] = { +#if defined (HELP_BUILTIN) +N_("Return an unsuccessful result.\n\ + \n\ + Exit Status:\n\ + Always fails."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const command_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute a simple command or display information about commands.\n\ + \n\ + Runs COMMAND with ARGS suppressing shell function lookup, or display\n\ + information about the specified COMMANDs. Can be used to invoke commands\n\ + on disk when a function with the same name exists.\n\ + \n\ + Options:\n\ + -p use a default value for PATH that is guaranteed to find all of\n\ + the standard utilities\n\ + -v print a description of COMMAND similar to the `type' builtin\n\ + -V print a more verbose description of each COMMAND\n\ + \n\ + Exit Status:\n\ + Returns exit status of COMMAND, or failure if COMMAND is not found."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const declare_doc[] = { +#if defined (HELP_BUILTIN) +N_("Set variable values and attributes.\n\ + \n\ + Declare variables and give them attributes. If no NAMEs are given,\n\ + display the attributes and values of all variables.\n\ + \n\ + Options:\n\ + -f restrict action or display to function names and definitions\n\ + -F restrict display to function names only (plus line number and\n\ + source file when debugging)\n\ + -g create global variables when used in a shell function; otherwise\n\ + ignored\n\ + -I if creating a local variable, inherit the attributes and value\n\ + of a variable with the same name at a previous scope\n\ + -p display the attributes and value of each NAME\n\ + \n\ + Options which set attributes:\n\ + -a to make NAMEs indexed arrays (if supported)\n\ + -A to make NAMEs associative arrays (if supported)\n\ + -i to make NAMEs have the `integer' attribute\n\ + -l to convert the value of each NAME to lower case on assignment\n\ + -n make NAME a reference to the variable named by its value\n\ + -r to make NAMEs readonly\n\ + -t to make NAMEs have the `trace' attribute\n\ + -u to convert the value of each NAME to upper case on assignment\n\ + -x to make NAMEs export\n\ + \n\ + Using `+' instead of `-' turns off the given attribute.\n\ + \n\ + Variables with the integer attribute have arithmetic evaluation (see\n\ + the `let' command) performed when the variable is assigned a value.\n\ + \n\ + When used in a function, `declare' makes NAMEs local, as with the `local'\n\ + command. The `-g' option suppresses this behavior.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is supplied or a variable\n\ + assignment error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const typeset_doc[] = { +#if defined (HELP_BUILTIN) +N_("Set variable values and attributes.\n\ + \n\ + A synonym for `declare'. See `help declare'."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const local_doc[] = { +#if defined (HELP_BUILTIN) +N_("Define local variables.\n\ + \n\ + Create a local variable called NAME, and give it VALUE. OPTION can\n\ + be any option accepted by `declare'.\n\ + \n\ + Local variables can only be used within a function; they are visible\n\ + only to the function where they are defined and its children.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is supplied, a variable\n\ + assignment error occurs, or the shell is not executing a function."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#if defined (V9_ECHO) +char * const echo_doc[] = { +#if defined (HELP_BUILTIN) +N_("Write arguments to the standard output.\n\ + \n\ + Display the ARGs, separated by a single space character and followed by a\n\ + newline, on the standard output.\n\ + \n\ + Options:\n\ + -n do not append a newline\n\ + -e enable interpretation of the following backslash escapes\n\ + -E explicitly suppress interpretation of backslash escapes\n\ + \n\ + `echo' interprets the following backslash-escaped characters:\n\ + \\a alert (bell)\n\ + \\b backspace\n\ + \\c suppress further output\n\ + \\e escape character\n\ + \\E escape character\n\ + \\f form feed\n\ + \\n new line\n\ + \\r carriage return\n\ + \\t horizontal tab\n\ + \\v vertical tab\n\ + \\\\ backslash\n\ + \\0nnn the character whose ASCII code is NNN (octal). NNN can be\n\ + 0 to 3 octal digits\n\ + \\xHH the eight-bit character whose value is HH (hexadecimal). HH\n\ + can be one or two hex digits\n\ + \\uHHHH the Unicode character whose value is the hexadecimal value HHHH.\n\ + HHHH can be one to four hex digits.\n\ + \\UHHHHHHHH the Unicode character whose value is the hexadecimal value\n\ + HHHHHHHH. HHHHHHHH can be one to eight hex digits.\n\ + \n\ + Exit Status:\n\ + Returns success unless a write error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* V9_ECHO */ +#if !defined (V9_ECHO) +char * const echo_doc[] = { +#if defined (HELP_BUILTIN) +N_("Write arguments to the standard output.\n\ + \n\ + Display the ARGs on the standard output followed by a newline.\n\ + \n\ + Options:\n\ + -n do not append a newline\n\ + \n\ + Exit Status:\n\ + Returns success unless a write error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* !V9_ECHO */ +char * const enable_doc[] = { +#if defined (HELP_BUILTIN) +N_("Enable and disable shell builtins.\n\ + \n\ + Enables and disables builtin shell commands. Disabling allows you to\n\ + execute a disk command which has the same name as a shell builtin\n\ + without using a full pathname.\n\ + \n\ + Options:\n\ + -a print a list of builtins showing whether or not each is enabled\n\ + -n disable each NAME or display a list of disabled builtins\n\ + -p print the list of builtins in a reusable format\n\ + -s print only the names of Posix `special' builtins\n\ + \n\ + Options controlling dynamic loading:\n\ + -f Load builtin NAME from shared object FILENAME\n\ + -d Remove a builtin loaded with -f\n\ + \n\ + Without options, each NAME is enabled.\n\ + \n\ + To use the `test' found in $PATH instead of the shell builtin\n\ + version, type `enable -n test'.\n\ + \n\ + Exit Status:\n\ + Returns success unless NAME is not a shell builtin or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const eval_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute arguments as a shell command.\n\ + \n\ + Combine ARGs into a single string, use the result as input to the shell,\n\ + and execute the resulting commands.\n\ + \n\ + Exit Status:\n\ + Returns exit status of command or success if command is null."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const getopts_doc[] = { +#if defined (HELP_BUILTIN) +N_("Parse option arguments.\n\ + \n\ + Getopts is used by shell procedures to parse positional parameters\n\ + as options.\n\ + \n\ + OPTSTRING contains the option letters to be recognized; if a letter\n\ + is followed by a colon, the option is expected to have an argument,\n\ + which should be separated from it by white space.\n\ + \n\ + Each time it is invoked, getopts will place the next option in the\n\ + shell variable $name, initializing name if it does not exist, and\n\ + the index of the next argument to be processed into the shell\n\ + variable OPTIND. OPTIND is initialized to 1 each time the shell or\n\ + a shell script is invoked. When an option requires an argument,\n\ + getopts places that argument into the shell variable OPTARG.\n\ + \n\ + getopts reports errors in one of two ways. If the first character\n\ + of OPTSTRING is a colon, getopts uses silent error reporting. In\n\ + this mode, no error messages are printed. If an invalid option is\n\ + seen, getopts places the option character found into OPTARG. If a\n\ + required argument is not found, getopts places a ':' into NAME and\n\ + sets OPTARG to the option character found. If getopts is not in\n\ + silent mode, and an invalid option is seen, getopts places '?' into\n\ + NAME and unsets OPTARG. If a required argument is not found, a '?'\n\ + is placed in NAME, OPTARG is unset, and a diagnostic message is\n\ + printed.\n\ + \n\ + If the shell variable OPTERR has the value 0, getopts disables the\n\ + printing of error messages, even if the first character of\n\ + OPTSTRING is not a colon. OPTERR has the value 1 by default.\n\ + \n\ + Getopts normally parses the positional parameters, but if arguments\n\ + are supplied as ARG values, they are parsed instead.\n\ + \n\ + Exit Status:\n\ + Returns success if an option is found; fails if the end of options is\n\ + encountered or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const exec_doc[] = { +#if defined (HELP_BUILTIN) +N_("Replace the shell with the given command.\n\ + \n\ + Execute COMMAND, replacing this shell with the specified program.\n\ + ARGUMENTS become the arguments to COMMAND. If COMMAND is not specified,\n\ + any redirections take effect in the current shell.\n\ + \n\ + Options:\n\ + -a name pass NAME as the zeroth argument to COMMAND\n\ + -c execute COMMAND with an empty environment\n\ + -l place a dash in the zeroth argument to COMMAND\n\ + \n\ + If the command cannot be executed, a non-interactive shell exits, unless\n\ + the shell option `execfail' is set.\n\ + \n\ + Exit Status:\n\ + Returns success unless COMMAND is not found or a redirection error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const exit_doc[] = { +#if defined (HELP_BUILTIN) +N_("Exit the shell.\n\ + \n\ + Exits the shell with a status of N. If N is omitted, the exit status\n\ + is that of the last command executed."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const logout_doc[] = { +#if defined (HELP_BUILTIN) +N_("Exit a login shell.\n\ + \n\ + Exits a login shell with exit status N. Returns an error if not executed\n\ + in a login shell."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#if defined (HISTORY) +char * const fc_doc[] = { +#if defined (HELP_BUILTIN) +N_("Display or execute commands from the history list.\n\ + \n\ + fc is used to list or edit and re-execute commands from the history list.\n\ + FIRST and LAST can be numbers specifying the range, or FIRST can be a\n\ + string, which means the most recent command beginning with that\n\ + string.\n\ + \n\ + Options:\n\ + -e ENAME select which editor to use. Default is FCEDIT, then EDITOR,\n\ + then vi\n\ + -l list lines instead of editing\n\ + -n omit line numbers when listing\n\ + -r reverse the order of the lines (newest listed first)\n\ + \n\ + With the `fc -s [pat=rep ...] [command]' format, COMMAND is\n\ + re-executed after the substitution OLD=NEW is performed.\n\ + \n\ + A useful alias to use with this is r='fc -s', so that typing `r cc'\n\ + runs the last command beginning with `cc' and typing `r' re-executes\n\ + the last command.\n\ + \n\ + Exit Status:\n\ + Returns success or status of executed command; non-zero if an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* HISTORY */ +#if defined (JOB_CONTROL) +char * const fg_doc[] = { +#if defined (HELP_BUILTIN) +N_("Move job to the foreground.\n\ + \n\ + Place the job identified by JOB_SPEC in the foreground, making it the\n\ + current job. If JOB_SPEC is not present, the shell's notion of the\n\ + current job is used.\n\ + \n\ + Exit Status:\n\ + Status of command placed in foreground, or failure if an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* JOB_CONTROL */ +#if defined (JOB_CONTROL) +char * const bg_doc[] = { +#if defined (HELP_BUILTIN) +N_("Move jobs to the background.\n\ + \n\ + Place the jobs identified by each JOB_SPEC in the background, as if they\n\ + had been started with `&'. If JOB_SPEC is not present, the shell's notion\n\ + of the current job is used.\n\ + \n\ + Exit Status:\n\ + Returns success unless job control is not enabled or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* JOB_CONTROL */ +char * const hash_doc[] = { +#if defined (HELP_BUILTIN) +N_("Remember or display program locations.\n\ + \n\ + Determine and remember the full pathname of each command NAME. If\n\ + no arguments are given, information about remembered commands is displayed.\n\ + \n\ + Options:\n\ + -d forget the remembered location of each NAME\n\ + -l display in a format that may be reused as input\n\ + -p pathname use PATHNAME as the full pathname of NAME\n\ + -r forget all remembered locations\n\ + -t print the remembered location of each NAME, preceding\n\ + each location with the corresponding NAME if multiple\n\ + NAMEs are given\n\ + Arguments:\n\ + NAME Each NAME is searched for in $PATH and added to the list\n\ + of remembered commands.\n\ + \n\ + Exit Status:\n\ + Returns success unless NAME is not found or an invalid option is given."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#if defined (HELP_BUILTIN) +char * const help_doc[] = { +#if defined (HELP_BUILTIN) +N_("Display information about builtin commands.\n\ + \n\ + Displays brief summaries of builtin commands. If PATTERN is\n\ + specified, gives detailed help on all commands matching PATTERN,\n\ + otherwise the list of help topics is printed.\n\ + \n\ + Options:\n\ + -d output short description for each topic\n\ + -m display usage in pseudo-manpage format\n\ + -s output only a short usage synopsis for each topic matching\n\ + PATTERN\n\ + \n\ + Arguments:\n\ + PATTERN Pattern specifying a help topic\n\ + \n\ + Exit Status:\n\ + Returns success unless PATTERN is not found or an invalid option is given."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* HELP_BUILTIN */ +#if defined (HISTORY) +char * const history_doc[] = { +#if defined (HELP_BUILTIN) +N_("Display or manipulate the history list.\n\ + \n\ + Display the history list with line numbers, prefixing each modified\n\ + entry with a `*'. An argument of N lists only the last N entries.\n\ + \n\ + Options:\n\ + -c clear the history list by deleting all of the entries\n\ + -d offset delete the history entry at position OFFSET. Negative\n\ + offsets count back from the end of the history list\n\ + \n\ + -a append history lines from this session to the history file\n\ + -n read all history lines not already read from the history file\n\ + and append them to the history list\n\ + -r read the history file and append the contents to the history\n\ + list\n\ + -w write the current history to the history file\n\ + \n\ + -p perform history expansion on each ARG and display the result\n\ + without storing it in the history list\n\ + -s append the ARGs to the history list as a single entry\n\ + \n\ + If FILENAME is given, it is used as the history file. Otherwise,\n\ + if HISTFILE has a value, that is used, else ~/.bash_history.\n\ + \n\ + If the HISTTIMEFORMAT variable is set and not null, its value is used\n\ + as a format string for strftime(3) to print the time stamp associated\n\ + with each displayed history entry. No time stamps are printed otherwise.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is given or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* HISTORY */ +#if defined (JOB_CONTROL) +char * const jobs_doc[] = { +#if defined (HELP_BUILTIN) +N_("Display status of jobs.\n\ + \n\ + Lists the active jobs. JOBSPEC restricts output to that job.\n\ + Without options, the status of all active jobs is displayed.\n\ + \n\ + Options:\n\ + -l lists process IDs in addition to the normal information\n\ + -n lists only processes that have changed status since the last\n\ + notification\n\ + -p lists process IDs only\n\ + -r restrict output to running jobs\n\ + -s restrict output to stopped jobs\n\ + \n\ + If -x is supplied, COMMAND is run after all job specifications that\n\ + appear in ARGS have been replaced with the process ID of that job's\n\ + process group leader.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is given or an error occurs.\n\ + If -x is used, returns the exit status of COMMAND."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* JOB_CONTROL */ +#if defined (JOB_CONTROL) +char * const disown_doc[] = { +#if defined (HELP_BUILTIN) +N_("Remove jobs from current shell.\n\ + \n\ + Removes each JOBSPEC argument from the table of active jobs. Without\n\ + any JOBSPECs, the shell uses its notion of the current job.\n\ + \n\ + Options:\n\ + -a remove all jobs if JOBSPEC is not supplied\n\ + -h mark each JOBSPEC so that SIGHUP is not sent to the job if the\n\ + shell receives a SIGHUP\n\ + -r remove only running jobs\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option or JOBSPEC is given."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* JOB_CONTROL */ +char * const kill_doc[] = { +#if defined (HELP_BUILTIN) +N_("Send a signal to a job.\n\ + \n\ + Send the processes identified by PID or JOBSPEC the signal named by\n\ + SIGSPEC or SIGNUM. If neither SIGSPEC nor SIGNUM is present, then\n\ + SIGTERM is assumed.\n\ + \n\ + Options:\n\ + -s sig SIG is a signal name\n\ + -n sig SIG is a signal number\n\ + -l list the signal names; if arguments follow `-l' they are\n\ + assumed to be signal numbers for which names should be listed\n\ + -L synonym for -l\n\ + \n\ + Kill is a shell builtin for two reasons: it allows job IDs to be used\n\ + instead of process IDs, and allows processes to be killed if the limit\n\ + on processes that you can create is reached.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is given or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const let_doc[] = { +#if defined (HELP_BUILTIN) +N_("Evaluate arithmetic expressions.\n\ + \n\ + Evaluate each ARG as an arithmetic expression. Evaluation is done in\n\ + fixed-width integers with no check for overflow, though division by 0\n\ + is trapped and flagged as an error. The following list of operators is\n\ + grouped into levels of equal-precedence operators. The levels are listed\n\ + in order of decreasing precedence.\n\ + \n\ + id++, id-- variable post-increment, post-decrement\n\ + ++id, --id variable pre-increment, pre-decrement\n\ + -, + unary minus, plus\n\ + !, ~ logical and bitwise negation\n\ + ** exponentiation\n\ + *, /, % multiplication, division, remainder\n\ + +, - addition, subtraction\n\ + <<, >> left and right bitwise shifts\n\ + <=, >=, <, > comparison\n\ + ==, != equality, inequality\n\ + & bitwise AND\n\ + ^ bitwise XOR\n\ + | bitwise OR\n\ + && logical AND\n\ + || logical OR\n\ + expr ? expr : expr\n\ + conditional operator\n\ + =, *=, /=, %=,\n\ + +=, -=, <<=, >>=,\n\ + &=, ^=, |= assignment\n\ + \n\ + Shell variables are allowed as operands. The name of the variable\n\ + is replaced by its value (coerced to a fixed-width integer) within\n\ + an expression. The variable need not have its integer attribute\n\ + turned on to be used in an expression.\n\ + \n\ + Operators are evaluated in order of precedence. Sub-expressions in\n\ + parentheses are evaluated first and may override the precedence\n\ + rules above.\n\ + \n\ + Exit Status:\n\ + If the last ARG evaluates to 0, let returns 1; let returns 0 otherwise."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const read_doc[] = { +#if defined (HELP_BUILTIN) +N_("Read a line from the standard input and split it into fields.\n\ + \n\ + Reads a single line from the standard input, or from file descriptor FD\n\ + if the -u option is supplied. The line is split into fields as with word\n\ + splitting, and the first word is assigned to the first NAME, the second\n\ + word to the second NAME, and so on, with any leftover words assigned to\n\ + the last NAME. Only the characters found in $IFS are recognized as word\n\ + delimiters. By default, the backslash character escapes delimiter characters\n\ + and newline.\n\ + \n\ + If no NAMEs are supplied, the line read is stored in the REPLY variable.\n\ + \n\ + Options:\n\ + -a array assign the words read to sequential indices of the array\n\ + variable ARRAY, starting at zero\n\ + -d delim continue until the first character of DELIM is read, rather\n\ + than newline\n\ + -e use Readline to obtain the line\n\ + -i text use TEXT as the initial text for Readline\n\ + -n nchars return after reading NCHARS characters rather than waiting\n\ + for a newline, but honor a delimiter if fewer than\n\ + NCHARS characters are read before the delimiter\n\ + -N nchars return only after reading exactly NCHARS characters, unless\n\ + EOF is encountered or read times out, ignoring any\n\ + delimiter\n\ + -p prompt output the string PROMPT without a trailing newline before\n\ + attempting to read\n\ + -r do not allow backslashes to escape any characters\n\ + -s do not echo input coming from a terminal\n\ + -t timeout time out and return failure if a complete line of\n\ + input is not read within TIMEOUT seconds. The value of the\n\ + TMOUT variable is the default timeout. TIMEOUT may be a\n\ + fractional number. If TIMEOUT is 0, read returns\n\ + immediately, without trying to read any data, returning\n\ + success only if input is available on the specified\n\ + file descriptor. The exit status is greater than 128\n\ + if the timeout is exceeded\n\ + -u fd read from file descriptor FD instead of the standard input\n\ + \n\ + Exit Status:\n\ + The return code is zero, unless end-of-file is encountered, read times out\n\ + (in which case it's greater than 128), a variable assignment error occurs,\n\ + or an invalid file descriptor is supplied as the argument to -u."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const return_doc[] = { +#if defined (HELP_BUILTIN) +N_("Return from a shell function.\n\ + \n\ + Causes a function or sourced script to exit with the return value\n\ + specified by N. If N is omitted, the return status is that of the\n\ + last command executed within the function or script.\n\ + \n\ + Exit Status:\n\ + Returns N, or failure if the shell is not executing a function or script."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const set_doc[] = { +#if defined (HELP_BUILTIN) +N_("Set or unset values of shell options and positional parameters.\n\ + \n\ + Change the value of shell attributes and positional parameters, or\n\ + display the names and values of shell variables.\n\ + \n\ + Options:\n\ + -a Mark variables which are modified or created for export.\n\ + -b Notify of job termination immediately.\n\ + -e Exit immediately if a command exits with a non-zero status.\n\ + -f Disable file name generation (globbing).\n\ + -h Remember the location of commands as they are looked up.\n\ + -k All assignment arguments are placed in the environment for a\n\ + command, not just those that precede the command name.\n\ + -m Job control is enabled.\n\ + -n Read commands but do not execute them.\n\ + -o option-name\n\ + Set the variable corresponding to option-name:\n\ + allexport same as -a\n\ + braceexpand same as -B\n\ + emacs use an emacs-style line editing interface\n\ + errexit same as -e\n\ + errtrace same as -E\n\ + functrace same as -T\n\ + hashall same as -h\n\ + histexpand same as -H\n\ + history enable command history\n\ + ignoreeof the shell will not exit upon reading EOF\n\ + interactive-comments\n\ + allow comments to appear in interactive commands\n\ + keyword same as -k\n\ + monitor same as -m\n\ + noclobber same as -C\n\ + noexec same as -n\n\ + noglob same as -f\n\ + nolog currently accepted but ignored\n\ + notify same as -b\n\ + nounset same as -u\n\ + onecmd same as -t\n\ + physical same as -P\n\ + pipefail the return value of a pipeline is the status of\n\ + the last command to exit with a non-zero status,\n\ + or zero if no command exited with a non-zero status\n\ + posix change the behavior of bash where the default\n\ + operation differs from the Posix standard to\n\ + match the standard\n\ + privileged same as -p\n\ + verbose same as -v\n\ + vi use a vi-style line editing interface\n\ + xtrace same as -x\n\ + -p Turned on whenever the real and effective user ids do not match.\n\ + Disables processing of the $ENV file and importing of shell\n\ + functions. Turning this option off causes the effective uid and\n\ + gid to be set to the real uid and gid.\n\ + -t Exit after reading and executing one command.\n\ + -u Treat unset variables as an error when substituting.\n\ + -v Print shell input lines as they are read.\n\ + -x Print commands and their arguments as they are executed.\n\ + -B the shell will perform brace expansion\n\ + -C If set, disallow existing regular files to be overwritten\n\ + by redirection of output.\n\ + -E If set, the ERR trap is inherited by shell functions.\n\ + -H Enable ! style history substitution. This flag is on\n\ + by default when the shell is interactive.\n\ + -P If set, do not resolve symbolic links when executing commands\n\ + such as cd which change the current directory.\n\ + -T If set, the DEBUG and RETURN traps are inherited by shell functions.\n\ + -- Assign any remaining arguments to the positional parameters.\n\ + If there are no remaining arguments, the positional parameters\n\ + are unset.\n\ + - Assign any remaining arguments to the positional parameters.\n\ + The -x and -v options are turned off.\n\ + \n\ + Using + rather than - causes these flags to be turned off. The\n\ + flags can also be used upon invocation of the shell. The current\n\ + set of flags may be found in $-. The remaining n ARGs are positional\n\ + parameters and are assigned, in order, to $1, $2, .. $n. If no\n\ + ARGs are given, all shell variables are printed.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is given."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const unset_doc[] = { +#if defined (HELP_BUILTIN) +N_("Unset values and attributes of shell variables and functions.\n\ + \n\ + For each NAME, remove the corresponding variable or function.\n\ + \n\ + Options:\n\ + -f treat each NAME as a shell function\n\ + -v treat each NAME as a shell variable\n\ + -n treat each NAME as a name reference and unset the variable itself\n\ + rather than the variable it references\n\ + \n\ + Without options, unset first tries to unset a variable, and if that fails,\n\ + tries to unset a function.\n\ + \n\ + Some variables cannot be unset; also see `readonly'.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is given or a NAME is read-only."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const export_doc[] = { +#if defined (HELP_BUILTIN) +N_("Set export attribute for shell variables.\n\ + \n\ + Marks each NAME for automatic export to the environment of subsequently\n\ + executed commands. If VALUE is supplied, assign VALUE before exporting.\n\ + \n\ + Options:\n\ + -f refer to shell functions\n\ + -n remove the export property from each NAME\n\ + -p display a list of all exported variables and functions\n\ + \n\ + An argument of `--' disables further option processing.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is given or NAME is invalid."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const readonly_doc[] = { +#if defined (HELP_BUILTIN) +N_("Mark shell variables as unchangeable.\n\ + \n\ + Mark each NAME as read-only; the values of these NAMEs may not be\n\ + changed by subsequent assignment. If VALUE is supplied, assign VALUE\n\ + before marking as read-only.\n\ + \n\ + Options:\n\ + -a refer to indexed array variables\n\ + -A refer to associative array variables\n\ + -f refer to shell functions\n\ + -p display a list of all readonly variables or functions,\n\ + depending on whether or not the -f option is given\n\ + \n\ + An argument of `--' disables further option processing.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is given or NAME is invalid."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const shift_doc[] = { +#if defined (HELP_BUILTIN) +N_("Shift positional parameters.\n\ + \n\ + Rename the positional parameters $N+1,$N+2 ... to $1,$2 ... If N is\n\ + not given, it is assumed to be 1.\n\ + \n\ + Exit Status:\n\ + Returns success unless N is negative or greater than $#."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const source_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute commands from a file in the current shell.\n\ + \n\ + Read and execute commands from FILENAME in the current shell. The\n\ + entries in $PATH are used to find the directory containing FILENAME.\n\ + If any ARGUMENTS are supplied, they become the positional parameters\n\ + when FILENAME is executed.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed in FILENAME; fails if\n\ + FILENAME cannot be read."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const dot_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute commands from a file in the current shell.\n\ + \n\ + Read and execute commands from FILENAME in the current shell. The\n\ + entries in $PATH are used to find the directory containing FILENAME.\n\ + If any ARGUMENTS are supplied, they become the positional parameters\n\ + when FILENAME is executed.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed in FILENAME; fails if\n\ + FILENAME cannot be read."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#if defined (JOB_CONTROL) +char * const suspend_doc[] = { +#if defined (HELP_BUILTIN) +N_("Suspend shell execution.\n\ + \n\ + Suspend the execution of this shell until it receives a SIGCONT signal.\n\ + Unless forced, login shells and shells without job control cannot be\n\ + suspended.\n\ + \n\ + Options:\n\ + -f force the suspend, even if the shell is a login shell or job\n\ + control is not enabled.\n\ + \n\ + Exit Status:\n\ + Returns success unless job control is not enabled or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* JOB_CONTROL */ +char * const test_doc[] = { +#if defined (HELP_BUILTIN) +N_("Evaluate conditional expression.\n\ + \n\ + Exits with a status of 0 (true) or 1 (false) depending on\n\ + the evaluation of EXPR. Expressions may be unary or binary. Unary\n\ + expressions are often used to examine the status of a file. There\n\ + are string operators and numeric comparison operators as well.\n\ + \n\ + The behavior of test depends on the number of arguments. Read the\n\ + bash manual page for the complete specification.\n\ + \n\ + File operators:\n\ + \n\ + -a FILE True if file exists.\n\ + -b FILE True if file is block special.\n\ + -c FILE True if file is character special.\n\ + -d FILE True if file is a directory.\n\ + -e FILE True if file exists.\n\ + -f FILE True if file exists and is a regular file.\n\ + -g FILE True if file is set-group-id.\n\ + -h FILE True if file is a symbolic link.\n\ + -L FILE True if file is a symbolic link.\n\ + -k FILE True if file has its `sticky' bit set.\n\ + -p FILE True if file is a named pipe.\n\ + -r FILE True if file is readable by you.\n\ + -s FILE True if file exists and is not empty.\n\ + -S FILE True if file is a socket.\n\ + -t FD True if FD is opened on a terminal.\n\ + -u FILE True if the file is set-user-id.\n\ + -w FILE True if the file is writable by you.\n\ + -x FILE True if the file is executable by you.\n\ + -O FILE True if the file is effectively owned by you.\n\ + -G FILE True if the file is effectively owned by your group.\n\ + -N FILE True if the file has been modified since it was last read.\n\ + \n\ + FILE1 -nt FILE2 True if file1 is newer than file2 (according to\n\ + modification date).\n\ + \n\ + FILE1 -ot FILE2 True if file1 is older than file2.\n\ + \n\ + FILE1 -ef FILE2 True if file1 is a hard link to file2.\n\ + \n\ + String operators:\n\ + \n\ + -z STRING True if string is empty.\n\ + \n\ + -n STRING\n\ + STRING True if string is not empty.\n\ + \n\ + STRING1 = STRING2\n\ + True if the strings are equal.\n\ + STRING1 != STRING2\n\ + True if the strings are not equal.\n\ + STRING1 < STRING2\n\ + True if STRING1 sorts before STRING2 lexicographically.\n\ + STRING1 > STRING2\n\ + True if STRING1 sorts after STRING2 lexicographically.\n\ + \n\ + Other operators:\n\ + \n\ + -o OPTION True if the shell option OPTION is enabled.\n\ + -v VAR True if the shell variable VAR is set.\n\ + -R VAR True if the shell variable VAR is set and is a name\n\ + reference.\n\ + ! EXPR True if expr is false.\n\ + EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.\n\ + EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.\n\ + \n\ + arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,\n\ + -lt, -le, -gt, or -ge.\n\ + \n\ + Arithmetic binary operators return true if ARG1 is equal, not-equal,\n\ + less-than, less-than-or-equal, greater-than, or greater-than-or-equal\n\ + than ARG2.\n\ + \n\ + Exit Status:\n\ + Returns success if EXPR evaluates to true; fails if EXPR evaluates to\n\ + false or an invalid argument is given."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const test_bracket_doc[] = { +#if defined (HELP_BUILTIN) +N_("Evaluate conditional expression.\n\ + \n\ + This is a synonym for the \"test\" builtin, but the last argument must\n\ + be a literal `]', to match the opening `['."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const times_doc[] = { +#if defined (HELP_BUILTIN) +N_("Display process times.\n\ + \n\ + Prints the accumulated user and system times for the shell and all of its\n\ + child processes.\n\ + \n\ + Exit Status:\n\ + Always succeeds."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const trap_doc[] = { +#if defined (HELP_BUILTIN) +N_("Trap signals and other events.\n\ + \n\ + Defines and activates handlers to be run when the shell receives signals\n\ + or other conditions.\n\ + \n\ + ARG is a command to be read and executed when the shell receives the\n\ + signal(s) SIGNAL_SPEC. If ARG is absent (and a single SIGNAL_SPEC\n\ + is supplied) or `-', each specified signal is reset to its original\n\ + value. If ARG is the null string each SIGNAL_SPEC is ignored by the\n\ + shell and by the commands it invokes.\n\ + \n\ + If a SIGNAL_SPEC is EXIT (0) ARG is executed on exit from the shell. If\n\ + a SIGNAL_SPEC is DEBUG, ARG is executed before every simple command. If\n\ + a SIGNAL_SPEC is RETURN, ARG is executed each time a shell function or a\n\ + script run by the . or source builtins finishes executing. A SIGNAL_SPEC\n\ + of ERR means to execute ARG each time a command's failure would cause the\n\ + shell to exit when the -e option is enabled.\n\ + \n\ + If no arguments are supplied, trap prints the list of commands associated\n\ + with each signal.\n\ + \n\ + Options:\n\ + -l print a list of signal names and their corresponding numbers\n\ + -p display the trap commands associated with each SIGNAL_SPEC\n\ + \n\ + Each SIGNAL_SPEC is either a signal name in or a signal number.\n\ + Signal names are case insensitive and the SIG prefix is optional. A\n\ + signal may be sent to the shell with \"kill -signal $$\".\n\ + \n\ + Exit Status:\n\ + Returns success unless a SIGSPEC is invalid or an invalid option is given."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const type_doc[] = { +#if defined (HELP_BUILTIN) +N_("Display information about command type.\n\ + \n\ + For each NAME, indicate how it would be interpreted if used as a\n\ + command name.\n\ + \n\ + Options:\n\ + -a display all locations containing an executable named NAME;\n\ + includes aliases, builtins, and functions, if and only if\n\ + the `-p' option is not also used\n\ + -f suppress shell function lookup\n\ + -P force a PATH search for each NAME, even if it is an alias,\n\ + builtin, or function, and returns the name of the disk file\n\ + that would be executed\n\ + -p returns either the name of the disk file that would be executed,\n\ + or nothing if `type -t NAME' would not return `file'\n\ + -t output a single word which is one of `alias', `keyword',\n\ + `function', `builtin', `file' or `', if NAME is an alias,\n\ + shell reserved word, shell function, shell builtin, disk file,\n\ + or not found, respectively\n\ + \n\ + Arguments:\n\ + NAME Command name to be interpreted.\n\ + \n\ + Exit Status:\n\ + Returns success if all of the NAMEs are found; fails if any are not found."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#if !defined (_MINIX) +char * const ulimit_doc[] = { +#if defined (HELP_BUILTIN) +N_("Modify shell resource limits.\n\ + \n\ + Provides control over the resources available to the shell and processes\n\ + it creates, on systems that allow such control.\n\ + \n\ + Options:\n\ + -S use the `soft' resource limit\n\ + -H use the `hard' resource limit\n\ + -a all current limits are reported\n\ + -b the socket buffer size\n\ + -c the maximum size of core files created\n\ + -d the maximum size of a process's data segment\n\ + -e the maximum scheduling priority (`nice')\n\ + -f the maximum size of files written by the shell and its children\n\ + -i the maximum number of pending signals\n\ + -k the maximum number of kqueues allocated for this process\n\ + -l the maximum size a process may lock into memory\n\ + -m the maximum resident set size\n\ + -n the maximum number of open file descriptors\n\ + -p the pipe buffer size\n\ + -q the maximum number of bytes in POSIX message queues\n\ + -r the maximum real-time scheduling priority\n\ + -s the maximum stack size\n\ + -t the maximum amount of cpu time in seconds\n\ + -u the maximum number of user processes\n\ + -v the size of virtual memory\n\ + -x the maximum number of file locks\n\ + -P the maximum number of pseudoterminals\n\ + -R the maximum time a real-time process can run before blocking\n\ + -T the maximum number of threads\n\ + \n\ + Not all options are available on all platforms.\n\ + \n\ + If LIMIT is given, it is the new value of the specified resource; the\n\ + special LIMIT values `soft', `hard', and `unlimited' stand for the\n\ + current soft limit, the current hard limit, and no limit, respectively.\n\ + Otherwise, the current value of the specified resource is printed. If\n\ + no option is given, then -f is assumed.\n\ + \n\ + Values are in 1024-byte increments, except for -t, which is in seconds,\n\ + -p, which is in increments of 512 bytes, and -u, which is an unscaled\n\ + number of processes.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is supplied or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* !_MINIX */ +char * const umask_doc[] = { +#if defined (HELP_BUILTIN) +N_("Display or set file mode mask.\n\ + \n\ + Sets the user file-creation mask to MODE. If MODE is omitted, prints\n\ + the current value of the mask.\n\ + \n\ + If MODE begins with a digit, it is interpreted as an octal number;\n\ + otherwise it is a symbolic mode string like that accepted by chmod(1).\n\ + \n\ + Options:\n\ + -p if MODE is omitted, output in a form that may be reused as input\n\ + -S makes the output symbolic; otherwise an octal number is output\n\ + \n\ + Exit Status:\n\ + Returns success unless MODE is invalid or an invalid option is given."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#if defined (JOB_CONTROL) +char * const wait_doc[] = { +#if defined (HELP_BUILTIN) +N_("Wait for job completion and return exit status.\n\ + \n\ + Waits for each process identified by an ID, which may be a process ID or a\n\ + job specification, and reports its termination status. If ID is not\n\ + given, waits for all currently active child processes, and the return\n\ + status is zero. If ID is a job specification, waits for all processes\n\ + in that job's pipeline.\n\ + \n\ + If the -n option is supplied, waits for a single job from the list of IDs,\n\ + or, if no IDs are supplied, for the next job to complete and returns its\n\ + exit status.\n\ + \n\ + If the -p option is supplied, the process or job identifier of the job\n\ + for which the exit status is returned is assigned to the variable VAR\n\ + named by the option argument. The variable will be unset initially, before\n\ + any assignment. This is useful only when the -n option is supplied.\n\ + \n\ + If the -f option is supplied, and job control is enabled, waits for the\n\ + specified ID to terminate, instead of waiting for it to change status.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last ID; fails if ID is invalid or an invalid\n\ + option is given, or if -n is supplied and the shell has no unwaited-for\n\ + children."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* JOB_CONTROL */ +#if !defined (JOB_CONTROL) +char * const wait_doc[] = { +#if defined (HELP_BUILTIN) +N_("Wait for process completion and return exit status.\n\ + \n\ + Waits for each process specified by a PID and reports its termination status.\n\ + If PID is not given, waits for all currently active child processes,\n\ + and the return status is zero. PID must be a process ID.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last PID; fails if PID is invalid or an invalid\n\ + option is given."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* !JOB_CONTROL */ +char * const for_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute commands for each member in a list.\n\ + \n\ + The `for' loop executes a sequence of commands for each member in a\n\ + list of items. If `in WORDS ...;' is not present, then `in \"$@\"' is\n\ + assumed. For each element in WORDS, NAME is set to that element, and\n\ + the COMMANDS are executed.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const arith_for_doc[] = { +#if defined (HELP_BUILTIN) +N_("Arithmetic for loop.\n\ + \n\ + Equivalent to\n\ + (( EXP1 ))\n\ + while (( EXP2 )); do\n\ + COMMANDS\n\ + (( EXP3 ))\n\ + done\n\ + EXP1, EXP2, and EXP3 are arithmetic expressions. If any expression is\n\ + omitted, it behaves as if it evaluates to 1.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const select_doc[] = { +#if defined (HELP_BUILTIN) +N_("Select words from a list and execute commands.\n\ + \n\ + The WORDS are expanded, generating a list of words. The\n\ + set of expanded words is printed on the standard error, each\n\ + preceded by a number. If `in WORDS' is not present, `in \"$@\"'\n\ + is assumed. The PS3 prompt is then displayed and a line read\n\ + from the standard input. If the line consists of the number\n\ + corresponding to one of the displayed words, then NAME is set\n\ + to that word. If the line is empty, WORDS and the prompt are\n\ + redisplayed. If EOF is read, the command completes. Any other\n\ + value read causes NAME to be set to null. The line read is saved\n\ + in the variable REPLY. COMMANDS are executed after each selection\n\ + until a break command is executed.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const time_doc[] = { +#if defined (HELP_BUILTIN) +N_("Report time consumed by pipeline's execution.\n\ + \n\ + Execute PIPELINE and print a summary of the real time, user CPU time,\n\ + and system CPU time spent executing PIPELINE when it terminates.\n\ + \n\ + Options:\n\ + -p print the timing summary in the portable Posix format\n\ + \n\ + The value of the TIMEFORMAT variable is used as the output format.\n\ + \n\ + Exit Status:\n\ + The return status is the return status of PIPELINE."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const case_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute commands based on pattern matching.\n\ + \n\ + Selectively execute COMMANDS based upon WORD matching PATTERN. The\n\ + `|' is used to separate multiple patterns.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const if_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute commands based on conditional.\n\ + \n\ + The `if COMMANDS' list is executed. If its exit status is zero, then the\n\ + `then COMMANDS' list is executed. Otherwise, each `elif COMMANDS' list is\n\ + executed in turn, and if its exit status is zero, the corresponding\n\ + `then COMMANDS' list is executed and the if command completes. Otherwise,\n\ + the `else COMMANDS' list is executed, if present. The exit status of the\n\ + entire construct is the exit status of the last command executed, or zero\n\ + if no condition tested true.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const while_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute commands as long as a test succeeds.\n\ + \n\ + Expand and execute COMMANDS-2 as long as the final command in COMMANDS has\n\ + an exit status of zero.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const until_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute commands as long as a test does not succeed.\n\ + \n\ + Expand and execute COMMANDS-2 as long as the final command in COMMANDS has\n\ + an exit status which is not zero.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const coproc_doc[] = { +#if defined (HELP_BUILTIN) +N_("Create a coprocess named NAME.\n\ + \n\ + Execute COMMAND asynchronously, with the standard output and standard\n\ + input of the command connected via a pipe to file descriptors assigned\n\ + to indices 0 and 1 of an array variable NAME in the executing shell.\n\ + The default NAME is \"COPROC\".\n\ + \n\ + Exit Status:\n\ + The coproc command returns an exit status of 0."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const function_doc[] = { +#if defined (HELP_BUILTIN) +N_("Define shell function.\n\ + \n\ + Create a shell function named NAME. When invoked as a simple command,\n\ + NAME runs COMMANDs in the calling shell's context. When NAME is invoked,\n\ + the arguments are passed to the function as $1...$n, and the function's\n\ + name is in $FUNCNAME.\n\ + \n\ + Exit Status:\n\ + Returns success unless NAME is readonly."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const grouping_braces_doc[] = { +#if defined (HELP_BUILTIN) +N_("Group commands as a unit.\n\ + \n\ + Run a set of commands in a group. This is one way to redirect an\n\ + entire set of commands.\n\ + \n\ + Exit Status:\n\ + Returns the status of the last command executed."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const fg_percent_doc[] = { +#if defined (HELP_BUILTIN) +N_("Resume job in foreground.\n\ + \n\ + Equivalent to the JOB_SPEC argument to the `fg' command. Resume a\n\ + stopped or background job. JOB_SPEC can specify either a job name\n\ + or a job number. Following JOB_SPEC with a `&' places the job in\n\ + the background, as if the job specification had been supplied as an\n\ + argument to `bg'.\n\ + \n\ + Exit Status:\n\ + Returns the status of the resumed job."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const arith_doc[] = { +#if defined (HELP_BUILTIN) +N_("Evaluate arithmetic expression.\n\ + \n\ + The EXPRESSION is evaluated according to the rules for arithmetic\n\ + evaluation. Equivalent to `let \"EXPRESSION\"'.\n\ + \n\ + Exit Status:\n\ + Returns 1 if EXPRESSION evaluates to 0; returns 0 otherwise."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const conditional_doc[] = { +#if defined (HELP_BUILTIN) +N_("Execute conditional command.\n\ + \n\ + Returns a status of 0 or 1 depending on the evaluation of the conditional\n\ + expression EXPRESSION. Expressions are composed of the same primaries used\n\ + by the `test' builtin, and may be combined using the following operators:\n\ + \n\ + ( EXPRESSION ) Returns the value of EXPRESSION\n\ + ! EXPRESSION True if EXPRESSION is false; else false\n\ + EXPR1 && EXPR2 True if both EXPR1 and EXPR2 are true; else false\n\ + EXPR1 || EXPR2 True if either EXPR1 or EXPR2 is true; else false\n\ + \n\ + When the `==' and `!=' operators are used, the string to the right of\n\ + the operator is used as a pattern and pattern matching is performed.\n\ + When the `=~' operator is used, the string to the right of the operator\n\ + is matched as a regular expression.\n\ + \n\ + The && and || operators do not evaluate EXPR2 if EXPR1 is sufficient to\n\ + determine the expression's value.\n\ + \n\ + Exit Status:\n\ + 0 or 1 depending on value of EXPRESSION."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const variable_help_doc[] = { +#if defined (HELP_BUILTIN) +N_("Common shell variable names and usage.\n\ + \n\ + BASH_VERSION Version information for this Bash.\n\ + CDPATH A colon-separated list of directories to search\n\ + for directories given as arguments to `cd'.\n\ + GLOBIGNORE A colon-separated list of patterns describing filenames to\n\ + be ignored by pathname expansion.\n\ + HISTFILE The name of the file where your command history is stored.\n\ + HISTFILESIZE The maximum number of lines this file can contain.\n\ + HISTSIZE The maximum number of history lines that a running\n\ + shell can access.\n\ + HOME The complete pathname to your login directory.\n\ + HOSTNAME The name of the current host.\n\ + HOSTTYPE The type of CPU this version of Bash is running under.\n\ + IGNOREEOF Controls the action of the shell on receipt of an EOF\n\ + character as the sole input. If set, then the value\n\ + of it is the number of EOF characters that can be seen\n\ + in a row on an empty line before the shell will exit\n\ + (default 10). When unset, EOF signifies the end of input.\n\ + MACHTYPE A string describing the current system Bash is running on.\n\ + MAILCHECK How often, in seconds, Bash checks for new mail.\n\ + MAILPATH A colon-separated list of filenames which Bash checks\n\ + for new mail.\n\ + OSTYPE The version of Unix this version of Bash is running on.\n\ + PATH A colon-separated list of directories to search when\n\ + looking for commands.\n\ + PROMPT_COMMAND A command to be executed before the printing of each\n\ + primary prompt.\n\ + PS1 The primary prompt string.\n\ + PS2 The secondary prompt string.\n\ + PWD The full pathname of the current directory.\n\ + SHELLOPTS A colon-separated list of enabled shell options.\n\ + TERM The name of the current terminal type.\n\ + TIMEFORMAT The output format for timing statistics displayed by the\n\ + `time' reserved word.\n\ + auto_resume Non-null means a command word appearing on a line by\n\ + itself is first looked for in the list of currently\n\ + stopped jobs. If found there, that job is foregrounded.\n\ + A value of `exact' means that the command word must\n\ + exactly match a command in the list of stopped jobs. A\n\ + value of `substring' means that the command word must\n\ + match a substring of the job. Any other value means that\n\ + the command must be a prefix of a stopped job.\n\ + histchars Characters controlling history expansion and quick\n\ + substitution. The first character is the history\n\ + substitution character, usually `!'. The second is\n\ + the `quick substitution' character, usually `^'. The\n\ + third is the `history comment' character, usually `#'.\n\ + HISTIGNORE A colon-separated list of patterns used to decide which\n\ + commands should be saved on the history list.\n\ +"), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#if defined (PUSHD_AND_POPD) +char * const pushd_doc[] = { +#if defined (HELP_BUILTIN) +N_("Add directories to stack.\n\ + \n\ + Adds a directory to the top of the directory stack, or rotates\n\ + the stack, making the new top of the stack the current working\n\ + directory. With no arguments, exchanges the top two directories.\n\ + \n\ + Options:\n\ + -n Suppresses the normal change of directory when adding\n\ + directories to the stack, so only the stack is manipulated.\n\ + \n\ + Arguments:\n\ + +N Rotates the stack so that the Nth directory (counting\n\ + from the left of the list shown by `dirs', starting with\n\ + zero) is at the top.\n\ + \n\ + -N Rotates the stack so that the Nth directory (counting\n\ + from the right of the list shown by `dirs', starting with\n\ + zero) is at the top.\n\ + \n\ + dir Adds DIR to the directory stack at the top, making it the\n\ + new current working directory.\n\ + \n\ + The `dirs' builtin displays the directory stack.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid argument is supplied or the directory\n\ + change fails."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* PUSHD_AND_POPD */ +#if defined (PUSHD_AND_POPD) +char * const popd_doc[] = { +#if defined (HELP_BUILTIN) +N_("Remove directories from stack.\n\ + \n\ + Removes entries from the directory stack. With no arguments, removes\n\ + the top directory from the stack, and changes to the new top directory.\n\ + \n\ + Options:\n\ + -n Suppresses the normal change of directory when removing\n\ + directories from the stack, so only the stack is manipulated.\n\ + \n\ + Arguments:\n\ + +N Removes the Nth entry counting from the left of the list\n\ + shown by `dirs', starting with zero. For example: `popd +0'\n\ + removes the first directory, `popd +1' the second.\n\ + \n\ + -N Removes the Nth entry counting from the right of the list\n\ + shown by `dirs', starting with zero. For example: `popd -0'\n\ + removes the last directory, `popd -1' the next to last.\n\ + \n\ + The `dirs' builtin displays the directory stack.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid argument is supplied or the directory\n\ + change fails."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* PUSHD_AND_POPD */ +#if defined (PUSHD_AND_POPD) +char * const dirs_doc[] = { +#if defined (HELP_BUILTIN) +N_("Display directory stack.\n\ + \n\ + Display the list of currently remembered directories. Directories\n\ + find their way onto the list with the `pushd' command; you can get\n\ + back up through the list with the `popd' command.\n\ + \n\ + Options:\n\ + -c clear the directory stack by deleting all of the elements\n\ + -l do not print tilde-prefixed versions of directories relative\n\ + to your home directory\n\ + -p print the directory stack with one entry per line\n\ + -v print the directory stack with one entry per line prefixed\n\ + with its position in the stack\n\ + \n\ + Arguments:\n\ + +N Displays the Nth entry counting from the left of the list\n\ + shown by dirs when invoked without options, starting with\n\ + zero.\n\ + \n\ + -N Displays the Nth entry counting from the right of the list\n\ + shown by dirs when invoked without options, starting with\n\ + zero.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is supplied or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* PUSHD_AND_POPD */ +char * const shopt_doc[] = { +#if defined (HELP_BUILTIN) +N_("Set and unset shell options.\n\ + \n\ + Change the setting of each shell option OPTNAME. Without any option\n\ + arguments, list each supplied OPTNAME, or all shell options if no\n\ + OPTNAMEs are given, with an indication of whether or not each is set.\n\ + \n\ + Options:\n\ + -o restrict OPTNAMEs to those defined for use with `set -o'\n\ + -p print each shell option with an indication of its status\n\ + -q suppress output\n\ + -s enable (set) each OPTNAME\n\ + -u disable (unset) each OPTNAME\n\ + \n\ + Exit Status:\n\ + Returns success if OPTNAME is enabled; fails if an invalid option is\n\ + given or OPTNAME is disabled."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const printf_doc[] = { +#if defined (HELP_BUILTIN) +N_("Formats and prints ARGUMENTS under control of the FORMAT.\n\ + \n\ + Options:\n\ + -v var assign the output to shell variable VAR rather than\n\ + display it on the standard output\n\ + \n\ + FORMAT is a character string which contains three types of objects: plain\n\ + characters, which are simply copied to standard output; character escape\n\ + sequences, which are converted and copied to the standard output; and\n\ + format specifications, each of which causes printing of the next successive\n\ + argument.\n\ + \n\ + In addition to the standard format specifications described in printf(1),\n\ + printf interprets:\n\ + \n\ + %b expand backslash escape sequences in the corresponding argument\n\ + %q quote the argument in a way that can be reused as shell input\n\ + %Q like %q, but apply any precision to the unquoted argument before\n\ + quoting\n\ + %(fmt)T output the date-time string resulting from using FMT as a format\n\ + string for strftime(3)\n\ + \n\ + The format is re-used as necessary to consume all of the arguments. If\n\ + there are fewer arguments than the format requires, extra format\n\ + specifications behave as if a zero value or null string, as appropriate,\n\ + had been supplied.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is given or a write or assignment\n\ + error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#if defined (PROGRAMMABLE_COMPLETION) +char * const complete_doc[] = { +#if defined (HELP_BUILTIN) +N_("Specify how arguments are to be completed by Readline.\n\ + \n\ + For each NAME, specify how arguments are to be completed. If no options\n\ + are supplied, existing completion specifications are printed in a way that\n\ + allows them to be reused as input.\n\ + \n\ + Options:\n\ + -p print existing completion specifications in a reusable format\n\ + -r remove a completion specification for each NAME, or, if no\n\ + NAMEs are supplied, all completion specifications\n\ + -D apply the completions and actions as the default for commands\n\ + without any specific completion defined\n\ + -E apply the completions and actions to \"empty\" commands --\n\ + completion attempted on a blank line\n\ + -I apply the completions and actions to the initial (usually the\n\ + command) word\n\ + \n\ + When completion is attempted, the actions are applied in the order the\n\ + uppercase-letter options are listed above. If multiple options are supplied,\n\ + the -D option takes precedence over -E, and both take precedence over -I.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is supplied or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* PROGRAMMABLE_COMPLETION */ +#if defined (PROGRAMMABLE_COMPLETION) +char * const compgen_doc[] = { +#if defined (HELP_BUILTIN) +N_("Display possible completions depending on the options.\n\ + \n\ + Intended to be used from within a shell function generating possible\n\ + completions. If the optional WORD argument is supplied, matches against\n\ + WORD are generated.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is supplied or an error occurs."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* PROGRAMMABLE_COMPLETION */ +#if defined (PROGRAMMABLE_COMPLETION) +char * const compopt_doc[] = { +#if defined (HELP_BUILTIN) +N_("Modify or display completion options.\n\ + \n\ + Modify the completion options for each NAME, or, if no NAMEs are supplied,\n\ + the completion currently being executed. If no OPTIONs are given, print\n\ + the completion options for each NAME or the current completion specification.\n\ + \n\ + Options:\n\ + -o option Set completion option OPTION for each NAME\n\ + -D Change options for the \"default\" command completion\n\ + -E Change options for the \"empty\" command completion\n\ + -I Change options for completion on the initial word\n\ + \n\ + Using `+o' instead of `-o' turns off the specified option.\n\ + \n\ + Arguments:\n\ + \n\ + Each NAME refers to a command for which a completion specification must\n\ + have previously been defined using the `complete' builtin. If no NAMEs\n\ + are supplied, compopt must be called by a function currently generating\n\ + completions, and the options for that currently-executing completion\n\ + generator are modified.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is supplied or NAME does not\n\ + have a completion specification defined."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +#endif /* PROGRAMMABLE_COMPLETION */ +char * const mapfile_doc[] = { +#if defined (HELP_BUILTIN) +N_("Read lines from the standard input into an indexed array variable.\n\ + \n\ + Read lines from the standard input into the indexed array variable ARRAY, or\n\ + from file descriptor FD if the -u option is supplied. The variable MAPFILE\n\ + is the default ARRAY.\n\ + \n\ + Options:\n\ + -d delim Use DELIM to terminate lines, instead of newline\n\ + -n count Copy at most COUNT lines. If COUNT is 0, all lines are copied\n\ + -O origin Begin assigning to ARRAY at index ORIGIN. The default index is 0\n\ + -s count Discard the first COUNT lines read\n\ + -t Remove a trailing DELIM from each line read (default newline)\n\ + -u fd Read lines from file descriptor FD instead of the standard input\n\ + -C callback Evaluate CALLBACK each time QUANTUM lines are read\n\ + -c quantum Specify the number of lines read between each call to\n\ + CALLBACK\n\ + \n\ + Arguments:\n\ + ARRAY Array variable name to use for file data\n\ + \n\ + If -C is supplied without -c, the default quantum is 5000. When\n\ + CALLBACK is evaluated, it is supplied the index of the next array\n\ + element to be assigned and the line to be assigned to that element\n\ + as additional arguments.\n\ + \n\ + If not supplied with an explicit origin, mapfile will clear ARRAY before\n\ + assigning to it.\n\ + \n\ + Exit Status:\n\ + Returns success unless an invalid option is given or ARRAY is readonly or\n\ + not an indexed array."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; +char * const readarray_doc[] = { +#if defined (HELP_BUILTIN) +N_("Read lines from a file into an array variable.\n\ + \n\ + A synonym for `mapfile'."), +#endif /* HELP_BUILTIN */ + (char *)NULL +}; diff --git a/third_party/bash/builtins.h b/third_party/bash/builtins.h new file mode 100644 index 000000000..015659356 --- /dev/null +++ b/third_party/bash/builtins.h @@ -0,0 +1,68 @@ +/* builtins.h -- What a builtin looks like, and where to find them. */ + +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef BUILTINS_H +#define BUILTINS_H + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "command.h" +#include "general.h" + +#if defined (ALIAS) +#include "alias.h" +#endif + +/* Flags describing various things about a builtin. */ +#define BUILTIN_ENABLED 0x01 /* This builtin is enabled. */ +#define BUILTIN_DELETED 0x02 /* This has been deleted with enable -d. */ +#define STATIC_BUILTIN 0x04 /* This builtin is not dynamically loaded. */ +#define SPECIAL_BUILTIN 0x08 /* This is a Posix `special' builtin. */ +#define ASSIGNMENT_BUILTIN 0x10 /* This builtin takes assignment statements. */ +#define POSIX_BUILTIN 0x20 /* This builtins is special in the Posix command search order. */ +#define LOCALVAR_BUILTIN 0x40 /* This builtin creates local variables */ +#define ARRAYREF_BUILTIN 0x80 /* This builtin takes array references as arguments */ + +#define BASE_INDENT 4 + +/* The thing that we build the array of builtins out of. */ +struct builtin { + char *name; /* The name that the user types. */ + sh_builtin_func_t *function; /* The address of the invoked function. */ + int flags; /* One of the #defines above. */ + char * const *long_doc; /* NULL terminated array of strings. */ + const char *short_doc; /* Short version of documentation. */ + char *handle; /* for future use */ +}; + +/* Found in builtins.c, created by builtins/mkbuiltins. */ +extern int num_shell_builtins; /* Number of shell builtins. */ +extern struct builtin static_shell_builtins[]; +extern struct builtin *shell_builtins; +extern struct builtin *current_builtin; + +#endif /* BUILTINS_H */ diff --git a/third_party/bash/builtins_alias.c b/third_party/bash/builtins_alias.c new file mode 100644 index 000000000..8eb017ba3 --- /dev/null +++ b/third_party/bash/builtins_alias.c @@ -0,0 +1,192 @@ +/* alias.c, created from alias.def. */ +#line 42 "./alias.def" + +#include "config.h" + +#if defined (ALIAS) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +# include "bashansi.h" +# include "bashintl.h" + +# include +# include "shell.h" +# include "alias.h" +# include "common.h" +# include "bashgetopt.h" + +/* Flags for print_alias */ +#define AL_REUSABLE 0x01 + +static void print_alias PARAMS((alias_t *, int)); + +/* Hack the alias command in a Korn shell way. */ +int +alias_builtin (list) + WORD_LIST *list; +{ + int any_failed, offset, pflag, dflags; + alias_t **alias_list, *t; + char *name, *value; + + dflags = posixly_correct ? 0 : AL_REUSABLE; + pflag = 0; + reset_internal_getopt (); + while ((offset = internal_getopt (list, "p")) != -1) + { + switch (offset) + { + case 'p': + pflag = 1; + dflags |= AL_REUSABLE; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + if (list == 0 || pflag) + { + if (aliases == 0) + return (EXECUTION_SUCCESS); + + alias_list = all_aliases (); + + if (alias_list == 0) + return (EXECUTION_SUCCESS); + + for (offset = 0; alias_list[offset]; offset++) + print_alias (alias_list[offset], dflags); + + free (alias_list); /* XXX - Do not free the strings. */ + + if (list == 0) + return (sh_chkwrite (EXECUTION_SUCCESS)); + } + + any_failed = 0; + while (list) + { + name = list->word->word; + + for (offset = 0; name[offset] && name[offset] != '='; offset++) + ; + + if (offset && name[offset] == '=') + { + name[offset] = '\0'; + value = name + offset + 1; + + if (legal_alias_name (name, 0) == 0) + { + builtin_error (_("`%s': invalid alias name"), name); + any_failed++; + } + else + add_alias (name, value); + } + else + { + t = find_alias (name); + if (t) + print_alias (t, dflags); + else + { + sh_notfound (name); + any_failed++; + } + } + list = list->next; + } + + return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} +#endif /* ALIAS */ + +#line 166 "./alias.def" + +#if defined (ALIAS) +/* Remove aliases named in LIST from the aliases database. */ +int +unalias_builtin (list) + register WORD_LIST *list; +{ + register alias_t *alias; + int opt, aflag; + + aflag = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "a")) != -1) + { + switch (opt) + { + case 'a': + aflag = 1; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + if (aflag) + { + delete_all_aliases (); + return (EXECUTION_SUCCESS); + } + + if (list == 0) + { + builtin_usage (); + return (EX_USAGE); + } + + aflag = 0; + while (list) + { + alias = find_alias (list->word->word); + + if (alias) + remove_alias (alias->name); + else + { + sh_notfound (list->word->word); + aflag++; + } + + list = list->next; + } + + return (aflag ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} + +/* Output ALIAS in such a way as to allow it to be read back in. */ +static void +print_alias (alias, flags) + alias_t *alias; + int flags; +{ + char *value; + + value = sh_single_quote (alias->value); + if (flags & AL_REUSABLE) + printf ("alias %s", (alias->name && alias->name[0] == '-') ? "-- " : ""); + printf ("%s=%s\n", alias->name, value); + free (value); + + fflush (stdout); +} +#endif /* ALIAS */ diff --git a/third_party/bash/builtins_bind.c b/third_party/bash/builtins_bind.c new file mode 100644 index 000000000..1fe36ab9a --- /dev/null +++ b/third_party/bash/builtins_bind.c @@ -0,0 +1,349 @@ +/* bind.c, created from bind.def. */ +#line 22 "./bind.def" + +#include "config.h" + +#line 63 "./bind.def" + +#if defined (READLINE) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#include "third_party/readline/readline.h" +#include "third_party/readline/history.h" + +#include "bashintl.h" + +#include "shell.h" +#include "bashline.h" +#include "bashgetopt.h" +#include "common.h" + +static int query_bindings PARAMS((char *)); +static int unbind_command PARAMS((char *)); +static int unbind_keyseq PARAMS((char *)); + +#define BIND_RETURN(x) do { return_code = x; goto bind_exit; } while (0) + +#define LFLAG 0x0001 +#define PFLAG 0x0002 +#define FFLAG 0x0004 +#define VFLAG 0x0008 +#define QFLAG 0x0010 +#define MFLAG 0x0020 +#define RFLAG 0x0040 +#define PPFLAG 0x0080 +#define VVFLAG 0x0100 +#define SFLAG 0x0200 +#define SSFLAG 0x0400 +#define UFLAG 0x0800 +#define XFLAG 0x1000 +#define XXFLAG 0x2000 + +int +bind_builtin (list) + WORD_LIST *list; +{ + int return_code; + Keymap kmap, saved_keymap; + int flags, opt; + char *initfile, *map_name, *fun_name, *unbind_name, *remove_seq, *cmd_seq, *t; + + if (no_line_editing) + { +#if 0 + builtin_error (_("line editing not enabled")); + return (EXECUTION_FAILURE); +#else + builtin_warning (_("line editing not enabled")); +#endif + } + + kmap = saved_keymap = (Keymap) NULL; + flags = 0; + initfile = map_name = fun_name = unbind_name = remove_seq = cmd_seq = (char *)NULL; + return_code = EXECUTION_SUCCESS; + + if (bash_readline_initialized == 0) + initialize_readline (); + + begin_unwind_frame ("bind_builtin"); + unwind_protect_var (rl_outstream); + + rl_outstream = stdout; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "lvpVPsSXf:q:u:m:r:x:")) != -1) + { + switch (opt) + { + case 'l': + flags |= LFLAG; + break; + case 'v': + flags |= VFLAG; + break; + case 'p': + flags |= PFLAG; + break; + case 'f': + flags |= FFLAG; + initfile = list_optarg; + break; + case 'm': + flags |= MFLAG; + map_name = list_optarg; + break; + case 'q': + flags |= QFLAG; + fun_name = list_optarg; + break; + case 'u': + flags |= UFLAG; + unbind_name = list_optarg; + break; + case 'r': + flags |= RFLAG; + remove_seq = list_optarg; + break; + case 'V': + flags |= VVFLAG; + break; + case 'P': + flags |= PPFLAG; + break; + case 's': + flags |= SFLAG; + break; + case 'S': + flags |= SSFLAG; + break; + case 'x': + flags |= XFLAG; + cmd_seq = list_optarg; + break; + case 'X': + flags |= XXFLAG; + break; + case GETOPT_HELP: + default: + builtin_usage (); + BIND_RETURN (EX_USAGE); + } + } + + list = loptend; + + /* First, see if we need to install a special keymap for this + command. Then start on the arguments. */ + + if ((flags & MFLAG) && map_name) + { + kmap = rl_get_keymap_by_name (map_name); + if (kmap == 0) + { + builtin_error (_("`%s': invalid keymap name"), map_name); + BIND_RETURN (EXECUTION_FAILURE); + } + } + + if (kmap) + { + saved_keymap = rl_get_keymap (); + rl_set_keymap (kmap); + } + + /* XXX - we need to add exclusive use tests here. It doesn't make sense + to use some of these options together. */ + /* Now hack the option arguments */ + if (flags & LFLAG) + rl_list_funmap_names (); + + if (flags & PFLAG) + rl_function_dumper (1); + + if (flags & PPFLAG) + rl_function_dumper (0); + + if (flags & SFLAG) + rl_macro_dumper (1); + + if (flags & SSFLAG) + rl_macro_dumper (0); + + if (flags & VFLAG) + rl_variable_dumper (1); + + if (flags & VVFLAG) + rl_variable_dumper (0); + + if ((flags & FFLAG) && initfile) + { + if (rl_read_init_file (initfile) != 0) + { + t = printable_filename (initfile, 0); + builtin_error (_("%s: cannot read: %s"), t, strerror (errno)); + if (t != initfile) + free (t); + BIND_RETURN (EXECUTION_FAILURE); + } + } + + if ((flags & QFLAG) && fun_name) + return_code = query_bindings (fun_name); + + if ((flags & UFLAG) && unbind_name) + return_code = unbind_command (unbind_name); + + if ((flags & RFLAG) && remove_seq) + { + opt = unbind_keyseq (remove_seq); + BIND_RETURN (opt); + } + + if (flags & XFLAG) + return_code = bind_keyseq_to_unix_command (cmd_seq); + + if (flags & XXFLAG) + return_code = print_unix_command_map (); + + /* Process the rest of the arguments as binding specifications. */ + while (list) + { + int olen, nlen, d, i; + char **obindings, **nbindings; + + obindings = rl_invoking_keyseqs (bash_execute_unix_command); + olen = obindings ? strvec_len (obindings) : 0; + + rl_parse_and_bind (list->word->word); + + nbindings = rl_invoking_keyseqs (bash_execute_unix_command); + nlen = nbindings ? strvec_len (nbindings) : 0; + + if (nlen < olen) /* fewer bind -x bindings */ + for (d = olen - nlen, i = 0; i < olen && d > 0; i++) + if (nlen == 0 || strvec_search (nbindings, obindings[i]) < 0) + { + unbind_unix_command (obindings[i]); + d--; + } + + strvec_dispose (obindings); + strvec_dispose (nbindings); + + list = list->next; + } + + bind_exit: + if (saved_keymap) + rl_set_keymap (saved_keymap); + + run_unwind_frame ("bind_builtin"); + + if (return_code < 0) + return_code = EXECUTION_FAILURE; + + return (sh_chkwrite (return_code)); +} + +static int +query_bindings (name) + char *name; +{ + rl_command_func_t *function; + char **keyseqs; + int j; + + function = rl_named_function (name); + if (function == 0) + { + builtin_error (_("`%s': unknown function name"), name); + return EXECUTION_FAILURE; + } + + keyseqs = rl_invoking_keyseqs (function); + + if (!keyseqs) + { + printf (_("%s is not bound to any keys.\n"), name); + return EXECUTION_FAILURE; + } + + printf (_("%s can be invoked via "), name); + for (j = 0; j < 5 && keyseqs[j]; j++) + printf ("\"%s\"%s", keyseqs[j], keyseqs[j + 1] ? ", " : ".\n"); + if (keyseqs[j]) + printf ("...\n"); + strvec_dispose (keyseqs); + return EXECUTION_SUCCESS; +} + +static int +unbind_command (name) + char *name; +{ + rl_command_func_t *function; + + function = rl_named_function (name); + if (function == 0) + { + builtin_error (_("`%s': unknown function name"), name); + return EXECUTION_FAILURE; + } + + rl_unbind_function_in_map (function, rl_get_keymap ()); + return EXECUTION_SUCCESS; +} + +static int +unbind_keyseq (seq) + char *seq; +{ + char *kseq; + int kslen, type; + rl_command_func_t *f; + + kseq = (char *)xmalloc ((2 * strlen (seq)) + 1); + if (rl_translate_keyseq (seq, kseq, &kslen)) + { + free (kseq); + builtin_error (_("`%s': cannot unbind"), seq); + return EXECUTION_FAILURE; + } + if ((f = rl_function_of_keyseq_len (kseq, kslen, (Keymap)0, &type)) == 0) + { + free (kseq); + return (EXECUTION_SUCCESS); + } + if (type == ISKMAP) + f = ((Keymap) f)[ANYOTHERKEY].function; + + /* I wish this didn't have to translate the key sequence again, but readline + doesn't have a binding function that takes a translated key sequence as + an argument. */ + if (rl_bind_keyseq (seq, (rl_command_func_t *)NULL) != 0) + { + free (kseq); + builtin_error (_("`%s': cannot unbind"), seq); + return (EXECUTION_FAILURE); + } + + if (f == bash_execute_unix_command) + unbind_unix_command (seq); + + free (kseq); + return (EXECUTION_SUCCESS); +} +#endif /* READLINE */ diff --git a/third_party/bash/builtins_break.c b/third_party/bash/builtins_break.c new file mode 100644 index 000000000..827b1a556 --- /dev/null +++ b/third_party/bash/builtins_break.c @@ -0,0 +1,104 @@ +/* break.c, created from break.def. */ +#line 22 "./break.def" + +#line 34 "./break.def" +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "common.h" + +static int check_loop_level PARAMS((void)); + +/* The depth of while's and until's. */ +int loop_level = 0; + +/* Non-zero when a "break" instruction is encountered. */ +int breaking = 0; + +/* Non-zero when we have encountered a continue instruction. */ +int continuing = 0; + +/* Set up to break x levels, where x defaults to 1, but can be specified + as the first argument. */ +int +break_builtin (list) + WORD_LIST *list; +{ + intmax_t newbreak; + + CHECK_HELPOPT (list); + + if (check_loop_level () == 0) + return (EXECUTION_SUCCESS); + + (void)get_numeric_arg (list, 1, &newbreak); + + if (newbreak <= 0) + { + sh_erange (list->word->word, _("loop count")); + breaking = loop_level; + return (EXECUTION_FAILURE); + } + + if (newbreak > loop_level) + newbreak = loop_level; + + breaking = newbreak; + + return (EXECUTION_SUCCESS); +} + +#line 101 "./break.def" + +/* Set up to continue x levels, where x defaults to 1, but can be specified + as the first argument. */ +int +continue_builtin (list) + WORD_LIST *list; +{ + intmax_t newcont; + + CHECK_HELPOPT (list); + + if (check_loop_level () == 0) + return (EXECUTION_SUCCESS); + + (void)get_numeric_arg (list, 1, &newcont); + + if (newcont <= 0) + { + sh_erange (list->word->word, _("loop count")); + breaking = loop_level; + return (EXECUTION_FAILURE); + } + + if (newcont > loop_level) + newcont = loop_level; + + continuing = newcont; + + return (EXECUTION_SUCCESS); +} + +/* Return non-zero if a break or continue command would be okay. + Print an error message if break or continue is meaningless here. */ +static int +check_loop_level () +{ +#if defined (BREAK_COMPLAINS) + if (loop_level == 0 && posixly_correct == 0) + builtin_error (_("only meaningful in a `for', `while', or `until' loop")); +#endif /* BREAK_COMPLAINS */ + + return (loop_level); +} diff --git a/third_party/bash/builtins_builtin.c b/third_party/bash/builtins_builtin.c new file mode 100644 index 000000000..2029376cc --- /dev/null +++ b/third_party/bash/builtins_builtin.c @@ -0,0 +1,54 @@ +/* builtin.c, created from builtin.def. */ +#line 22 "./builtin.def" + +#line 36 "./builtin.def" +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "shell.h" +#include "execute_cmd.h" +#include "common.h" +#include "bashgetopt.h" + +/* Run the command mentioned in list directly, without going through the + normal alias/function/builtin/filename lookup process. */ +int +builtin_builtin (list) + WORD_LIST *list; +{ + sh_builtin_func_t *function; + register char *command; + + if (no_options (list)) + return (EX_USAGE); + list = loptend; /* skip over possible `--' */ + + if (list == 0) + return (EXECUTION_SUCCESS); + + command = list->word->word; +#if defined (DISABLED_BUILTINS) + function = builtin_address (command); +#else /* !DISABLED_BUILTINS */ + function = find_shell_builtin (command); +#endif /* !DISABLED_BUILTINS */ + + if (function == 0) + { + sh_notbuiltin (command); + return (EXECUTION_FAILURE); + } + else + { + this_command_name = command; + this_shell_builtin = function; /* overwrite "builtin" as this builtin */ + list = list->next; + return ((*function) (list)); + } +} diff --git a/third_party/bash/builtins_caller.c b/third_party/bash/builtins_caller.c new file mode 100644 index 000000000..accb9455e --- /dev/null +++ b/third_party/bash/builtins_caller.c @@ -0,0 +1,120 @@ +/* caller.c, created from caller.def. */ +#line 23 "./caller.def" + +#line 41 "./caller.def" + +#include "config.h" +#include +#include "chartypes.h" +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include + +#include "bashintl.h" + +#include "shell.h" +#include "common.h" +#include "builtext.h" +#include "bashgetopt.h" + +#ifdef LOADABLE_BUILTIN +# include "builtins.h" +#endif + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +int +caller_builtin (list) + WORD_LIST *list; +{ +#if !defined (ARRAY_VARS) + printf ("1 NULL\n"); + return (EXECUTION_FAILURE); +#else + SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v; + ARRAY *funcname_a, *bash_source_a, *bash_lineno_a; + char *funcname_s, *source_s, *lineno_s; + intmax_t num; + + CHECK_HELPOPT (list); + + GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a); + GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a); + GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a); + + if (bash_lineno_a == 0 || array_empty (bash_lineno_a)) + return (EXECUTION_FAILURE); + + if (bash_source_a == 0 || array_empty (bash_source_a)) + return (EXECUTION_FAILURE); + + if (no_options (list)) + return (EX_USAGE); + list = loptend; /* skip over possible `--' */ + + /* If there is no argument list, then give short form: line filename. */ + if (list == 0) + { + lineno_s = array_reference (bash_lineno_a, 0); + source_s = array_reference (bash_source_a, 1); + printf("%s %s\n", lineno_s ? lineno_s : "NULL", source_s ? source_s : "NULL"); + return (EXECUTION_SUCCESS); + } + + if (funcname_a == 0 || array_empty (funcname_a)) + return (EXECUTION_FAILURE); + + if (legal_number (list->word->word, &num)) + { + lineno_s = array_reference (bash_lineno_a, num); + source_s = array_reference (bash_source_a, num+1); + funcname_s = array_reference (funcname_a, num+1); + + if (lineno_s == NULL|| source_s == NULL || funcname_s == NULL) + return (EXECUTION_FAILURE); + + printf("%s %s %s\n", lineno_s, funcname_s, source_s); + } + else + { + sh_invalidnum (list->word->word); + builtin_usage (); + return (EX_USAGE); + } + + return (EXECUTION_SUCCESS); +#endif +} + +#ifdef LOADABLE_BUILTIN +static char *caller_doc[] = { +N_("Returns the context of the current subroutine call.\n\ + \n\ + Without EXPR, returns \"$line $filename\". With EXPR, returns\n\ + \"$line $subroutine $filename\"; this extra information can be used to\n\ + provide a stack trace.\n\ + \n\ + The value of EXPR indicates how many call frames to go back before the\n\ + current one; the top frame is frame 0."), + (char *)NULL +}; + +struct builtin caller_struct = { + "caller", + caller_builtin, + BUILTIN_ENABLED, + caller_doc, + "caller [EXPR]", + 0 +}; + +#endif /* LOADABLE_BUILTIN */ diff --git a/third_party/bash/builtins_cd.c b/third_party/bash/builtins_cd.c new file mode 100644 index 000000000..29f3b735d --- /dev/null +++ b/third_party/bash/builtins_cd.c @@ -0,0 +1,613 @@ +/* cd.c, created from cd.def. */ +#line 22 "./cd.def" +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashtypes.h" +#include "posixdir.h" +#include "posixstat.h" +#if defined (HAVE_SYS_PARAM_H) +#include +#endif +#include + +#include + +#include "bashansi.h" +#include "bashintl.h" + +#include +#include "tilde.h" + +#include "shell.h" +#include "flags.h" +#include "maxpath.h" +#include "common.h" +#include "bashgetopt.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern const char * const bash_getcwd_errstr; + +static int bindpwd PARAMS((int)); +static int setpwd PARAMS((char *)); +static char *resetpwd PARAMS((char *)); +static int change_to_directory PARAMS((char *, int, int)); + +static int cdxattr PARAMS((char *, char **)); +static void resetxattr PARAMS((void)); + +/* Change this to 1 to get cd spelling correction by default. */ +int cdspelling = 0; + +int cdable_vars; + +static int eflag; /* file scope so bindpwd() can see it */ +static int xattrflag; /* O_XATTR support for openat */ +static int xattrfd = -1; + +#line 115 "./cd.def" + +/* Just set $PWD, don't change OLDPWD. Used by `pwd -P' in posix mode. */ +static int +setpwd (dirname) + char *dirname; +{ + int old_anm; + SHELL_VAR *tvar; + + old_anm = array_needs_making; + tvar = bind_variable ("PWD", dirname ? dirname : "", 0); + if (tvar && readonly_p (tvar)) + return EXECUTION_FAILURE; + if (tvar && old_anm == 0 && array_needs_making && exported_p (tvar)) + { + update_export_env_inplace ("PWD=", 4, dirname ? dirname : ""); + array_needs_making = 0; + } + return EXECUTION_SUCCESS; +} + +static int +bindpwd (no_symlinks) + int no_symlinks; +{ + char *dirname, *pwdvar; + int old_anm, r, canon_failed; + SHELL_VAR *tvar; + + r = sh_chkwrite (EXECUTION_SUCCESS); + +#define tcwd the_current_working_directory + dirname = tcwd ? (no_symlinks ? sh_physpath (tcwd, 0) : tcwd) + : get_working_directory ("cd"); +#undef tcwd + + /* If canonicalization fails, reset dirname to the_current_working_directory */ + canon_failed = 0; + if (dirname == 0) + { + canon_failed = 1; + dirname = the_current_working_directory; + } + + old_anm = array_needs_making; + pwdvar = get_string_value ("PWD"); + + tvar = bind_variable ("OLDPWD", pwdvar, 0); + if (tvar && readonly_p (tvar)) + r = EXECUTION_FAILURE; + + if (old_anm == 0 && array_needs_making && exported_p (tvar)) + { + update_export_env_inplace ("OLDPWD=", 7, pwdvar); + array_needs_making = 0; + } + + if (setpwd (dirname) == EXECUTION_FAILURE) + r = EXECUTION_FAILURE; + if (canon_failed && eflag) + r = EXECUTION_FAILURE; + + if (dirname && dirname != the_current_working_directory) + free (dirname); + + return (r); +} + +/* Call get_working_directory to reset the value of + the_current_working_directory () */ +static char * +resetpwd (caller) + char *caller; +{ + char *tdir; + + FREE (the_current_working_directory); + the_current_working_directory = (char *)NULL; + tdir = get_working_directory (caller); + return (tdir); +} + +static int +cdxattr (dir, ndirp) + char *dir; /* don't assume we can always free DIR */ + char **ndirp; /* return new constructed directory name */ +{ +#if defined (O_XATTR) + int apfd, fd, r, e; + char buf[11+40+40]; /* construct new `fake' path for pwd */ + + apfd = openat (AT_FDCWD, dir, O_RDONLY|O_NONBLOCK); + if (apfd < 0) + return -1; + fd = openat (apfd, ".", O_XATTR); + e = errno; + close (apfd); /* ignore close error for now */ + errno = e; + if (fd < 0) + return -1; + r = fchdir (fd); /* assume fchdir exists everywhere with O_XATTR */ + if (r < 0) + { + close (fd); + return -1; + } + /* NFSv4 and ZFS extended attribute directories do not have names which are + visible in the standard Unix directory tree structure. To ensure we have + a valid name for $PWD, we synthesize one under /proc, but to keep that + path valid, we need to keep the file descriptor open as long as we are in + this directory. This imposes a certain structure on /proc. */ + if (ndirp) + { + sprintf (buf, "/proc/%d/fd/%d", getpid(), fd); + *ndirp = savestring (buf); + } + + if (xattrfd >= 0) + close (xattrfd); + xattrfd = fd; + + return r; +#else + return -1; +#endif +} + +/* Clean up the O_XATTR baggage. Currently only closes xattrfd */ +static void +resetxattr () +{ +#if defined (O_XATTR) + if (xattrfd >= 0) + { + close (xattrfd); + xattrfd = -1; + } +#else + xattrfd = -1; /* not strictly necessary */ +#endif +} + +#define LCD_DOVARS 0x001 +#define LCD_DOSPELL 0x002 +#define LCD_PRINTPATH 0x004 +#define LCD_FREEDIRNAME 0x008 + +/* This builtin is ultimately the way that all user-visible commands should + change the current working directory. It is called by cd_to_string (), + so the programming interface is simple, and it handles errors and + restrictions properly. */ +int +cd_builtin (list) + WORD_LIST *list; +{ + char *dirname, *cdpath, *path, *temp; + int path_index, no_symlinks, opt, lflag, e; + +#if defined (RESTRICTED_SHELL) + if (restricted) + { + sh_restricted ((char *)NULL); + return (EXECUTION_FAILURE); + } +#endif /* RESTRICTED_SHELL */ + + eflag = 0; + no_symlinks = no_symbolic_links; + xattrflag = 0; + reset_internal_getopt (); +#if defined (O_XATTR) + while ((opt = internal_getopt (list, "eLP@")) != -1) +#else + while ((opt = internal_getopt (list, "eLP")) != -1) +#endif + { + switch (opt) + { + case 'P': + no_symlinks = 1; + break; + case 'L': + no_symlinks = 0; + break; + case 'e': + eflag = 1; + break; +#if defined (O_XATTR) + case '@': + xattrflag = 1; + break; +#endif + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + lflag = (cdable_vars ? LCD_DOVARS : 0) | + ((interactive && cdspelling) ? LCD_DOSPELL : 0); + if (eflag && no_symlinks == 0) + eflag = 0; + + if (list == 0) + { + /* `cd' without arguments is equivalent to `cd $HOME' */ + dirname = get_string_value ("HOME"); + + if (dirname == 0) + { + builtin_error (_("HOME not set")); + return (EXECUTION_FAILURE); + } + lflag = 0; + } +#if defined (CD_COMPLAINS) + else if (list->next) + { + builtin_error (_("too many arguments")); + return (EXECUTION_FAILURE); + } +#endif +#if 0 + else if (list->word->word[0] == '\0') + { + builtin_error (_("null directory")); + return (EXECUTION_FAILURE); + } +#endif + else if (list->word->word[0] == '-' && list->word->word[1] == '\0') + { + /* This is `cd -', equivalent to `cd $OLDPWD' */ + dirname = get_string_value ("OLDPWD"); + + if (dirname == 0) + { + builtin_error (_("OLDPWD not set")); + return (EXECUTION_FAILURE); + } +#if 0 + lflag = interactive ? LCD_PRINTPATH : 0; +#else + lflag = LCD_PRINTPATH; /* According to SUSv3 */ +#endif + } + else if (absolute_pathname (list->word->word)) + dirname = list->word->word; + else if (privileged_mode == 0 && (cdpath = get_string_value ("CDPATH"))) + { + dirname = list->word->word; + + /* Find directory in $CDPATH. */ + path_index = 0; + while (path = extract_colon_unit (cdpath, &path_index)) + { + /* OPT is 1 if the path element is non-empty */ + opt = path[0] != '\0'; + temp = sh_makepath (path, dirname, MP_DOTILDE); + free (path); + + if (change_to_directory (temp, no_symlinks, xattrflag)) + { + /* POSIX.2 says that if a nonempty directory from CDPATH + is used to find the directory to change to, the new + directory name is echoed to stdout, whether or not + the shell is interactive. */ + if (opt && (path = no_symlinks ? temp : the_current_working_directory)) + printf ("%s\n", path); + + free (temp); +#if 0 + /* Posix.2 says that after using CDPATH, the resultant + value of $PWD will not contain `.' or `..'. */ + return (bindpwd (posixly_correct || no_symlinks)); +#else + return (bindpwd (no_symlinks)); +#endif + } + else + free (temp); + } + +#if 0 + /* changed for bash-4.2 Posix cd description steps 5-6 */ + /* POSIX.2 says that if `.' does not appear in $CDPATH, we don't + try the current directory, so we just punt now with an error + message if POSIXLY_CORRECT is non-zero. The check for cdpath[0] + is so we don't mistakenly treat a CDPATH value of "" as not + specifying the current directory. */ + if (posixly_correct && cdpath[0]) + { + builtin_error ("%s: %s", dirname, strerror (ENOENT)); + return (EXECUTION_FAILURE); + } +#endif + } + else + dirname = list->word->word; + + /* When we get here, DIRNAME is the directory to change to. If we + chdir successfully, just return. */ + if (change_to_directory (dirname, no_symlinks, xattrflag)) + { + if (lflag & LCD_PRINTPATH) + printf ("%s\n", dirname); + return (bindpwd (no_symlinks)); + } + + /* If the user requests it, then perhaps this is the name of + a shell variable, whose value contains the directory to + change to. */ + if (lflag & LCD_DOVARS) + { + temp = get_string_value (dirname); + if (temp && change_to_directory (temp, no_symlinks, xattrflag)) + { + printf ("%s\n", temp); + return (bindpwd (no_symlinks)); + } + } + + /* If the user requests it, try to find a directory name similar in + spelling to the one requested, in case the user made a simple + typo. This is similar to the UNIX 8th and 9th Edition shells. */ + if (lflag & LCD_DOSPELL) + { + temp = dirspell (dirname); + if (temp && change_to_directory (temp, no_symlinks, xattrflag)) + { + printf ("%s\n", temp); + free (temp); + return (bindpwd (no_symlinks)); + } + else + FREE (temp); + } + + e = errno; + temp = printable_filename (dirname, 0); + builtin_error ("%s: %s", temp, strerror (e)); + if (temp != dirname) + free (temp); + return (EXECUTION_FAILURE); +} + +#line 478 "./cd.def" + +/* Non-zero means that pwd always prints the physical directory, without + symbolic links. */ +static int verbatim_pwd; + +/* Print the name of the current working directory. */ +int +pwd_builtin (list) + WORD_LIST *list; +{ + char *directory; + int opt, pflag; + + verbatim_pwd = no_symbolic_links; + pflag = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "LP")) != -1) + { + switch (opt) + { + case 'P': + verbatim_pwd = pflag = 1; + break; + case 'L': + verbatim_pwd = 0; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + +#define tcwd the_current_working_directory + + directory = tcwd ? (verbatim_pwd ? sh_physpath (tcwd, 0) : tcwd) + : get_working_directory ("pwd"); + + /* Try again using getcwd() if canonicalization fails (for instance, if + the file system has changed state underneath bash). */ + if ((tcwd && directory == 0) || + (posixly_correct && same_file (".", tcwd, (struct stat *)0, (struct stat *)0) == 0)) + { + if (directory && directory != tcwd) + free (directory); + directory = resetpwd ("pwd"); + } + +#undef tcwd + + if (directory) + { + opt = EXECUTION_SUCCESS; + printf ("%s\n", directory); + /* This is dumb but posix-mandated. */ + if (posixly_correct && pflag) + opt = setpwd (directory); + if (directory != the_current_working_directory) + free (directory); + return (sh_chkwrite (opt)); + } + else + return (EXECUTION_FAILURE); +} + +/* Do the work of changing to the directory NEWDIR. Handle symbolic + link following, etc. This function *must* return with + the_current_working_directory either set to NULL (in which case + getcwd() will eventually be called), or set to a string corresponding + to the working directory. Return 1 on success, 0 on failure. */ + +static int +change_to_directory (newdir, nolinks, xattr) + char *newdir; + int nolinks, xattr; +{ + char *t, *tdir, *ndir; + int err, canon_failed, r, ndlen; + + tdir = (char *)NULL; + + if (the_current_working_directory == 0) + { + t = get_working_directory ("chdir"); + FREE (t); + } + + t = make_absolute (newdir, the_current_working_directory); + + /* TDIR is either the canonicalized absolute pathname of NEWDIR + (nolinks == 0) or the absolute physical pathname of NEWDIR + (nolinks != 0). */ + tdir = nolinks ? sh_physpath (t, 0) + : sh_canonpath (t, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + + ndlen = strlen (newdir); + + /* Use the canonicalized version of NEWDIR, or, if canonicalization + failed, use the non-canonical form. */ + canon_failed = 0; + if (tdir && *tdir) + free (t); + else + { + FREE (tdir); + tdir = t; + canon_failed = 1; + } + + /* In POSIX mode, if we're resolving symlinks logically and sh_canonpath + returns NULL (because it checks the path, it will return NULL if the + resolved path doesn't exist), fail immediately. */ +#if defined (ENAMETOOLONG) + if (posixly_correct && nolinks == 0 && canon_failed && (errno != ENAMETOOLONG || ndlen > PATH_MAX)) +#else + if (posixly_correct && nolinks == 0 && canon_failed && ndlen > PATH_MAX) +#endif + { +#if defined ENAMETOOLONG + if (errno != ENOENT && errno != ENAMETOOLONG) +#else + if (errno != ENOENT) +#endif + errno = ENOTDIR; + free (tdir); + return (0); + } + +#if defined (O_XATTR) + if (xattrflag) + { + r = cdxattr (nolinks ? newdir : tdir, &ndir); + if (r >= 0) + { + canon_failed = 0; + free (tdir); + tdir = ndir; + } + else + { + err = errno; + free (tdir); + errno = err; + return (0); /* no xattr */ + } + } + else +#endif + { + r = chdir (nolinks ? newdir : tdir); + if (r >= 0) + resetxattr (); + } + + /* If the chdir succeeds, update the_current_working_directory. */ + if (r == 0) + { + /* If canonicalization failed, but the chdir succeeded, reset the + shell's idea of the_current_working_directory. */ + if (canon_failed) + { + t = resetpwd ("cd"); + if (t == 0) + set_working_directory (tdir); + else + free (t); + } + else + set_working_directory (tdir); + + free (tdir); + return (1); + } + + /* We failed to change to the appropriate directory name. If we tried + what the user passed (nolinks != 0), punt now. */ + if (nolinks) + { + free (tdir); + return (0); + } + + err = errno; + + /* We're not in physical mode (nolinks == 0), but we failed to change to + the canonicalized directory name (TDIR). Try what the user passed + verbatim. If we succeed, reinitialize the_current_working_directory. + POSIX requires that we just fail here, so we do in posix mode. */ + if (posixly_correct == 0 && chdir (newdir) == 0) + { + t = resetpwd ("cd"); + if (t == 0) + set_working_directory (tdir); + else + free (t); + + r = 1; + } + else + { + errno = err; + r = 0; + } + + free (tdir); + return r; +} diff --git a/third_party/bash/builtins_colon.c b/third_party/bash/builtins_colon.c new file mode 100644 index 000000000..dcb10e91c --- /dev/null +++ b/third_party/bash/builtins_colon.c @@ -0,0 +1,33 @@ +/* colon.c, created from colon.def. */ +#line 22 "./colon.def" + +#line 34 "./colon.def" + +#line 43 "./colon.def" + +#line 52 "./colon.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "shell.h" + +/* Return a successful result. */ +int +colon_builtin (ignore) + WORD_LIST *ignore; +{ + return (0); +} + +/* Return an unsuccessful result. */ +int +false_builtin (ignore) + WORD_LIST *ignore; +{ + return (1); +} diff --git a/third_party/bash/builtins_command.c b/third_party/bash/builtins_command.c new file mode 100644 index 000000000..c41887ec9 --- /dev/null +++ b/third_party/bash/builtins_command.c @@ -0,0 +1,107 @@ +/* command.c, created from command.def. */ +#line 22 "./command.def" + +#line 41 "./command.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "flags.h" +#include "bashgetopt.h" +#include "common.h" + +#if defined (_CS_PATH) && defined (HAVE_CONFSTR) && !HAVE_DECL_CONFSTR +extern size_t confstr PARAMS((int, char *, size_t)); +#endif + +/* Run the commands mentioned in LIST without paying attention to shell + functions. */ +int +command_builtin (list) + WORD_LIST *list; +{ + int result, verbose, use_standard_path, opt; + COMMAND *command; + + verbose = use_standard_path = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "pvV")) != -1) + { + switch (opt) + { + case 'p': + use_standard_path = CDESC_STDPATH; + break; + case 'V': + verbose = CDESC_SHORTDESC|CDESC_ABSPATH; /* look in common.h for constants */ + break; + case 'v': + verbose = CDESC_REUSABLE; /* ditto */ + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if (list == 0) + return (EXECUTION_SUCCESS); + +#if defined (RESTRICTED_SHELL) + if (use_standard_path && restricted) + { + sh_restricted ("-p"); + return (EXECUTION_FAILURE); + } +#endif + + if (verbose) + { + int found, any_found; + + for (any_found = 0; list; list = list->next) + { + found = describe_command (list->word->word, verbose|use_standard_path); + + if (found == 0 && verbose != CDESC_REUSABLE) + sh_notfound (list->word->word); + + any_found += found; + } + + return (any_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + } + + begin_unwind_frame ("command_builtin"); + +#define COMMAND_BUILTIN_FLAGS (CMD_NO_FUNCTIONS | CMD_INHIBIT_EXPANSION | CMD_COMMAND_BUILTIN | (use_standard_path ? CMD_STDPATH : 0)) + + INTERNAL_DEBUG (("command_builtin: running execute_command for `%s'", list->word->word)); + + /* We don't want this to be reparsed (consider command echo 'foo &'), so + just make a simple_command structure and call execute_command with it. */ + command = make_bare_simple_command (); + command->value.Simple->words = (WORD_LIST *)copy_word_list (list); + command->value.Simple->redirects = (REDIRECT *)NULL; + command->flags |= COMMAND_BUILTIN_FLAGS; + command->value.Simple->flags |= COMMAND_BUILTIN_FLAGS; + + add_unwind_protect ((char *)dispose_command, command); + result = execute_command (command); + + run_unwind_frame ("command_builtin"); + + return (result); +} diff --git a/third_party/bash/builtins_complete.c b/third_party/bash/builtins_complete.c new file mode 100644 index 000000000..2309fe446 --- /dev/null +++ b/third_party/bash/builtins_complete.c @@ -0,0 +1,805 @@ +/* complete.c, created from complete.def. */ +#line 22 "./complete.def" + +#line 51 "./complete.def" + +#include "config.h" + +#include + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "builtins.h" +#include "pcomplete.h" +#include "bashline.h" + +#include "common.h" +#include "bashgetopt.h" + +#include "third_party/readline/readline.h" + +#define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) + +/* Structure containing all the non-action (binary) options; filled in by + build_actions(). */ +struct _optflags { + int pflag; + int rflag; + int Dflag; + int Eflag; + int Iflag; +}; + +static int find_compact PARAMS((char *)); +static int find_compopt PARAMS((char *)); + +static int build_actions PARAMS((WORD_LIST *, struct _optflags *, unsigned long *, unsigned long *)); + +static int remove_cmd_completions PARAMS((WORD_LIST *)); + +static int print_one_completion PARAMS((char *, COMPSPEC *)); +static int print_compitem PARAMS((BUCKET_CONTENTS *)); +static void print_compopts PARAMS((const char *, COMPSPEC *, int)); +static void print_all_completions PARAMS((void)); +static int print_cmd_completions PARAMS((WORD_LIST *)); + +static void print_compoptions PARAMS((unsigned long, int)); +static void print_compactions PARAMS((unsigned long)); +static void print_arg PARAMS((const char *, const char *, int)); +static void print_cmd_name PARAMS((const char *)); + +static char *Garg, *Warg, *Parg, *Sarg, *Xarg, *Farg, *Carg; + +static const struct _compacts { + const char * const actname; + unsigned long actflag; + int actopt; +} compacts[] = { + { "alias", CA_ALIAS, 'a' }, + { "arrayvar", CA_ARRAYVAR, 0 }, + { "binding", CA_BINDING, 0 }, + { "builtin", CA_BUILTIN, 'b' }, + { "command", CA_COMMAND, 'c' }, + { "directory", CA_DIRECTORY, 'd' }, + { "disabled", CA_DISABLED, 0 }, + { "enabled", CA_ENABLED, 0 }, + { "export", CA_EXPORT, 'e' }, + { "file", CA_FILE, 'f' }, + { "function", CA_FUNCTION, 0 }, + { "helptopic", CA_HELPTOPIC, 0 }, + { "hostname", CA_HOSTNAME, 0 }, + { "group", CA_GROUP, 'g' }, + { "job", CA_JOB, 'j' }, + { "keyword", CA_KEYWORD, 'k' }, + { "running", CA_RUNNING, 0 }, + { "service", CA_SERVICE, 's' }, + { "setopt", CA_SETOPT, 0 }, + { "shopt", CA_SHOPT, 0 }, + { "signal", CA_SIGNAL, 0 }, + { "stopped", CA_STOPPED, 0 }, + { "user", CA_USER, 'u' }, + { "variable", CA_VARIABLE, 'v' }, + { (char *)NULL, 0, 0 }, +}; + +/* This should be a STRING_INT_ALIST */ +static const struct _compopt { + const char * const optname; + unsigned long optflag; +} compopts[] = { + { "bashdefault", COPT_BASHDEFAULT }, + { "default", COPT_DEFAULT }, + { "dirnames", COPT_DIRNAMES }, + { "filenames",COPT_FILENAMES}, + { "noquote", COPT_NOQUOTE }, + { "nosort", COPT_NOSORT }, + { "nospace", COPT_NOSPACE }, + { "plusdirs", COPT_PLUSDIRS }, + { (char *)NULL, 0 }, +}; + +static int +find_compact (name) + char *name; +{ + register int i; + + for (i = 0; compacts[i].actname; i++) + if (STREQ (name, compacts[i].actname)) + return i; + return -1; +} + +static int +find_compopt (name) + char *name; +{ + register int i; + + for (i = 0; compopts[i].optname; i++) + if (STREQ (name, compopts[i].optname)) + return i; + return -1; +} + +/* Build the actions and compspec options from the options specified in LIST. + ACTP is a pointer to an unsigned long in which to place the bitmap of + actions. OPTP is a pointer to an unsigned long in which to place the + bitmap of compspec options (arguments to `-o'). PP, if non-null, gets 1 + if -p is supplied; RP, if non-null, gets 1 if -r is supplied. + If either is null, the corresponding option generates an error. + This also sets variables corresponding to options that take arguments as + a side effect; the caller should ensure that those variables are set to + NULL before calling build_actions. Return value: + EX_USAGE = bad option + EXECUTION_SUCCESS = some options supplied + EXECUTION_FAILURE = no options supplied +*/ + +static int +build_actions (list, flagp, actp, optp) + WORD_LIST *list; + struct _optflags *flagp; + unsigned long *actp, *optp; +{ + int opt, ind, opt_given; + unsigned long acts, copts; + WORD_DESC w; + + acts = copts = (unsigned long)0L; + opt_given = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "abcdefgjko:prsuvA:G:W:P:S:X:F:C:DEI")) != -1) + { + opt_given = 1; + switch (opt) + { + case 'r': + if (flagp) + { + flagp->rflag = 1; + break; + } + else + { + sh_invalidopt ("-r"); + builtin_usage (); + return (EX_USAGE); + } + + case 'p': + if (flagp) + { + flagp->pflag = 1; + break; + } + else + { + sh_invalidopt ("-p"); + builtin_usage (); + return (EX_USAGE); + } + + case 'a': + acts |= CA_ALIAS; + break; + case 'b': + acts |= CA_BUILTIN; + break; + case 'c': + acts |= CA_COMMAND; + break; + case 'd': + acts |= CA_DIRECTORY; + break; + case 'e': + acts |= CA_EXPORT; + break; + case 'f': + acts |= CA_FILE; + break; + case 'g': + acts |= CA_GROUP; + break; + case 'j': + acts |= CA_JOB; + break; + case 'k': + acts |= CA_KEYWORD; + break; + case 's': + acts |= CA_SERVICE; + break; + case 'u': + acts |= CA_USER; + break; + case 'v': + acts |= CA_VARIABLE; + break; + case 'o': + ind = find_compopt (list_optarg); + if (ind < 0) + { + sh_invalidoptname (list_optarg); + return (EX_USAGE); + } + copts |= compopts[ind].optflag; + break; + case 'A': + ind = find_compact (list_optarg); + if (ind < 0) + { + builtin_error (_("%s: invalid action name"), list_optarg); + return (EX_USAGE); + } + acts |= compacts[ind].actflag; + break; + case 'C': + Carg = list_optarg; + break; + case 'D': + if (flagp) + { + flagp->Dflag = 1; + break; + } + else + { + sh_invalidopt ("-D"); + builtin_usage (); + return (EX_USAGE); + } + case 'E': + if (flagp) + { + flagp->Eflag = 1; + break; + } + else + { + sh_invalidopt ("-E"); + builtin_usage (); + return (EX_USAGE); + } + case 'I': + if (flagp) + { + flagp->Iflag = 1; + break; + } + else + { + sh_invalidopt ("-I"); + builtin_usage (); + return (EX_USAGE); + } + case 'F': + w.word = Farg = list_optarg; + w.flags = 0; + if (check_identifier (&w, posixly_correct) == 0 || strpbrk (Farg, shell_break_chars) != 0) + { + sh_invalidid (Farg); + return (EX_USAGE); + } + break; + case 'G': + Garg = list_optarg; + break; + case 'P': + Parg = list_optarg; + break; + case 'S': + Sarg = list_optarg; + break; + case 'W': + Warg = list_optarg; + break; + case 'X': + Xarg = list_optarg; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + *actp = acts; + *optp = copts; + + return (opt_given ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +} + +/* Add, remove, and display completion specifiers. */ +int +complete_builtin (list) + WORD_LIST *list; +{ + int opt_given, rval; + unsigned long acts, copts; + COMPSPEC *cs; + struct _optflags oflags; + WORD_LIST *l, *wl; + + if (list == 0) + { + print_all_completions (); + return (EXECUTION_SUCCESS); + } + + opt_given = oflags.pflag = oflags.rflag = 0; + oflags.Dflag = oflags.Eflag = oflags.Iflag = 0; + + acts = copts = (unsigned long)0L; + Garg = Warg = Parg = Sarg = Xarg = Farg = Carg = (char *)NULL; + cs = (COMPSPEC *)NULL; + + /* Build the actions from the arguments. Also sets the [A-Z]arg variables + as a side effect if they are supplied as options. */ + rval = build_actions (list, &oflags, &acts, &copts); + if (rval == EX_USAGE) + return (rval); + opt_given = rval != EXECUTION_FAILURE; + + list = loptend; + + if (oflags.Dflag) + wl = make_word_list (make_bare_word (DEFAULTCMD), (WORD_LIST *)NULL); + else if (oflags.Eflag) + wl = make_word_list (make_bare_word (EMPTYCMD), (WORD_LIST *)NULL); + else if (oflags.Iflag) + wl = make_word_list (make_bare_word (INITIALWORD), (WORD_LIST *)NULL); + else + wl = (WORD_LIST *)NULL; + + /* -p overrides everything else */ + if (oflags.pflag || (list == 0 && opt_given == 0)) + { + if (wl) + { + rval = print_cmd_completions (wl); + dispose_words (wl); + return rval; + } + else if (list == 0) + { + print_all_completions (); + return (EXECUTION_SUCCESS); + } + return (print_cmd_completions (list)); + } + + /* next, -r overrides everything else. */ + if (oflags.rflag) + { + if (wl) + { + rval = remove_cmd_completions (wl); + dispose_words (wl); + return rval; + } + else if (list == 0) + { + progcomp_flush (); + return (EXECUTION_SUCCESS); + } + return (remove_cmd_completions (list)); + } + + if (wl == 0 && list == 0 && opt_given) + { + builtin_usage (); + return (EX_USAGE); + } + + /* If we get here, we need to build a compspec and add it for each + remaining argument. */ + cs = compspec_create (); + cs->actions = acts; + cs->options = copts; + + cs->globpat = STRDUP (Garg); + cs->words = STRDUP (Warg); + cs->prefix = STRDUP (Parg); + cs->suffix = STRDUP (Sarg); + cs->funcname = STRDUP (Farg); + cs->command = STRDUP (Carg); + cs->filterpat = STRDUP (Xarg); + + for (rval = EXECUTION_SUCCESS, l = wl ? wl : list ; l; l = l->next) + { + /* Add CS as the compspec for the specified commands. */ + if (progcomp_insert (l->word->word, cs) == 0) + rval = EXECUTION_FAILURE; + } + + dispose_words (wl); + return (rval); +} + +static int +remove_cmd_completions (list) + WORD_LIST *list; +{ + WORD_LIST *l; + int ret; + + for (ret = EXECUTION_SUCCESS, l = list; l; l = l->next) + { + if (progcomp_remove (l->word->word) == 0) + { + builtin_error (_("%s: no completion specification"), l->word->word); + ret = EXECUTION_FAILURE; + } + } + return ret; +} + +static void +print_compoptions (copts, full) + unsigned long copts; + int full; +{ + const struct _compopt *co; + + for (co = compopts; co->optname; co++) + if (copts & co->optflag) + printf ("-o %s ", co->optname); + else if (full) + printf ("+o %s ", co->optname); +} + +static void +print_compactions (acts) + unsigned long acts; +{ + const struct _compacts *ca; + + /* simple flags first */ + for (ca = compacts; ca->actname; ca++) + if (ca->actopt && (acts & ca->actflag)) + printf ("-%c ", ca->actopt); + + /* then the rest of the actions */ + for (ca = compacts; ca->actname; ca++) + if (ca->actopt == 0 && (acts & ca->actflag)) + printf ("-A %s ", ca->actname); +} + +static void +print_arg (arg, flag, quote) + const char *arg, *flag; + int quote; +{ + char *x; + + if (arg) + { + x = quote ? sh_single_quote (arg) : (char *)arg; + printf ("%s %s ", flag, x); + if (x != arg) + free (x); + } +} + +static void +print_cmd_name (cmd) + const char *cmd; +{ + char *x; + + if (STREQ (cmd, DEFAULTCMD)) + printf ("-D"); + else if (STREQ (cmd, EMPTYCMD)) + printf ("-E"); + else if (STREQ (cmd, INITIALWORD)) + printf ("-I"); + else if (*cmd == 0) /* XXX - can this happen? */ + printf ("''"); + else if (sh_contains_shell_metas (cmd)) + { + x = sh_single_quote (cmd); + printf ("%s", x); + free (x); + } + else + printf ("%s", cmd); +} + +static int +print_one_completion (cmd, cs) + char *cmd; + COMPSPEC *cs; +{ + printf ("complete "); + + print_compoptions (cs->options, 0); + print_compactions (cs->actions); + + /* now the rest of the arguments */ + + /* arguments that require quoting */ + print_arg (cs->globpat, "-G", 1); + print_arg (cs->words, "-W", 1); + print_arg (cs->prefix, "-P", 1); + print_arg (cs->suffix, "-S", 1); + print_arg (cs->filterpat, "-X", 1); + + print_arg (cs->command, "-C", 1); + + /* simple arguments that don't require quoting */ + print_arg (cs->funcname, "-F", sh_contains_shell_metas (cs->funcname) != 0); + + print_cmd_name (cmd); + printf ("\n"); + + return (0); +} + +static void +print_compopts (cmd, cs, full) + const char *cmd; + COMPSPEC *cs; + int full; +{ + printf ("compopt "); + + print_compoptions (cs->options, full); + print_cmd_name (cmd); + + printf ("\n"); +} + +static int +print_compitem (item) + BUCKET_CONTENTS *item; +{ + COMPSPEC *cs; + char *cmd; + + cmd = item->key; + cs = (COMPSPEC *)item->data; + + return (print_one_completion (cmd, cs)); +} + +static void +print_all_completions () +{ + progcomp_walk (print_compitem); +} + +static int +print_cmd_completions (list) + WORD_LIST *list; +{ + WORD_LIST *l; + COMPSPEC *cs; + int ret; + + for (ret = EXECUTION_SUCCESS, l = list; l; l = l->next) + { + cs = progcomp_search (l->word->word); + if (cs) + print_one_completion (l->word->word, cs); + else + { + builtin_error (_("%s: no completion specification"), l->word->word); + ret = EXECUTION_FAILURE; + } + } + + return (sh_chkwrite (ret)); +} + +#line 663 "./complete.def" + +int +compgen_builtin (list) + WORD_LIST *list; +{ + int rval; + unsigned long acts, copts; + COMPSPEC *cs; + STRINGLIST *sl; + char *word, **matches; + char *old_line; + int old_ind; + + if (list == 0) + return (EXECUTION_SUCCESS); + + acts = copts = (unsigned long)0L; + Garg = Warg = Parg = Sarg = Xarg = Farg = Carg = (char *)NULL; + cs = (COMPSPEC *)NULL; + + /* Build the actions from the arguments. Also sets the [A-Z]arg variables + as a side effect if they are supplied as options. */ + rval = build_actions (list, (struct _optflags *)NULL, &acts, &copts); + if (rval == EX_USAGE) + return (rval); + if (rval == EXECUTION_FAILURE) + return (EXECUTION_SUCCESS); + + list = loptend; + + word = (list && list->word) ? list->word->word : ""; + + if (Farg) + builtin_error (_("warning: -F option may not work as you expect")); + if (Carg) + builtin_error (_("warning: -C option may not work as you expect")); + + /* If we get here, we need to build a compspec and evaluate it. */ + cs = compspec_create (); + cs->actions = acts; + cs->options = copts; + cs->refcount = 1; + + cs->globpat = STRDUP (Garg); + cs->words = STRDUP (Warg); + cs->prefix = STRDUP (Parg); + cs->suffix = STRDUP (Sarg); + cs->funcname = STRDUP (Farg); + cs->command = STRDUP (Carg); + cs->filterpat = STRDUP (Xarg); + + rval = EXECUTION_FAILURE; + + /* probably don't have to save these, just being safe */ + old_line = pcomp_line; + old_ind = pcomp_ind; + pcomp_line = (char *)NULL; + pcomp_ind = 0; + sl = gen_compspec_completions (cs, "compgen", word, 0, 0, 0); + pcomp_line = old_line; + pcomp_ind = old_ind; + + /* If the compspec wants the bash default completions, temporarily + turn off programmable completion and call the bash completion code. */ + if ((sl == 0 || sl->list_len == 0) && (copts & COPT_BASHDEFAULT)) + { + matches = bash_default_completion (word, 0, 0, 0, 0); + sl = completions_to_stringlist (matches); + strvec_dispose (matches); + } + + /* This isn't perfect, but it's the best we can do, given what readline + exports from its set of completion utility functions. */ + if ((sl == 0 || sl->list_len == 0) && (copts & COPT_DEFAULT)) + { + matches = rl_completion_matches (word, rl_filename_completion_function); + strlist_dispose (sl); + sl = completions_to_stringlist (matches); + strvec_dispose (matches); + } + + if (sl) + { + if (sl->list && sl->list_len) + { + rval = EXECUTION_SUCCESS; + strlist_print (sl, (char *)NULL); + } + strlist_dispose (sl); + } + + compspec_dispose (cs); + return (rval); +} + +#line 788 "./complete.def" + +int +compopt_builtin (list) + WORD_LIST *list; +{ + int opts_on, opts_off, *opts, opt, oind, ret, Dflag, Eflag, Iflag; + WORD_LIST *l, *wl; + COMPSPEC *cs; + + opts_on = opts_off = Eflag = Dflag = Iflag = 0; + ret = EXECUTION_SUCCESS; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "+o:DEI")) != -1) + { + opts = (list_opttype == '-') ? &opts_on : &opts_off; + + switch (opt) + { + case 'o': + oind = find_compopt (list_optarg); + if (oind < 0) + { + sh_invalidoptname (list_optarg); + return (EX_USAGE); + } + *opts |= compopts[oind].optflag; + break; + case 'D': + Dflag = 1; + break; + case 'E': + Eflag = 1; + break; + case 'I': + Iflag = 1; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if (Dflag) + wl = make_word_list (make_bare_word (DEFAULTCMD), (WORD_LIST *)NULL); + else if (Eflag) + wl = make_word_list (make_bare_word (EMPTYCMD), (WORD_LIST *)NULL); + else if (Iflag) + wl = make_word_list (make_bare_word (INITIALWORD), (WORD_LIST *)NULL); + else + wl = (WORD_LIST *)NULL; + + if (list == 0 && wl == 0) + { + if (RL_ISSTATE (RL_STATE_COMPLETING) == 0 || pcomp_curcs == 0) + { + builtin_error (_("not currently executing completion function")); + return (EXECUTION_FAILURE); + } + cs = pcomp_curcs; + + if (opts_on == 0 && opts_off == 0) + { + print_compopts (pcomp_curcmd, cs, 1); + return (sh_chkwrite (ret)); + } + + /* Set the compspec options */ + pcomp_set_compspec_options (cs, opts_on, 1); + pcomp_set_compspec_options (cs, opts_off, 0); + + /* And change the readline variables the options control */ + pcomp_set_readline_variables (opts_on, 1); + pcomp_set_readline_variables (opts_off, 0); + + return (ret); + } + + for (l = wl ? wl : list; l; l = l->next) + { + cs = progcomp_search (l->word->word); + if (cs == 0) + { + builtin_error (_("%s: no completion specification"), l->word->word); + ret = EXECUTION_FAILURE; + continue; + } + if (opts_on == 0 && opts_off == 0) + { + print_compopts (l->word->word, cs, 1); + continue; /* XXX -- fill in later */ + } + + /* Set the compspec options */ + pcomp_set_compspec_options (cs, opts_on, 1); + pcomp_set_compspec_options (cs, opts_off, 0); + } + + if (wl) + dispose_words (wl); + + return (ret); +} diff --git a/third_party/bash/builtins_declare.c b/third_party/bash/builtins_declare.c new file mode 100644 index 000000000..77f9f8e35 --- /dev/null +++ b/third_party/bash/builtins_declare.c @@ -0,0 +1,969 @@ +/* declare.c, created from declare.def. */ +#line 22 "./declare.def" + +#line 64 "./declare.def" + +#line 72 "./declare.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "flags.h" +#include "common.h" +#include "builtext.h" +#include "bashgetopt.h" + +static SHELL_VAR *declare_find_variable PARAMS((const char *, int, int)); +static char *declare_build_newname PARAMS((char *, char *, int, char *, int)); +static char *declare_transform_name PARAMS((char *, int, int)); + +static int declare_internal PARAMS((register WORD_LIST *, int)); + +/* Declare or change variable attributes. */ +int +declare_builtin (list) + register WORD_LIST *list; +{ + return (declare_internal (list, 0)); +} + +#line 122 "./declare.def" +int +local_builtin (list) + register WORD_LIST *list; +{ + /* Catch a straight `local --help' before checking function context */ + if (list && list->word && STREQ (list->word->word, "--help")) + { + builtin_help (); + return (EX_USAGE); + } + + if (variable_context) + return (declare_internal (list, 1)); + else + { + builtin_error (_("can only be used in a function")); + return (EXECUTION_FAILURE); + } +} + +#if defined (ARRAY_VARS) +# define DECLARE_OPTS "+acfgilnprtuxAFGI" +#else +# define DECLARE_OPTS "+cfgilnprtuxFGI" +#endif + +static SHELL_VAR * +declare_find_variable (name, mkglobal, chklocal) + const char *name; + int mkglobal, chklocal; +{ + SHELL_VAR *var; + + if (mkglobal == 0) + return (find_variable (name)); + else if (chklocal) + { + var = find_variable (name); + if (var && local_p (var) && var->context == variable_context) + return var; + return (find_global_variable (name)); + } + else + return (find_global_variable (name)); +} + +/* Build a new string + NAME[SUBSCRIPT][[+]=VALUE] + from expanding a nameref into NAME */ +static char * +declare_build_newname (name, subscript_start, offset, value, aflags) + char *name, *subscript_start; + int offset; + char *value; + int aflags; +{ + size_t namelen, savelen; + char *ret; + + savelen = namelen = strlen (name); + if (subscript_start) + { + *subscript_start = '['; /* ] */ + namelen += strlen (subscript_start); + } + ret = xmalloc (namelen + 2 + strlen (value) + 1); + strcpy (ret, name); + if (subscript_start) + strcpy (ret + savelen, subscript_start); + if (offset) + { + if (aflags & ASS_APPEND) + ret[namelen++] = '+'; + ret[namelen++] = '='; + if (value && *value) + strcpy (ret + namelen, value); + else + ret[namelen] = '\0'; + } + + return (ret); +} + +static char * +declare_transform_name (name, flags_on, flags_off) + char *name; + int flags_on, flags_off; +{ + SHELL_VAR *var, *v; + char *newname; + + var = find_variable (name); + if (var == 0) + newname = nameref_transform_name (name, ASS_MKLOCAL); + else if ((flags_on & att_nameref) == 0 && (flags_off & att_nameref) == 0) + { + /* Ok, we're following namerefs here, so let's make sure that if + we followed one, it was at the same context (see below for + more details). */ + v = find_variable_last_nameref (name, 1); + newname = (v && v->context != variable_context) ? name : name_cell (var); + } + else + newname = name; /* dealing with nameref attribute */ + + return (newname); +} + +/* The workhorse function. */ +static int +declare_internal (list, local_var) + register WORD_LIST *list; + int local_var; +{ + int flags_on, flags_off, *flags; + int any_failed, assign_error, pflag, nodefs, opt, onref, offref; + int mkglobal, chklocal, inherit_flag; + char *t, *subscript_start; + SHELL_VAR *var, *refvar, *v; + FUNCTION_DEF *shell_fn; + + flags_on = flags_off = any_failed = assign_error = pflag = nodefs = 0; + mkglobal = chklocal = inherit_flag = 0; + refvar = (SHELL_VAR *)NULL; + reset_internal_getopt (); + while ((opt = internal_getopt (list, DECLARE_OPTS)) != -1) + { + flags = list_opttype == '+' ? &flags_off : &flags_on; + + /* If you add options here, see whether or not they need to be added to + the loop in subst.c:shell_expand_word_list() */ + switch (opt) + { + case 'a': +#if defined (ARRAY_VARS) + *flags |= att_array; + break; +#else + builtin_usage (); + return (EX_USAGE); +#endif + case 'A': +#if defined (ARRAY_VARS) + *flags |= att_assoc; + break; +#else + builtin_usage (); + return (EX_USAGE); +#endif + case 'p': + pflag++; + break; + case 'F': + nodefs++; + *flags |= att_function; + break; + case 'f': + *flags |= att_function; + break; + case 'G': + if (flags == &flags_on) + chklocal = 1; + /*FALLTHROUGH*/ + case 'g': + if (flags == &flags_on) + mkglobal = 1; + break; + case 'i': + *flags |= att_integer; + break; + case 'n': + *flags |= att_nameref; + break; + case 'r': + *flags |= att_readonly; + break; + case 't': + *flags |= att_trace; + break; + case 'x': + *flags |= att_exported; + array_needs_making = 1; + break; +#if defined (CASEMOD_ATTRS) +# if defined (CASEMOD_CAPCASE) + case 'c': + *flags |= att_capcase; + if (flags == &flags_on) + flags_off |= att_uppercase|att_lowercase; + break; +# endif + case 'l': + *flags |= att_lowercase; + if (flags == &flags_on) + flags_off |= att_capcase|att_uppercase; + break; + case 'u': + *flags |= att_uppercase; + if (flags == &flags_on) + flags_off |= att_capcase|att_lowercase; + break; +#endif /* CASEMOD_ATTRS */ + case 'I': + inherit_flag = MKLOC_INHERIT; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + /* If there are no more arguments left, then we just want to show + some variables. */ + if (list == 0) /* declare -[aAfFilnrtux] */ + { + /* Show local variables defined at this context level if this is + the `local' builtin. */ + if (local_var) + show_local_var_attributes (0, nodefs); /* XXX - fix up args later */ + else if (pflag && (flags_on == 0 || flags_on == att_function)) + show_all_var_attributes (flags_on == 0, nodefs); + else if (flags_on == 0) + return (set_builtin ((WORD_LIST *)NULL)); + else + set_or_show_attributes ((WORD_LIST *)NULL, flags_on, nodefs); + + return (sh_chkwrite (EXECUTION_SUCCESS)); + } + + if (pflag) /* declare -p [-aAfFilnrtux] [name ...] */ + { + for (any_failed = 0; list; list = list->next) + { + if (flags_on & att_function) + pflag = show_func_attributes (list->word->word, nodefs); + else if (local_var) + pflag = show_localname_attributes (list->word->word, nodefs); + else + pflag = show_name_attributes (list->word->word, nodefs); + if (pflag) + { + sh_notfound (list->word->word); + any_failed++; + } + } + return (sh_chkwrite (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS)); + } + + /* Some option combinations that don't make any sense */ + if ((flags_on & att_function) && (flags_on & (att_array|att_assoc|att_integer|att_nameref))) + { + char *optchar; + + if (flags_on & att_nameref) + optchar = "-n"; + else if (flags_on & att_integer) + optchar = "-i"; + else if (flags_on & att_assoc) + optchar = "-A"; + else if (flags_on & att_array) + optchar = "-a"; + + sh_invalidopt (optchar); + return (EXECUTION_FAILURE); + } + +#define NEXT_VARIABLE() free (name); list = list->next; continue + + /* There are arguments left, so we are making variables. */ + while (list) /* declare [-aAfFilnrtux] name[=value] [name[=value] ...] */ + { + char *value, *name, *newname; + int offset, aflags, wflags, created_var; + int assoc_noexpand; +#if defined (ARRAY_VARS) + int making_array_special, compound_array_assign, simple_array_assign; + int var_exists, array_exists, creating_array, array_subscript_assignment; +#endif + + name = savestring (list->word->word); + wflags = list->word->flags; +#if defined (ARRAY_VARS) + assoc_noexpand = assoc_expand_once && (wflags & W_ASSIGNMENT); +#else + assoc_noexpand = 0; +#endif + /* XXX - we allow unbalanced brackets if assoc_noexpand is set, we count + brackets and make sure they match if assoc_noexpand is not set. So we + need to make sure we're checking assoc_noexpand and expand_once_flag + for backwards compatibility. We also use assoc_noexpand below when + we call assign_array_element, so we need to make sure they're + consistent in how they count brackets. */ + offset = assignment (name, assoc_noexpand ? 2 : 0); + aflags = 0; + created_var = 0; + + if (local_var && variable_context && STREQ (name, "-")) + { + var = make_local_variable ("-", 0); + FREE (value_cell (var)); /* just in case */ + value = get_current_options (); + var_setvalue (var, value); + VSETATTR (var, att_invisible); + NEXT_VARIABLE (); + } + + /* If we are declaring a function, then complain about it in some way. + We don't let people make functions by saying `typeset -f foo=bar'. */ + + /* Can't define functions using assignment statements */ + if (offset && (flags_on & att_function)) /* declare -f [-rix] foo=bar */ + { + builtin_error (_("cannot use `-f' to make functions")); + free (name); + return (EXECUTION_FAILURE); + } + + /* There should be a way, however, to let people look at a particular + function definition by saying `typeset -f foo'. This is the only + place in this builtin where we deal with functions. */ + + if (flags_on & att_function) + { + /* Should we restrict this when the shell is in posix mode even if + the function was created before the shell entered posix mode? + Previous versions of the shell enforced the restriction. */ + if (posixly_correct && legal_identifier (name) == 0) + { + sh_invalidid (name); + assign_error++; + NEXT_VARIABLE (); + } + + var = find_function (name); + + if (var) + { + if (readonly_p (var) && (flags_off & att_readonly)) + { + builtin_error (_("%s: readonly function"), name); + any_failed++; + NEXT_VARIABLE (); + } + /* declare -[Ff] name [name...] */ + if (flags_on == att_function && flags_off == 0) + { +#if defined (DEBUGGER) + if (nodefs && debugging_mode) + { + shell_fn = find_function_def (name_cell (var)); + if (shell_fn) + printf ("%s %d %s\n", name_cell (var), shell_fn->line, shell_fn->source_file); + else + printf ("%s\n", name_cell (var)); + } + else +#endif /* DEBUGGER */ + { + t = nodefs ? name_cell (var) : named_function_string (name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL); + printf ("%s\n", t); + any_failed = sh_chkwrite (any_failed); + } + } + else /* declare -[fF] -[rx] name [name...] */ + { + VSETATTR (var, flags_on); + flags_off &= ~att_function; /* makes no sense */ + VUNSETATTR (var, flags_off); + } + } + else + any_failed++; + + NEXT_VARIABLE (); + } + + if (offset) /* declare [-aAfFirx] name=value */ + { + name[offset] = '\0'; + value = name + offset + 1; + if (name[offset - 1] == '+') + { + aflags |= ASS_APPEND; + name[offset - 1] = '\0'; + } + } + else + value = ""; + + /* Do some lexical error checking on the LHS and RHS of the assignment + that is specific to nameref variables. */ + if (flags_on & att_nameref) + { +#if defined (ARRAY_VARS) + if (valid_array_reference (name, 0)) + { + builtin_error (_("%s: reference variable cannot be an array"), name); + any_failed++; + NEXT_VARIABLE (); + } + else +#endif + /* disallow self references at global scope, warn at function scope */ + if (check_selfref (name, value, 0)) + { + if (variable_context == 0) + { + builtin_error (_("%s: nameref variable self references not allowed"), name); + assign_error++; /* XXX any_failed++ instead? */ + NEXT_VARIABLE (); + } + else + builtin_warning (_("%s: circular name reference"), name); + } + if (value && *value && (aflags & ASS_APPEND) == 0 && valid_nameref_value (value, 1) == 0) + { + builtin_error (_("`%s': invalid variable name for name reference"), value); + assign_error++; + NEXT_VARIABLE (); + } + } + +restart_new_var_name: + + /* The rest of the loop body deals with declare -[aAlinrtux] name [name...] + where each NAME can be an assignment statement. */ + + subscript_start = (char *)NULL; /* used below */ +#if defined (ARRAY_VARS) + /* Determine whether we are creating or assigning an array variable */ + var_exists = array_exists = creating_array = 0; + compound_array_assign = simple_array_assign = 0; + array_subscript_assignment = 0; + if (t = strchr (name, '[')) /* ] */ + { + /* If offset != 0 we have already validated any array reference + because assignment() calls skipsubscript() */ + if (offset == 0 && valid_array_reference (name, 0) == 0) + { + sh_invalidid (name); + assign_error++; + NEXT_VARIABLE (); + } + subscript_start = t; + *t = '\0'; + making_array_special = 1; /* XXX - should this check offset? */ + array_subscript_assignment = offset != 0; + } + else + making_array_special = 0; +#endif + + /* Ensure the argument is a valid, well-formed shell identifier. */ + if (legal_identifier (name) == 0) + { + sh_invalidid (name); + assign_error++; + NEXT_VARIABLE (); + } + + /* If VARIABLE_CONTEXT has a non-zero value, then we are executing + inside of a function. This means we should make local variables, + not global ones. */ + + /* XXX - this has consequences when we're making a local copy of a + variable that was in the temporary environment. Watch out + for this. */ + refvar = (SHELL_VAR *)NULL; + if (variable_context && mkglobal == 0) + { + /* We don't check newname for validity here. We should not have an + invalid name assigned as the value of a nameref, but this could + cause problems. */ + newname = declare_transform_name (name, flags_on, flags_off); + +#if defined (ARRAY_VARS) + /* Pass 1 as second argument to make_local_{assoc,array}_variable + return an existing {array,assoc} variable to be flagged as an + error below. */ + if (flags_on & att_assoc) + var = make_local_assoc_variable (newname, MKLOC_ARRAYOK|inherit_flag); + else if ((flags_on & att_array) || making_array_special) + var = make_local_array_variable (newname, MKLOC_ASSOCOK|inherit_flag); + else +#endif + if (offset == 0 && (flags_on & att_nameref)) + { + /* First look for refvar at current scope */ + refvar = find_variable_last_nameref (name, 1); + /* VARIABLE_CONTEXT != 0, so we are attempting to create or modify + the attributes for a local variable at the same scope. If we've + used a reference from a previous context to resolve VAR, we + want to throw REFVAR and VAR away and create a new local var. */ + if (refvar && refvar->context != variable_context) + { + refvar = 0; + var = make_local_variable (name, inherit_flag); + } + else if (refvar && refvar->context == variable_context) + var = refvar; + /* Maybe we just want to create a new local variable */ + else if ((var = find_variable (name)) == 0 || var->context != variable_context) + var = make_local_variable (name, inherit_flag); + /* otherwise we have a var at the right context */ + } + else + /* XXX - check name for validity here with valid_nameref_value? */ + var = make_local_variable ((flags_on & att_nameref) ? name : newname, inherit_flag); /* sets att_invisible for new vars */ + + if (var == 0) + { + any_failed++; + NEXT_VARIABLE (); + } + if (var && nameref_p (var) && readonly_p (var) && nameref_cell (var) && (flags_off & att_nameref)) + { + sh_readonly (name); + any_failed++; + NEXT_VARIABLE (); + } + } + else + var = (SHELL_VAR *)NULL; + + /* VAR is non-null if we just created or fetched a local variable. */ + + /* Here's what ksh93 seems to do as of the 2012 version: if we are + using declare -n to modify the value of an existing nameref + variable, don't follow the nameref chain at all and just search + for a nameref at the current context. If we have a nameref, + modify its value (changing which variable it references). */ + if (var == 0 && (flags_on & att_nameref)) + { + /* See if we are trying to modify an existing nameref variable, + but don't follow the nameref chain. */ + var = mkglobal ? find_global_variable_noref (name) : find_variable_noref (name); + if (var && nameref_p (var) == 0) + var = 0; + } + + /* However, if we're turning off the nameref attribute on an existing + nameref variable, we first follow the nameref chain to the end, + modify the value of the variable this nameref variable references + if there is an assignment statement argument, + *CHANGING ITS VALUE AS A SIDE EFFECT*, then turn off the nameref + flag *LEAVING THE NAMEREF VARIABLE'S VALUE UNCHANGED* */ + else if (var == 0 && (flags_off & att_nameref)) + { + /* See if we are trying to modify an existing nameref variable */ + refvar = mkglobal ? find_global_variable_last_nameref (name, 0) : find_variable_last_nameref (name, 0); + if (refvar && nameref_p (refvar) == 0) + refvar = 0; + /* If the nameref is readonly but doesn't have a value, ksh93 + allows the nameref attribute to be removed. If it's readonly + and has a value, even if the value doesn't reference an + existing variable, we disallow the modification */ + if (refvar && nameref_cell (refvar) && readonly_p (refvar)) + { + sh_readonly (name); + any_failed++; + NEXT_VARIABLE (); + } + + /* If all we're doing is turning off the nameref attribute, don't + bother with VAR at all, whether it exists or not. Just turn it + off and go on. */ + if (refvar && flags_on == 0 && offset == 0 && flags_off == att_nameref) + { + VUNSETATTR (refvar, att_nameref); + NEXT_VARIABLE (); + } + + if (refvar) + var = declare_find_variable (nameref_cell (refvar), mkglobal, 0); + } +#if defined (ARRAY_VARS) + /* If we have an array assignment to a nameref, remove the nameref + attribute and go on. This handles + declare -n xref[=value]; declare [-a] xref[1]=one */ + else if (var == 0 && offset && array_subscript_assignment) + { + var = mkglobal ? find_global_variable_noref (name) : find_variable_noref (name); + if (var && nameref_p (var)) + { + internal_warning (_("%s: removing nameref attribute"), name); + FREE (value_cell (var)); /* XXX - bash-4.3 compat */ + var_setvalue (var, (char *)NULL); + VUNSETATTR (var, att_nameref); + } + } +#endif + + /* See if we are trying to set flags or value (or create) for an + existing nameref that points to a non-existent variable: e.g., + declare -n foo=bar + unset foo # unsets bar + declare -i foo + foo=4+4 + declare -p foo + */ + if (var == 0 && (mkglobal || flags_on || flags_off || offset)) + { + refvar = mkglobal ? find_global_variable_last_nameref (name, 0) : find_variable_last_nameref (name, 0); + if (refvar && nameref_p (refvar) == 0) + refvar = 0; + if (refvar) + var = declare_find_variable (nameref_cell (refvar), mkglobal, 0); + if (refvar && var == 0) + { + /* I'm not sure subscript_start is ever non-null here. In any + event, build a new name from the nameref value, including any + subscript, and add the [[+]=value] if offset != 0 */ + newname = declare_build_newname (nameref_cell (refvar), subscript_start, offset, value, aflags); + free (name); + name = newname; + + if (offset) + { + offset = assignment (name, 0); + /* If offset was valid previously, but substituting the + the nameref value results in an invalid assignment, + throw an invalid identifier error */ + if (offset == 0) + { + sh_invalidid (name); + assign_error++; + NEXT_VARIABLE (); + } + name[(aflags & ASS_APPEND) ? offset - 1 : offset] = '\0'; + value = name + offset + 1; + } + + /* OK, let's turn off the nameref attribute. + Now everything else applies to VAR. */ + if (flags_off & att_nameref) + VUNSETATTR (refvar, att_nameref); + + goto restart_new_var_name; + /* NOTREACHED */ + } + } + if (var == 0) + var = declare_find_variable (name, mkglobal, chklocal); + + /* At this point, VAR is the variable we are dealing with; REFVAR is the + nameref variable we dereferenced to get VAR, if any. */ +#if defined (ARRAY_VARS) + var_exists = var != 0; + array_exists = var && (array_p (var) || assoc_p (var)); + creating_array = flags_on & (att_array|att_assoc); +#endif + + /* Make a new variable if we need to. */ + if (var == 0) + { +#if defined (ARRAY_VARS) + if (flags_on & att_assoc) + var = make_new_assoc_variable (name); + else if ((flags_on & att_array) || making_array_special) + var = make_new_array_variable (name); + else +#endif + var = mkglobal ? bind_global_variable (name, (char *)NULL, ASS_FORCE) : bind_variable (name, (char *)NULL, ASS_FORCE); + + if (var == 0) + { + /* Has to appear in brackets */ + NEXT_VARIABLE (); + } + if (offset == 0) + VSETATTR (var, att_invisible); + created_var = 1; + } + + /* Nameref variable error checking. */ + + /* Can't take an existing array variable and make it a nameref */ + else if ((array_p (var) || assoc_p (var)) && (flags_on & att_nameref)) + { + builtin_error (_("%s: reference variable cannot be an array"), name); + any_failed++; + NEXT_VARIABLE (); + } + /* Can't have an invalid identifier as nameref value */ + else if (nameref_p (var) && (flags_on & att_nameref) == 0 && (flags_off & att_nameref) == 0 && offset && valid_nameref_value (value, 1) == 0) + { + builtin_error (_("`%s': invalid variable name for name reference"), value); + any_failed++; + NEXT_VARIABLE (); + } + /* Can't make an existing variable a nameref if its current value is not + a valid identifier. Check of offset is to allow an assignment to a + nameref var as part of the declare word to override existing value. */ + else if ((flags_on & att_nameref) && nameref_p (var) == 0 && var_isset (var) && offset == 0 && valid_nameref_value (value_cell (var), 0) == 0) + { + builtin_error (_("`%s': invalid variable name for name reference"), value_cell (var)); + any_failed++; + NEXT_VARIABLE (); + } + /* Can't make an existing readonly variable a nameref. */ + else if ((flags_on & att_nameref) && readonly_p (var)) + { + sh_readonly (name); + any_failed++; + NEXT_VARIABLE (); + } + + /* Readonly variable error checking. */ + + /* Cannot use declare +r to turn off readonly attribute. */ + if (readonly_p (var) && (flags_off & att_readonly)) + { + sh_readonly (name_cell (var)); + any_failed++; + NEXT_VARIABLE (); + } + /* Cannot use declare to assign value to readonly or noassign variable. */ + else if ((readonly_p (var) || noassign_p (var)) && offset) + { + if (readonly_p (var)) + sh_readonly (name); + assign_error++; + NEXT_VARIABLE (); + } + +#if defined (ARRAY_VARS) + /* Array variable error checking. */ + + /* Cannot use declare +a name or declare +A name to remove an array variable. */ + if (((flags_off & att_array) && array_p (var)) || ((flags_off & att_assoc) && assoc_p (var))) + { + builtin_error (_("%s: cannot destroy array variables in this way"), name); + any_failed++; + NEXT_VARIABLE (); + } + else if ((flags_on & att_array) && assoc_p (var)) + { + builtin_error (_("%s: cannot convert associative to indexed array"), name); + any_failed++; + NEXT_VARIABLE (); + } + else if ((flags_on & att_assoc) && array_p (var)) + { + builtin_error (_("%s: cannot convert indexed to associative array"), name); + any_failed++; + NEXT_VARIABLE (); + } + + /* make declare A[2]=foo as similar to A[2]=foo as possible if A is + already an array or assoc variable. */ + if (array_subscript_assignment && array_exists && creating_array == 0) + simple_array_assign = 1; + else if ((making_array_special || creating_array || array_exists) && offset) + { + int vlen; + vlen = STRLEN (value); +/*itrace("declare_builtin: name = %s value = %s flags = %d", name, value, wflags);*/ + + if (shell_compatibility_level > 43 && (wflags & W_COMPASSIGN) == 0 && + value[0] == '(' && value[vlen-1] == ')') + { + /* I don't believe this warning is printed any more. + We use creating_array to allow things like + declare -a foo$bar='(abc)' + to work as they have in the past. */ + if (array_exists == 0 && creating_array == 0) + internal_warning (_("%s: quoted compound array assignment deprecated"), list->word->word); + compound_array_assign = array_exists || creating_array; + simple_array_assign = making_array_special; + } + else if (value[0] == '(' && value[vlen-1] == ')' && (shell_compatibility_level < 44 || (wflags & W_COMPASSIGN))) + compound_array_assign = 1; + else + simple_array_assign = 1; + } + + /* declare -A name[[n]] makes name an associative array variable. */ + if (flags_on & att_assoc) + { + if (assoc_p (var) == 0) + var = convert_var_to_assoc (var); + } + /* declare -a name[[n]] or declare name[n] makes NAME an indexed + array variable. */ + else if ((making_array_special || (flags_on & att_array)) && array_p (var) == 0 && assoc_p (var) == 0) + var = convert_var_to_array (var); +#endif /* ARRAY_VARS */ + + /* ksh93 compat: turning on nameref attribute turns off -ilu */ + if (flags_on & att_nameref) + VUNSETATTR (var, att_integer|att_uppercase|att_lowercase|att_capcase); + + /* XXX - we note that we are turning on nameref attribute and defer + setting it until the assignment has been made so we don't do an + inadvertent nameref lookup. Might have to do the same thing for + flags_off&att_nameref. */ + /* XXX - ksh93 makes it an error to set a readonly nameref variable + using a single typeset command. */ + onref = (flags_on & att_nameref); + flags_on &= ~att_nameref; +#if defined (ARRAY_VARS) + /* I don't believe this condition ever tests true, but array variables + may not be namerefs */ + if (array_p (var) || assoc_p (var) || compound_array_assign || simple_array_assign) + onref = 0; +#endif + + /* ksh93 seems to do this */ + offref = (flags_off & att_nameref); + flags_off &= ~att_nameref; + + VSETATTR (var, flags_on); + VUNSETATTR (var, flags_off); + +#if defined (ARRAY_VARS) + if (offset && compound_array_assign) + assign_array_var_from_string (var, value, aflags|ASS_FORCE); + else if (simple_array_assign && subscript_start) + { + int local_aflags; + + /* declare [-aA] name[N]=value */ + *subscript_start = '['; /* ] */ + /* XXX - problem here with appending */ + local_aflags = aflags&ASS_APPEND; + local_aflags |= assoc_noexpand ? ASS_NOEXPAND : 0; + local_aflags |= ASS_ALLOWALLSUB; /* allow declare a[@]=at */ + var = assign_array_element (name, value, local_aflags, (array_eltstate_t *)0); /* XXX - not aflags */ + *subscript_start = '\0'; + if (var == 0) /* some kind of assignment error */ + { + assign_error++; + flags_on |= onref; + flags_off |= offref; + NEXT_VARIABLE (); + } + } + else if (simple_array_assign) + { + /* let bind_{array,assoc}_variable take care of this. */ + if (assoc_p (var)) + bind_assoc_variable (var, name, savestring ("0"), value, aflags|ASS_FORCE); + else + bind_array_variable (name, 0, value, aflags|ASS_FORCE); + } + else +#endif + /* XXX - no ASS_FORCE here */ + /* bind_variable_value duplicates the essential internals of bind_variable() */ + if (offset) + { + if (onref || nameref_p (var)) + aflags |= ASS_NAMEREF; + v = bind_variable_value (var, value, aflags); + if (v == 0 && (onref || nameref_p (var))) + { + if (valid_nameref_value (value, 1) == 0) + sh_invalidid (value); + assign_error++; + /* XXX - unset this variable? or leave it as normal var? */ + if (created_var) + delete_var (name_cell (var), mkglobal ? global_variables : shell_variables); + flags_on |= onref; /* undo change from above */ + flags_off |= offref; + NEXT_VARIABLE (); + } + } + + /* If we found this variable in the temporary environment, as with + `var=value declare -x var', make sure it is treated identically + to `var=value export var'. Do the same for `declare -r' and + `readonly'. Preserve the attributes, except for att_tempvar. */ + /* XXX -- should this create a variable in the global scope, or + modify the local variable flags? ksh93 has it modify the + global scope. + Need to handle case like in set_var_attribute where a temporary + variable is in the same table as the function local vars. */ + if ((flags_on & (att_exported|att_readonly)) && tempvar_p (var)) + { + SHELL_VAR *tv; + char *tvalue; + + tv = find_tempenv_variable (name_cell (var)); + if (tv) + { + tvalue = var_isset (var) ? savestring (value_cell (var)) : savestring (""); + tv = bind_variable (name_cell (var), tvalue, 0); + if (tv) + { + tv->attributes |= var->attributes & ~att_tempvar; + if (tv->context > 0) + VSETATTR (tv, att_propagate); + } + free (tvalue); + } + VSETATTR (var, att_propagate); + } + + /* Turn on nameref attribute we deferred above. */ + VSETATTR (var, onref); + flags_on |= onref; + VUNSETATTR (var, offref); + flags_off |= offref; + + /* Yuck. ksh93 compatibility. XXX - need to investigate more but + definitely happens when turning off nameref attribute on nameref + (see comments above). Under no circumstances allow this to turn + off readonly attribute on readonly nameref variable. */ + if (refvar) + { + if (flags_off & att_readonly) + flags_off &= ~att_readonly; + VUNSETATTR (refvar, flags_off); + } + + stupidly_hack_special_variables (name); + + NEXT_VARIABLE (); + } + + return (assign_error ? EX_BADASSIGN + : ((any_failed == 0) ? EXECUTION_SUCCESS + : EXECUTION_FAILURE)); +} diff --git a/third_party/bash/builtins_echo.c b/third_party/bash/builtins_echo.c new file mode 100644 index 000000000..957d2e34c --- /dev/null +++ b/third_party/bash/builtins_echo.c @@ -0,0 +1,133 @@ +/* echo.c, created from echo.def. */ +#line 22 "./echo.def" +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" + +#include +#include "shell.h" + +#include "common.h" + +#line 73 "./echo.def" + +#line 88 "./echo.def" + +#if defined (V9_ECHO) +# define VALID_ECHO_OPTIONS "neE" +#else /* !V9_ECHO */ +# define VALID_ECHO_OPTIONS "n" +#endif /* !V9_ECHO */ + +/* System V machines already have a /bin/sh with a v9 behaviour. We + give Bash the identical behaviour for these machines so that the + existing system shells won't barf. Regrettably, the SUS v2 has + standardized the Sys V echo behavior. This variable is external + so that we can have a `shopt' variable to control it at runtime. */ +#if defined (DEFAULT_ECHO_TO_XPG) || defined (STRICT_POSIX) +int xpg_echo = 1; +#else +int xpg_echo = 0; +#endif /* DEFAULT_ECHO_TO_XPG */ + +/* Print the words in LIST to standard output. If the first word is + `-n', then don't print a trailing newline. We also support the + echo syntax from Version 9 Unix systems. */ +int +echo_builtin (list) + WORD_LIST *list; +{ + int display_return, do_v9, i, len; + char *temp, *s; + + do_v9 = xpg_echo; + display_return = 1; + + if (posixly_correct && xpg_echo) + goto just_echo; + + for (; list && (temp = list->word->word) && *temp == '-'; list = list->next) + { + /* If it appears that we are handling options, then make sure that + all of the options specified are actually valid. Otherwise, the + string should just be echoed. */ + temp++; + + for (i = 0; temp[i]; i++) + { + if (strchr (VALID_ECHO_OPTIONS, temp[i]) == 0) + break; + } + + /* echo - and echo - both mean to just echo the arguments. */ + if (*temp == 0 || temp[i]) + break; + + /* All of the options in TEMP are valid options to ECHO. + Handle them. */ + while (i = *temp++) + { + switch (i) + { + case 'n': + display_return = 0; + break; +#if defined (V9_ECHO) + case 'e': + do_v9 = 1; + break; + case 'E': + do_v9 = 0; + break; +#endif /* V9_ECHO */ + default: + goto just_echo; /* XXX */ + } + } + } + +just_echo: + + clearerr (stdout); /* clear error before writing and testing success */ + + while (list) + { + i = len = 0; + temp = do_v9 ? ansicstr (list->word->word, STRLEN (list->word->word), 1, &i, &len) + : list->word->word; + if (temp) + { + if (do_v9) + { + for (s = temp; len > 0; len--) + putchar (*s++); + } + else + printf ("%s", temp); +#if defined (SunOS5) + fflush (stdout); /* Fix for bug in SunOS 5.5 printf(3) */ +#endif + } + QUIT; + if (do_v9 && temp) + free (temp); + list = list->next; + if (i) + { + display_return = 0; + break; + } + if (list) + putchar(' '); + QUIT; + } + + if (display_return) + putchar ('\n'); + + return (sh_chkwrite (EXECUTION_SUCCESS)); +} diff --git a/third_party/bash/builtins_enable.c b/third_party/bash/builtins_enable.c new file mode 100644 index 000000000..6edacda69 --- /dev/null +++ b/third_party/bash/builtins_enable.c @@ -0,0 +1,541 @@ +/* enable.c, created from enable.def. */ +#line 22 "./enable.def" + +#line 50 "./enable.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "builtins.h" +#include "flags.h" +#include "common.h" +#include "bashgetopt.h" +#include "findcmd.h" + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +#define ENABLED 1 +#define DISABLED 2 +#define SPECIAL 4 +#define SILENT 8 /* affects dyn_load_builtin behavior */ + +#define AFLAG 0x01 +#define DFLAG 0x02 +#define FFLAG 0x04 +#define NFLAG 0x08 +#define PFLAG 0x10 +#define SFLAG 0x20 + +#if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) +static int dyn_load_builtin PARAMS((WORD_LIST *, int, char *)); +#endif + +#if defined (HAVE_DLCLOSE) +static int dyn_unload_builtin PARAMS((char *)); +static void delete_builtin PARAMS((struct builtin *)); +static int local_dlclose PARAMS((void *)); +#endif + +#define STRUCT_SUFFIX "_struct" +/* for now */ +#define LOAD_SUFFIX "_builtin_load" +#define UNLOAD_SUFFIX "_builtin_unload" + +static void list_some_builtins PARAMS((int)); +static int enable_shell_command PARAMS((char *, int)); + +/* Enable/disable shell commands present in LIST. If list is not specified, + then print out a list of shell commands showing which are enabled and + which are disabled. */ +int +enable_builtin (list) + WORD_LIST *list; +{ + int result, flags; + int opt, filter; + WORD_LIST *next; +#if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) + char *filename; +#endif + + result = EXECUTION_SUCCESS; + flags = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "adnpsf:")) != -1) + { + switch (opt) + { + case 'a': + flags |= AFLAG; + break; + case 'n': + flags |= NFLAG; + break; + case 'p': + flags |= PFLAG; + break; + case 's': + flags |= SFLAG; + break; + case 'f': +#if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) + flags |= FFLAG; + filename = list_optarg; + break; +#else + builtin_error (_("dynamic loading not available")); + return (EX_USAGE); +#endif +#if defined (HAVE_DLCLOSE) + case 'd': + flags |= DFLAG; + break; +#else + builtin_error (_("dynamic loading not available")); + return (EX_USAGE); +#endif /* HAVE_DLCLOSE */ + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + +#if defined (RESTRICTED_SHELL) + /* Restricted shells cannot load new builtins. */ + if (restricted && (flags & (FFLAG|DFLAG))) + { + sh_restricted ((char *)NULL); + return (EXECUTION_FAILURE); + } +#endif + + if (list == 0 || (flags & PFLAG)) + { + filter = (flags & AFLAG) ? (ENABLED | DISABLED) + : (flags & NFLAG) ? DISABLED : ENABLED; + + if (flags & SFLAG) + filter |= SPECIAL; + + list_some_builtins (filter); + result = sh_chkwrite (EXECUTION_SUCCESS); + } +#if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) + else if (flags & FFLAG) + { + filter = (flags & NFLAG) ? DISABLED : ENABLED; + if (flags & SFLAG) + filter |= SPECIAL; + + result = dyn_load_builtin (list, filter, filename); + if (result != EXECUTION_SUCCESS) + result = EXECUTION_FAILURE; /* normalize return value */ +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_builtins); +#endif + } +#endif +#if defined (HAVE_DLCLOSE) + else if (flags & DFLAG) + { + while (list) + { + opt = dyn_unload_builtin (list->word->word); + if (opt == EXECUTION_FAILURE) + result = EXECUTION_FAILURE; + list = list->next; + } +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_builtins); +#endif + } +#endif + else + { + while (list) + { + opt = enable_shell_command (list->word->word, flags & NFLAG); + next = list->next; + +#if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) + /* If we try to enable a non-existent builtin, and we have dynamic + loading, try the equivalent of `enable -f name name'. */ + if (opt == EX_NOTFOUND) + { + int dflags, r; + + dflags = ENABLED|SILENT|((flags & SFLAG) ? SPECIAL : 0); + + list->next = 0; + r = dyn_load_builtin (list, dflags, list->word->word); + list->next = next; + if (r == EXECUTION_SUCCESS) + opt = r; +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_builtins); +#endif + } +#endif + + if (opt == EX_NOTFOUND) + { + sh_notbuiltin (list->word->word); + result = EXECUTION_FAILURE; + } + else if (opt != EXECUTION_SUCCESS) + result = EXECUTION_FAILURE; + + list = next; + } + } + return (result); +} + +/* List some builtins. + FILTER is a mask with two slots: ENABLED and DISABLED. */ +static void +list_some_builtins (filter) + int filter; +{ + register int i; + + for (i = 0; i < num_shell_builtins; i++) + { + if (shell_builtins[i].function == 0 || (shell_builtins[i].flags & BUILTIN_DELETED)) + continue; + + if ((filter & SPECIAL) && + (shell_builtins[i].flags & SPECIAL_BUILTIN) == 0) + continue; + + if ((filter & ENABLED) && (shell_builtins[i].flags & BUILTIN_ENABLED)) + printf ("enable %s\n", shell_builtins[i].name); + else if ((filter & DISABLED) && + ((shell_builtins[i].flags & BUILTIN_ENABLED) == 0)) + printf ("enable -n %s\n", shell_builtins[i].name); + } +} + +/* Enable the shell command NAME. If DISABLE_P is non-zero, then + disable NAME instead. */ +static int +enable_shell_command (name, disable_p) + char *name; + int disable_p; +{ + struct builtin *b; + + b = builtin_address_internal (name, 1); + if (b == 0) + return (EX_NOTFOUND); + + if (disable_p) + b->flags &= ~BUILTIN_ENABLED; +#if defined (RESTRICTED_SHELL) + else if (restricted && ((b->flags & BUILTIN_ENABLED) == 0)) + { + sh_restricted ((char *)NULL); + return (EXECUTION_FAILURE); + } +#endif + else + b->flags |= BUILTIN_ENABLED; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_enabled); + set_itemlist_dirty (&it_disabled); +#endif + + return (EXECUTION_SUCCESS); +} + +#if defined (HAVE_DLOPEN) && defined (HAVE_DLSYM) + +#if defined (HAVE_DLFCN_H) +# include +#endif + +static int +dyn_load_builtin (list, flags, filename) + WORD_LIST *list; + int flags; + char *filename; +{ + WORD_LIST *l; + void *handle; + + int total, size, new, replaced, r; + char *struct_name, *name, *funcname; + sh_load_func_t *loadfunc; + struct builtin **new_builtins, *b, *new_shell_builtins, *old_builtin; + char *loadables_path, *load_path; + + if (list == 0) + return (EXECUTION_FAILURE); + +#ifndef RTLD_LAZY +#define RTLD_LAZY 1 +#endif + + handle = 0; + if (absolute_program (filename) == 0) + { + loadables_path = get_string_value ("BASH_LOADABLES_PATH"); + if (loadables_path) + { + load_path = find_in_path (filename, loadables_path, FS_NODIRS|FS_EXEC_PREFERRED); + if (load_path) + { +#if defined (_AIX) + handle = dlopen (load_path, RTLD_NOW|RTLD_GLOBAL); +#else + handle = dlopen (load_path, RTLD_LAZY); +#endif /* !_AIX */ + free (load_path); + } + } + } + + /* Fall back to current directory for now */ + if (handle == 0) +#if defined (_AIX) + handle = dlopen (filename, RTLD_NOW|RTLD_GLOBAL); +#else + handle = dlopen (filename, RTLD_LAZY); +#endif /* !_AIX */ + + if (handle == 0) + { + /* If we've been told to be quiet, don't complain about not finding the + specified shared object. */ + if ((flags & SILENT) == 0) + { + name = printable_filename (filename, 0); + builtin_error (_("cannot open shared object %s: %s"), name, dlerror ()); + if (name != filename) + free (name); + } + return (EX_NOTFOUND); + } + + for (new = 0, l = list; l; l = l->next, new++) + ; + new_builtins = (struct builtin **)xmalloc (new * sizeof (struct builtin *)); + + /* For each new builtin in the shared object, find it and its describing + structure. If this is overwriting an existing builtin, do so, otherwise + save the loaded struct for creating the new list of builtins. */ + for (replaced = new = 0; list; list = list->next) + { + name = list->word->word; + + size = strlen (name); + struct_name = (char *)xmalloc (size + 8); + strcpy (struct_name, name); + strcpy (struct_name + size, STRUCT_SUFFIX); + + old_builtin = builtin_address_internal (name, 1); + + b = (struct builtin *)dlsym (handle, struct_name); + if (b == 0) + { + name = printable_filename (filename, 0); + builtin_error (_("cannot find %s in shared object %s: %s"), + struct_name, name, dlerror ()); + if (name != filename) + free (name); + free (struct_name); + continue; + } + + funcname = xrealloc (struct_name, size + sizeof (LOAD_SUFFIX) + 1); + strcpy (funcname, name); + strcpy (funcname + size, LOAD_SUFFIX); + + loadfunc = (sh_load_func_t *)dlsym (handle, funcname); + if (loadfunc) + { + /* Add warning if running an init function more than once */ + if (old_builtin && (old_builtin->flags & STATIC_BUILTIN) == 0) + builtin_warning (_("%s: dynamic builtin already loaded"), name); + r = (*loadfunc) (name); + if (r == 0) + { + builtin_error (_("load function for %s returns failure (%d): not loaded"), name, r); + free (funcname); + continue; + } + } + free (funcname); + + b->flags &= ~STATIC_BUILTIN; + if (flags & SPECIAL) + b->flags |= SPECIAL_BUILTIN; + b->handle = handle; + + if (old_builtin) + { + replaced++; + FASTCOPY ((char *)b, (char *)old_builtin, sizeof (struct builtin)); + } + else + new_builtins[new++] = b; + } + + if (replaced == 0 && new == 0) + { + free (new_builtins); + dlclose (handle); + return (EXECUTION_FAILURE); + } + + if (new) + { + total = num_shell_builtins + new; + size = (total + 1) * sizeof (struct builtin); + + new_shell_builtins = (struct builtin *)xmalloc (size); + FASTCOPY ((char *)shell_builtins, (char *)new_shell_builtins, + num_shell_builtins * sizeof (struct builtin)); + for (replaced = 0; replaced < new; replaced++) + FASTCOPY ((char *)new_builtins[replaced], + (char *)&new_shell_builtins[num_shell_builtins + replaced], + sizeof (struct builtin)); + + new_shell_builtins[total].name = (char *)0; + new_shell_builtins[total].function = (sh_builtin_func_t *)0; + new_shell_builtins[total].flags = 0; + + if (shell_builtins != static_shell_builtins) + free (shell_builtins); + + shell_builtins = new_shell_builtins; + num_shell_builtins = total; + initialize_shell_builtins (); + } + + free (new_builtins); + return (EXECUTION_SUCCESS); +} +#endif + +#if defined (HAVE_DLCLOSE) +static void +delete_builtin (b) + struct builtin *b; +{ + int ind, size; + struct builtin *new_shell_builtins; + + /* XXX - funky pointer arithmetic - XXX */ +#ifdef __STDC__ + ind = b - shell_builtins; +#else + ind = ((int)b - (int)shell_builtins) / sizeof (struct builtin); +#endif + size = num_shell_builtins * sizeof (struct builtin); + new_shell_builtins = (struct builtin *)xmalloc (size); + + /* Copy shell_builtins[0]...shell_builtins[ind - 1] to new_shell_builtins */ + if (ind) + FASTCOPY ((char *)shell_builtins, (char *)new_shell_builtins, + ind * sizeof (struct builtin)); + /* Copy shell_builtins[ind+1]...shell_builtins[num_shell_builtins to + new_shell_builtins, starting at ind. */ + FASTCOPY ((char *)(&shell_builtins[ind+1]), + (char *)(&new_shell_builtins[ind]), + (num_shell_builtins - ind) * sizeof (struct builtin)); + + if (shell_builtins != static_shell_builtins) + free (shell_builtins); + + /* The result is still sorted. */ + num_shell_builtins--; + shell_builtins = new_shell_builtins; +} + +/* Tenon's MachTen has a dlclose that doesn't return a value, so we + finesse it with a local wrapper. */ +static int +local_dlclose (handle) + void *handle; +{ +#if !defined (__MACHTEN__) + return (dlclose (handle)); +#else /* __MACHTEN__ */ + dlclose (handle); + return ((dlerror () != NULL) ? -1 : 0); +#endif /* __MACHTEN__ */ +} + +static int +dyn_unload_builtin (name) + char *name; +{ + struct builtin *b; + void *handle; + char *funcname; + sh_unload_func_t *unloadfunc; + int ref, i, size; + + b = builtin_address_internal (name, 1); + if (b == 0) + { + sh_notbuiltin (name); + return (EXECUTION_FAILURE); + } + if (b->flags & STATIC_BUILTIN) + { + builtin_error (_("%s: not dynamically loaded"), name); + return (EXECUTION_FAILURE); + } + + handle = (void *)b->handle; + for (ref = i = 0; i < num_shell_builtins; i++) + { + if (shell_builtins[i].handle == b->handle) + ref++; + } + + /* Call any unload function */ + size = strlen (name); + funcname = xmalloc (size + sizeof (UNLOAD_SUFFIX) + 1); + strcpy (funcname, name); + strcpy (funcname + size, UNLOAD_SUFFIX); + + unloadfunc = (sh_unload_func_t *)dlsym (handle, funcname); + if (unloadfunc) + (*unloadfunc) (name); /* void function */ + free (funcname); + + /* Don't remove the shared object unless the reference count of builtins + using it drops to zero. */ + if (ref == 1 && local_dlclose (handle) != 0) + { + builtin_error (_("%s: cannot delete: %s"), name, dlerror ()); + return (EXECUTION_FAILURE); + } + + /* Now remove this entry from the builtin table and reinitialize. */ + delete_builtin (b); + + return (EXECUTION_SUCCESS); +} +#endif diff --git a/third_party/bash/builtins_eval.c b/third_party/bash/builtins_eval.c new file mode 100644 index 000000000..8bb0b8b23 --- /dev/null +++ b/third_party/bash/builtins_eval.c @@ -0,0 +1,28 @@ +/* eval.c, created from eval.def. */ +#line 22 "./eval.def" + +#line 34 "./eval.def" + +#include "config.h" +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "shell.h" +#include "bashgetopt.h" +#include "common.h" + +/* Parse the string that these words make, and execute the command found. */ +int +eval_builtin (list) + WORD_LIST *list; +{ + if (no_options (list)) + return (EX_USAGE); + list = loptend; /* skip over possible `--' */ + + return (list ? evalstring (string_list (list), "eval", SEVAL_NOHIST) : EXECUTION_SUCCESS); +} diff --git a/third_party/bash/builtins_exec.c b/third_party/bash/builtins_exec.c new file mode 100644 index 000000000..ae5ca7eb6 --- /dev/null +++ b/third_party/bash/builtins_exec.c @@ -0,0 +1,238 @@ +/* exec.c, created from exec.def. */ +#line 22 "./exec.def" + +#line 43 "./exec.def" + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "findcmd.h" +#if defined (JOB_CONTROL) +# include "jobs.h" +#endif +#include "flags.h" +#include "trap.h" +#if defined (HISTORY) +# include "bashhist.h" +#endif +#include "common.h" +#include "bashgetopt.h" +#include "input.h" + +/* Not all systems declare ERRNO in errno.h... and some systems #define it! */ +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern REDIRECT *redirection_undo_list; +extern char *exec_argv0; + +int no_exit_on_failed_exec; + +/* If the user wants this to look like a login shell, then + prepend a `-' onto NAME and return the new name. */ +static char * +mkdashname (name) + char *name; +{ + char *ret; + + ret = (char *)xmalloc (2 + strlen (name)); + ret[0] = '-'; + strcpy (ret + 1, name); + return ret; +} + +int +exec_builtin (list) + WORD_LIST *list; +{ + int exit_value = EXECUTION_FAILURE; + int cleanenv, login, opt, orig_job_control; + char *argv0, *command, **args, **env, *newname, *com2; + + cleanenv = login = orig_job_control = 0; + exec_argv0 = argv0 = (char *)NULL; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "cla:")) != -1) + { + switch (opt) + { + case 'c': + cleanenv = 1; + break; + case 'l': + login = 1; + break; + case 'a': + argv0 = list_optarg; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + /* First, let the redirections remain. */ + dispose_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + + if (list == 0) + return (EXECUTION_SUCCESS); + +#if defined (RESTRICTED_SHELL) + if (restricted) + { + sh_restricted ((char *)NULL); + return (EXECUTION_FAILURE); + } +#endif /* RESTRICTED_SHELL */ + + args = strvec_from_word_list (list, 1, 0, (int *)NULL); + env = (char **)0; + + /* A command with a slash anywhere in its name is not looked up in $PATH. */ + command = absolute_program (args[0]) ? args[0] : search_for_command (args[0], 1); + + if (command == 0) + { + if (file_isdir (args[0])) + { +#if defined (EISDIR) + builtin_error (_("%s: cannot execute: %s"), args[0], strerror (EISDIR)); +#else + builtin_error (_("%s: cannot execute: %s"), args[0], strerror (errno)); +#endif + exit_value = EX_NOEXEC; + } + else + { + sh_notfound (args[0]); + exit_value = EX_NOTFOUND; /* As per Posix.2, 3.14.6 */ + } + goto failed_exec; + } + + com2 = full_pathname (command); + if (com2) + { + if (command != args[0]) + free (command); + command = com2; + } + + if (argv0) + { + free (args[0]); + args[0] = login ? mkdashname (argv0) : savestring (argv0); + exec_argv0 = savestring (args[0]); + } + else if (login) + { + newname = mkdashname (args[0]); + free (args[0]); + args[0] = newname; + } + + /* Decrement SHLVL by 1 so a new shell started here has the same value, + preserving the appearance. After we do that, we need to change the + exported environment to include the new value. If we've already forked + and are in a subshell, we don't want to decrement the shell level, + since we are `increasing' the level */ + + if (cleanenv == 0 && (subshell_environment & SUBSHELL_PAREN) == 0) + adjust_shell_level (-1); + + if (cleanenv) + { + env = strvec_create (1); + env[0] = (char *)0; + } + else + { + maybe_make_export_env (); + env = export_env; + } + +#if defined (HISTORY) + if (interactive_shell && subshell_environment == 0) + maybe_save_shell_history (); +#endif /* HISTORY */ + + reset_signal_handlers (); /* leave trap strings in place */ + +#if defined (JOB_CONTROL) + orig_job_control = job_control; /* XXX - was also interactive_shell */ + if (subshell_environment == 0) + end_job_control (); + if (interactive || job_control) + default_tty_job_signals (); /* undo initialize_job_signals */ +#endif /* JOB_CONTROL */ + +#if defined (BUFFERED_INPUT) + if (default_buffered_input >= 0) + sync_buffered_stream (default_buffered_input); +#endif + + exit_value = shell_execve (command, args, env); + + /* We have to set this to NULL because shell_execve has called realloc() + to stuff more items at the front of the array, which may have caused + the memory to be freed by realloc(). We don't want to free it twice. */ + args = (char **)NULL; + if (cleanenv == 0) + adjust_shell_level (1); + + if (exit_value == EX_NOTFOUND) /* no duplicate error message */ + goto failed_exec; + else if (executable_file (command) == 0) + { + builtin_error (_("%s: cannot execute: %s"), command, strerror (errno)); + exit_value = EX_NOEXEC; /* As per Posix.2, 3.14.6 */ + } + else + file_error (command); + +failed_exec: + FREE (command); + + if (subshell_environment || (interactive == 0 && no_exit_on_failed_exec == 0)) + exit_shell (last_command_exit_value = exit_value); + + if (args) + strvec_dispose (args); + + if (env && env != export_env) + strvec_dispose (env); + + /* If we're not exiting after the exec fails, we restore the shell signal + handlers and then modify the signal dispositions based on the trap strings + before the failed exec. */ + initialize_signals (1); + restore_traps (); + +#if defined (JOB_CONTROL) + if (orig_job_control) + restart_job_control (); +#endif /* JOB_CONTROL */ + + return (exit_value); +} diff --git a/third_party/bash/builtins_exit.c b/third_party/bash/builtins_exit.c new file mode 100644 index 000000000..89f8d3a78 --- /dev/null +++ b/third_party/bash/builtins_exit.c @@ -0,0 +1,136 @@ +/* exit.c, created from exit.def. */ +#line 22 "./exit.def" + +#line 31 "./exit.def" + +#include "config.h" + +#include "bashtypes.h" +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "jobs.h" +#include "trap.h" + +#include "common.h" +#include "builtext.h" /* for jobs_builtin */ + +extern int check_jobs_at_exit; + +static int exit_or_logout PARAMS((WORD_LIST *)); +static int sourced_logout; + +int +exit_builtin (list) + WORD_LIST *list; +{ + CHECK_HELPOPT (list); + + if (interactive) + { + fprintf (stderr, login_shell ? _("logout\n") : "exit\n"); + fflush (stderr); + } + + return (exit_or_logout (list)); +} + +#line 79 "./exit.def" + +/* How to logout. */ +int +logout_builtin (list) + WORD_LIST *list; +{ + CHECK_HELPOPT (list); + + if (login_shell == 0 /* && interactive */) + { + builtin_error (_("not login shell: use `exit'")); + return (EXECUTION_FAILURE); + } + else + return (exit_or_logout (list)); +} + +static int +exit_or_logout (list) + WORD_LIST *list; +{ + int exit_value; + +#if defined (JOB_CONTROL) + int exit_immediate_okay, stopmsg; + + exit_immediate_okay = (interactive == 0 || + last_shell_builtin == exit_builtin || + last_shell_builtin == logout_builtin || + last_shell_builtin == jobs_builtin); + + /* Check for stopped jobs if the user wants to. */ + if (exit_immediate_okay == 0) + { + register int i; + for (i = stopmsg = 0; i < js.j_jobslots; i++) + if (jobs[i] && STOPPED (i)) + stopmsg = JSTOPPED; + else if (check_jobs_at_exit && stopmsg == 0 && jobs[i] && RUNNING (i)) + stopmsg = JRUNNING; + + if (stopmsg == JSTOPPED) + fprintf (stderr, _("There are stopped jobs.\n")); + else if (stopmsg == JRUNNING) + fprintf (stderr, _("There are running jobs.\n")); + + if (stopmsg && check_jobs_at_exit) + list_all_jobs (JLIST_STANDARD); + + if (stopmsg) + { + /* This is NOT superfluous because EOF can get here without + going through the command parser. Set both last and this + so that either `exit', `logout', or ^D will work to exit + immediately if nothing intervenes. */ + this_shell_builtin = last_shell_builtin = exit_builtin; + return (EXECUTION_FAILURE); + } + } +#endif /* JOB_CONTROL */ + + /* Get return value if present. This means that you can type + `logout 5' to a shell, and it returns 5. */ + + /* If we're running the exit trap (running_trap == 1, since running_trap + gets set to SIG+1), and we don't have a argument given to `exit' + (list == 0), use the exit status we saved before running the trap + commands (trap_saved_exit_value). */ + exit_value = (running_trap == 1 && list == 0) ? trap_saved_exit_value : get_exitstat (list); + + bash_logout (); + + last_command_exit_value = exit_value; + + /* Exit the program. */ + jump_to_top_level (EXITBLTIN); + /*NOTREACHED*/ +} + +void +bash_logout () +{ + /* Run our `~/.bash_logout' file if it exists, and this is a login shell. */ + if (login_shell && sourced_logout++ == 0 && subshell_environment == 0) + { + maybe_execute_file ("~/.bash_logout", 1); +#ifdef SYS_BASH_LOGOUT + maybe_execute_file (SYS_BASH_LOGOUT, 1); +#endif + } +} diff --git a/third_party/bash/builtins_fc.c b/third_party/bash/builtins_fc.c new file mode 100644 index 000000000..20d4b5433 --- /dev/null +++ b/third_party/bash/builtins_fc.c @@ -0,0 +1,739 @@ +/* fc.c, created from fc.def. */ +#line 22 "./fc.def" + +#line 51 "./fc.def" + +#include "config.h" + +#if defined (HISTORY) +#if defined (HAVE_SYS_PARAM_H) +# include +#endif +#include "bashtypes.h" +#include "posixstat.h" +#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include "chartypes.h" + +#include "bashansi.h" +#include "bashintl.h" +#include + +#include "shell.h" +#include "builtins.h" +#include "flags.h" +#include "parser.h" +#include "bashhist.h" +#include "maxpath.h" +#include "third_party/readline/history.h" +#include "bashgetopt.h" +#include "common.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#define HIST_INVALID INT_MIN +#define HIST_ERANGE INT_MIN+1 +#define HIST_NOTFOUND INT_MIN+2 + +/* Values for the flags argument to fc_gethnum */ +#define HN_LISTING 0x01 +#define HN_FIRST 0x02 + +extern int unlink PARAMS((const char *)); + +extern FILE *sh_mktmpfp PARAMS((char *, int, char **)); + +extern int suppress_debug_trap_verbose; + +/* **************************************************************** */ +/* */ +/* The K*rn shell style fc command (Fix Command) */ +/* */ +/* **************************************************************** */ + +/* fc builtin command (fix command) for Bash for those who + like K*rn-style history better than csh-style. + + fc [-e ename] [-nlr] [first] [last] + + FIRST and LAST can be numbers specifying the range, or FIRST can be + a string, which means the most recent command beginning with that + string. + + -e ENAME selects which editor to use. Default is FCEDIT, then EDITOR, + then the editor which corresponds to the current readline editing + mode, then vi. + + -l means list lines instead of editing. + -n means no line numbers listed. + -r means reverse the order of the lines (making it newest listed first). + + fc -e - [pat=rep ...] [command] + fc -s [pat=rep ...] [command] + + Equivalent to !command:sg/pat/rep execpt there can be multiple PAT=REP's. +*/ + +/* Data structure describing a list of global replacements to perform. */ +typedef struct repl { + struct repl *next; + char *pat; + char *rep; +} REPL; + +/* Accessors for HIST_ENTRY lists that are called HLIST. */ +#define histline(i) (hlist[(i)]->line) +#define histdata(i) (hlist[(i)]->data) + +#define FREE_RLIST() \ + do { \ + for (rl = rlist; rl; ) { \ + REPL *r; \ + r = rl->next; \ + if (rl->pat) \ + free (rl->pat); \ + if (rl->rep) \ + free (rl->rep); \ + free (rl); \ + rl = r; \ + } \ + } while (0) + +static char *fc_dosubs PARAMS((char *, REPL *)); +static char *fc_gethist PARAMS((char *, HIST_ENTRY **, int)); +static int fc_gethnum PARAMS((char *, HIST_ENTRY **, int)); +static int fc_number PARAMS((WORD_LIST *)); +static void fc_replhist PARAMS((char *)); +#ifdef INCLUDE_UNUSED +static char *fc_readline PARAMS((FILE *)); +static void fc_addhist PARAMS((char *)); +#endif + +static void +set_verbose_flag () +{ + echo_input_at_read = verbose_flag; +} + +/* String to execute on a file that we want to edit. */ +#define FC_EDIT_COMMAND "${FCEDIT:-${EDITOR:-vi}}" +#if defined (STRICT_POSIX) +# define POSIX_FC_EDIT_COMMAND "${FCEDIT:-ed}" +#else +# define POSIX_FC_EDIT_COMMAND "${FCEDIT:-${EDITOR:-ed}}" +#endif + +int +fc_builtin (list) + WORD_LIST *list; +{ + register int i; + register char *sep; + int numbering, reverse, listing, execute; + int histbeg, histend, last_hist, retval, opt, rh, real_last; + FILE *stream; + REPL *rlist, *rl; + char *ename, *command, *newcom, *fcedit; + HIST_ENTRY **hlist; + char *fn; + + numbering = 1; + reverse = listing = execute = 0; + ename = (char *)NULL; + + /* Parse out the options and set which of the two forms we're in. */ + reset_internal_getopt (); + lcurrent = list; /* XXX */ + while (fc_number (loptend = lcurrent) == 0 && + (opt = internal_getopt (list, ":e:lnrs")) != -1) + { + switch (opt) + { + case 'n': + numbering = 0; + break; + + case 'l': + listing = HN_LISTING; /* for fc_gethnum */ + break; + + case 'r': + reverse = 1; + break; + + case 's': + execute = 1; + break; + + case 'e': + ename = list_optarg; + break; + + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + if (ename && (*ename == '-') && (ename[1] == '\0')) + execute = 1; + + /* The "execute" form of the command (re-run, with possible string + substitutions). */ + if (execute) + { + rlist = (REPL *)NULL; + while (list && ((sep = (char *)strchr (list->word->word, '=')) != NULL)) + { + *sep++ = '\0'; + rl = (REPL *)xmalloc (sizeof (REPL)); + rl->next = (REPL *)NULL; + rl->pat = savestring (list->word->word); + rl->rep = savestring (sep); + + if (rlist == NULL) + rlist = rl; + else + { + rl->next = rlist; + rlist = rl; + } + list = list->next; + } + + /* If we have a list of substitutions to do, then reverse it + to get the replacements in the proper order. */ + + rlist = REVERSE_LIST (rlist, REPL *); + + hlist = history_list (); + + /* If we still have something in list, it is a command spec. + Otherwise, we use the most recent command in time. */ + command = fc_gethist (list ? list->word->word : (char *)NULL, hlist, 0); + + if (command == NULL) + { + builtin_error (_("no command found")); + if (rlist) + FREE_RLIST (); + + return (EXECUTION_FAILURE); + } + + if (rlist) + { + newcom = fc_dosubs (command, rlist); + free (command); + FREE_RLIST (); + command = newcom; + } + + fprintf (stderr, "%s\n", command); + fc_replhist (command); /* replace `fc -s' with command */ + /* Posix says that the re-executed commands should be entered into the + history. */ + return (parse_and_execute (command, "fc", SEVAL_NOHIST)); + } + + /* This is the second form of the command (the list-or-edit-and-rerun + form). */ + hlist = history_list (); + if (hlist == 0) + return (EXECUTION_SUCCESS); + for (i = 0; hlist[i]; i++); + + /* With the Bash implementation of history, the current command line + ("fc blah..." and so on) is already part of the history list by + the time we get to this point. This just skips over that command + and makes the last command that this deals with be the last command + the user entered before the fc. We need to check whether the + line was actually added (HISTIGNORE may have caused it to not be), + so we check hist_last_line_added. */ + + /* Even though command substitution through parse_and_execute turns off + remember_on_history, command substitution in a shell when set -o history + has been enabled (interactive or not) should use it in the last_hist + calculation as if it were on. */ + rh = remember_on_history || ((subshell_environment & SUBSHELL_COMSUB) && enable_history_list); + last_hist = i - rh - hist_last_line_added; + + /* Make sure that real_last is calculated the same way here and in + fc_gethnum. The return value from fc_gethnum is treated specially if + it is == real_last and we are listing commands. */ + real_last = i; + /* back up from the end to the last non-null history entry */ + while (hlist[real_last] == 0 && real_last > 0) + real_last--; + + /* XXX */ + if (i == last_hist && hlist[last_hist] == 0) + while (last_hist >= 0 && hlist[last_hist] == 0) + last_hist--; + if (last_hist < 0) + last_hist = 0; /* per POSIX */ + + if (list) + { + histbeg = fc_gethnum (list->word->word, hlist, listing|HN_FIRST); + list = list->next; + + if (list) + histend = fc_gethnum (list->word->word, hlist, listing); + else if (histbeg == real_last) + histend = listing ? real_last : histbeg; + else + histend = listing ? last_hist : histbeg; + } + else + { + /* The default for listing is the last 16 history items. */ + if (listing) + { + histend = last_hist; + histbeg = histend - 16 + 1; /* +1 because loop below uses >= */ + if (histbeg < 0) + histbeg = 0; + } + else + /* For editing, it is the last history command. */ + histbeg = histend = last_hist; + } + + if (histbeg == HIST_INVALID || histend == HIST_INVALID) + { + sh_erange ((char *)NULL, _("history specification")); + return (EXECUTION_FAILURE); + } + else if (histbeg == HIST_ERANGE || histend == HIST_ERANGE) + { + sh_erange ((char *)NULL, _("history specification")); + return (EXECUTION_FAILURE); + } + else if (histbeg == HIST_NOTFOUND || histend == HIST_NOTFOUND) + { + builtin_error (_("no command found")); + return (EXECUTION_FAILURE); + } + + /* We don't throw an error for line specifications out of range, per POSIX */ + if (histbeg < 0) + histbeg = 0; + if (histend < 0) + histend = 0; + + /* "When not listing, the fc command that caused the editing shall not be + entered into the history list." */ + if (listing == 0 && hist_last_line_added) + { + bash_delete_last_history (); + /* If we're editing a single command -- the last command in the + history -- and we just removed the dummy command added by + edit_and_execute_command (), we need to check whether or not we + just removed the last command in the history and need to back + the pointer up. remember_on_history is off because we're running + in parse_and_execute(). */ + if (histbeg == histend && histend == last_hist && hlist[last_hist] == 0) + last_hist = histbeg = --histend; + + if (hlist[last_hist] == 0) + last_hist--; + if (histend >= last_hist) + histend = last_hist; + else if (histbeg >= last_hist) + histbeg = last_hist; + } + + if (histbeg == HIST_INVALID || histend == HIST_INVALID) + { + sh_erange ((char *)NULL, _("history specification")); + return (EXECUTION_FAILURE); + } + else if (histbeg == HIST_ERANGE || histend == HIST_ERANGE) + { + sh_erange ((char *)NULL, _("history specification")); + return (EXECUTION_FAILURE); + } + else if (histbeg == HIST_NOTFOUND || histend == HIST_NOTFOUND) + { + builtin_error (_("no command found")); + return (EXECUTION_FAILURE); + } + + /* We don't throw an error for line specifications out of range, per POSIX */ + if (histbeg < 0) + histbeg = 0; + if (histend < 0) + histend = 0; + + if (histend < histbeg) + { + i = histend; + histend = histbeg; + histbeg = i; + + reverse = 1; + } + + if (listing) + stream = stdout; + else + { + numbering = 0; + stream = sh_mktmpfp ("bash-fc", MT_USERANDOM|MT_USETMPDIR, &fn); + if (stream == 0) + { + builtin_error (_("%s: cannot open temp file: %s"), fn ? fn : "", strerror (errno)); + FREE (fn); + return (EXECUTION_FAILURE); + } + } + + for (i = reverse ? histend : histbeg; reverse ? i >= histbeg : i <= histend; reverse ? i-- : i++) + { + QUIT; + if (hlist[i] == 0) + continue; + if (numbering) + fprintf (stream, "%d", i + history_base); + if (listing) + { + if (posixly_correct) + fputs ("\t", stream); + else + fprintf (stream, "\t%c", histdata (i) ? '*' : ' '); + } + if (histline (i)) + fprintf (stream, "%s\n", histline (i)); + } + + if (listing) + return (sh_chkwrite (EXECUTION_SUCCESS)); + + fflush (stream); + if (ferror (stream)) + { + sh_wrerror (); + fclose (stream); + FREE (fn); + return (EXECUTION_FAILURE); + } + fclose (stream); + + /* Now edit the file of commands. */ + if (ename) + { + command = (char *)xmalloc (strlen (ename) + strlen (fn) + 2); + sprintf (command, "%s %s", ename, fn); + } + else + { + fcedit = posixly_correct ? POSIX_FC_EDIT_COMMAND : FC_EDIT_COMMAND; + command = (char *)xmalloc (3 + strlen (fcedit) + strlen (fn)); + sprintf (command, "%s %s", fcedit, fn); + } + retval = parse_and_execute (command, "fc", SEVAL_NOHIST); + if (retval != EXECUTION_SUCCESS) + { + unlink (fn); + free (fn); + return (EXECUTION_FAILURE); + } + +#if defined (READLINE) + /* If we're executing as part of a dispatched readline command like + {emacs,vi}_edit_and_execute_command, the readline state will indicate it. + We could remove the partial command from the history, but ksh93 doesn't + so we stay compatible. */ +#endif + + /* Make sure parse_and_execute doesn't turn this off, even though a + call to parse_and_execute farther up the function call stack (e.g., + if this is called by vi_edit_and_execute_command) may have already + called bash_history_disable. */ + remember_on_history = 1; + + /* Turn on the `v' flag while fc_execute_file runs so the commands + will be echoed as they are read by the parser. */ + begin_unwind_frame ("fc builtin"); + add_unwind_protect (xfree, fn); + add_unwind_protect (unlink, fn); + add_unwind_protect (set_verbose_flag, (char *)NULL); + unwind_protect_int (suppress_debug_trap_verbose); + echo_input_at_read = 1; + suppress_debug_trap_verbose = 1; + + retval = fc_execute_file (fn); + run_unwind_frame ("fc builtin"); + + return (retval); +} + +/* Return 1 if LIST->word->word is a legal number for fc's use. */ +static int +fc_number (list) + WORD_LIST *list; +{ + char *s; + + if (list == 0) + return 0; + s = list->word->word; + if (*s == '-') + s++; + return (legal_number (s, (intmax_t *)NULL)); +} + +/* Return an absolute index into HLIST which corresponds to COMMAND. If + COMMAND is a number, then it was specified in relative terms. If it + is a string, then it is the start of a command line present in HLIST. + MODE includes HN_LISTING if we are listing commands, and does not if we + are executing them. If MODE includes HN_FIRST we are looking for the + first history number specification. */ +static int +fc_gethnum (command, hlist, mode) + char *command; + HIST_ENTRY **hlist; + int mode; +{ + int sign, n, clen, rh; + register int i, j, last_hist, real_last, listing; + register char *s; + + listing = mode & HN_LISTING; + sign = 1; + /* Count history elements. */ + for (i = 0; hlist[i]; i++); + + /* With the Bash implementation of history, the current command line + ("fc blah..." and so on) is already part of the history list by + the time we get to this point. This just skips over that command + and makes the last command that this deals with be the last command + the user entered before the fc. We need to check whether the + line was actually added (HISTIGNORE may have caused it to not be), + so we check hist_last_line_added. This needs to agree with the + calculation of last_hist in fc_builtin above. */ + /* Even though command substitution through parse_and_execute turns off + remember_on_history, command substitution in a shell when set -o history + has been enabled (interactive or not) should use it in the last_hist + calculation as if it were on. */ + rh = remember_on_history || ((subshell_environment & SUBSHELL_COMSUB) && enable_history_list); + last_hist = i - rh - hist_last_line_added; + + if (i == last_hist && hlist[last_hist] == 0) + while (last_hist >= 0 && hlist[last_hist] == 0) + last_hist--; + if (last_hist < 0) + return (-1); + + real_last = i; + i = last_hist; + + /* No specification defaults to most recent command. */ + if (command == NULL) + return (i); + + /* back up from the end to the last non-null history entry */ + while (hlist[real_last] == 0 && real_last > 0) + real_last--; + + /* Otherwise, there is a specification. It can be a number relative to + the current position, or an absolute history number. */ + s = command; + + /* Handle possible leading minus sign. */ + if (s && (*s == '-')) + { + sign = -1; + s++; + } + + if (s && DIGIT(*s)) + { + n = atoi (s); + n *= sign; + + /* We want to return something that is an offset to HISTORY_BASE. */ + + /* If the value is negative or zero, then it is an offset from + the current history item. */ + /* We don't use HN_FIRST here, so we don't return different values + depending on whether we're looking for the first or last in a + pair of range arguments, but nobody else does, either. */ + if (n < 0) + { + n += i + 1; + return (n < 0 ? 0 : n); + } + else if (n == 0) + return ((sign == -1) ? (listing ? real_last : HIST_INVALID) : i); + else + { + /* If we're out of range (greater than I (last history entry) or + less than HISTORY_BASE, we want to return different values + based on whether or not we are looking for the first or last + value in a desired range of history entries. */ + n -= history_base; + if (n < 0) + return (mode & HN_FIRST ? 0 : i); + else if (n >= i) + return (mode & HN_FIRST ? 0 : i); + else + return n; + } + } + + clen = strlen (command); + for (j = i; j >= 0; j--) + { + if (STREQN (command, histline (j), clen)) + return (j); + } + return (HIST_NOTFOUND); +} + +/* Locate the most recent history line which begins with + COMMAND in HLIST, and return a malloc()'ed copy of it. + MODE is 1 if we are listing commands, 0 if we are executing them. */ +static char * +fc_gethist (command, hlist, mode) + char *command; + HIST_ENTRY **hlist; + int mode; +{ + int i; + + if (hlist == 0) + return ((char *)NULL); + + i = fc_gethnum (command, hlist, mode); + + if (i >= 0) + return (savestring (histline (i))); + else + return ((char *)NULL); +} + +#ifdef INCLUDE_UNUSED +/* Read the edited history lines from STREAM and return them + one at a time. This can read unlimited length lines. The + caller should free the storage. */ +static char * +fc_readline (stream) + FILE *stream; +{ + register int c; + int line_len = 0, lindex = 0; + char *line = (char *)NULL; + + while ((c = getc (stream)) != EOF) + { + if ((lindex + 2) >= line_len) + line = (char *)xrealloc (line, (line_len += 128)); + + if (c == '\n') + { + line[lindex++] = '\n'; + line[lindex++] = '\0'; + return (line); + } + else + line[lindex++] = c; + } + + if (!lindex) + { + if (line) + free (line); + + return ((char *)NULL); + } + + if (lindex + 2 >= line_len) + line = (char *)xrealloc (line, lindex + 3); + + line[lindex++] = '\n'; /* Finish with newline if none in file */ + line[lindex++] = '\0'; + return (line); +} +#endif + +/* Perform the SUBS on COMMAND. + SUBS is a list of substitutions, and COMMAND is a simple string. + Return a pointer to a malloc'ed string which contains the substituted + command. */ +static char * +fc_dosubs (command, subs) + char *command; + REPL *subs; +{ + register char *new, *t; + register REPL *r; + + for (new = savestring (command), r = subs; r; r = r->next) + { + t = strsub (new, r->pat, r->rep, 1); + free (new); + new = t; + } + return (new); +} + +/* Use `command' to replace the last entry in the history list, which, + by this time, is `fc blah...'. The intent is that the new command + become the history entry, and that `fc' should never appear in the + history list. This way you can do `r' to your heart's content. */ +static void +fc_replhist (command) + char *command; +{ + int n; + + if (command == 0 || *command == '\0') + return; + + n = strlen (command); + if (command[n - 1] == '\n') + command[n - 1] = '\0'; + + if (command && *command) + { + bash_delete_last_history (); + maybe_add_history (command); /* Obeys HISTCONTROL setting. */ + } +} + +#ifdef INCLUDE_UNUSED +/* Add LINE to the history, after removing a single trailing newline. */ +static void +fc_addhist (line) + char *line; +{ + register int n; + + if (line == 0 || *line == 0) + return; + + n = strlen (line); + + if (line[n - 1] == '\n') + line[n - 1] = '\0'; + + if (line && *line) + maybe_add_history (line); /* Obeys HISTCONTROL setting. */ +} +#endif + +#endif /* HISTORY */ diff --git a/third_party/bash/builtins_fg_bg.c b/third_party/bash/builtins_fg_bg.c new file mode 100644 index 000000000..93ce42d19 --- /dev/null +++ b/third_party/bash/builtins_fg_bg.c @@ -0,0 +1,146 @@ +/* fg_bg.c, created from fg_bg.def. */ +#line 22 "./fg_bg.def" + +#line 36 "./fg_bg.def" + +#include "config.h" + +#include "bashtypes.h" +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "jobs.h" +#include "common.h" +#include "bashgetopt.h" + +#if defined (JOB_CONTROL) +static int fg_bg PARAMS((WORD_LIST *, int)); + +/* How to bring a job into the foreground. */ +int +fg_builtin (list) + WORD_LIST *list; +{ + int fg_bit; + register WORD_LIST *t; + + CHECK_HELPOPT (list); + + if (job_control == 0) + { + sh_nojobs ((char *)NULL); + return (EXECUTION_FAILURE); + } + + if (no_options (list)) + return (EX_USAGE); + list = loptend; + + /* If the last arg on the line is '&', then start this job in the + background. Else, fg the job. */ + for (t = list; t && t->next; t = t->next) + ; + fg_bit = (t && t->word->word[0] == '&' && t->word->word[1] == '\0') == 0; + + return (fg_bg (list, fg_bit)); +} +#endif /* JOB_CONTROL */ + +#line 100 "./fg_bg.def" + +#if defined (JOB_CONTROL) +/* How to put a job into the background. */ +int +bg_builtin (list) + WORD_LIST *list; +{ + int r; + + CHECK_HELPOPT (list); + + if (job_control == 0) + { + sh_nojobs ((char *)NULL); + return (EXECUTION_FAILURE); + } + + if (no_options (list)) + return (EX_USAGE); + list = loptend; + + /* This relies on the fact that fg_bg() takes a WORD_LIST *, but only acts + on the first member (if any) of that list. */ + r = EXECUTION_SUCCESS; + do + { + if (fg_bg (list, 0) == EXECUTION_FAILURE) + r = EXECUTION_FAILURE; + if (list) + list = list->next; + } + while (list); + + return r; +} + +/* How to put a job into the foreground/background. */ +static int +fg_bg (list, foreground) + WORD_LIST *list; + int foreground; +{ + sigset_t set, oset; + int job, status, old_async_pid; + JOB *j; + + BLOCK_CHILD (set, oset); + job = get_job_spec (list); + + if (INVALID_JOB (job)) + { + if (job != DUP_JOB) + sh_badjob (list ? list->word->word : _("current")); + + goto failure; + } + + j = get_job_by_jid (job); + /* Or if j->pgrp == shell_pgrp. */ + if (IS_JOBCONTROL (job) == 0) + { + builtin_error (_("job %d started without job control"), job + 1); + goto failure; + } + + if (foreground == 0) + { + old_async_pid = last_asynchronous_pid; + last_asynchronous_pid = j->pgrp; /* As per Posix.2 5.4.2 */ + } + + status = start_job (job, foreground); + + if (status >= 0) + { + /* win: */ + UNBLOCK_CHILD (oset); + return (foreground ? status : EXECUTION_SUCCESS); + } + else + { + if (foreground == 0) + last_asynchronous_pid = old_async_pid; + + failure: + UNBLOCK_CHILD (oset); + return (EXECUTION_FAILURE); + } +} +#endif /* JOB_CONTROL */ diff --git a/third_party/bash/builtins_getopts.c b/third_party/bash/builtins_getopts.c new file mode 100644 index 000000000..961906259 --- /dev/null +++ b/third_party/bash/builtins_getopts.c @@ -0,0 +1,284 @@ +/* getopts.c, created from getopts.def. */ +#line 22 "./getopts.def" + +#line 64 "./getopts.def" + +#include "config.h" + +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "common.h" +#include "bashgetopt.h" +#include "getopt.h" + +#define G_EOF -1 +#define G_INVALID_OPT -2 +#define G_ARG_MISSING -3 + +static int getopts_unbind_variable PARAMS((char *)); +static int getopts_bind_variable PARAMS((char *, char *)); +static int dogetopts PARAMS((int, char **)); + +/* getopts_reset is magic code for when OPTIND is reset. N is the + value that has just been assigned to OPTIND. */ +void +getopts_reset (newind) + int newind; +{ + sh_optind = newind; + sh_badopt = 0; +} + +static int +getopts_unbind_variable (name) + char *name; +{ +#if 0 + return (unbind_variable (name)); +#else + return (unbind_variable_noref (name)); +#endif +} + +static int +getopts_bind_variable (name, value) + char *name, *value; +{ + SHELL_VAR *v; + + if (legal_identifier (name)) + { + v = bind_variable (name, value, 0); + if (v && (readonly_p (v) || noassign_p (v))) + return (EX_MISCERROR); + return (v ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + } + else + { + sh_invalidid (name); + return (EXECUTION_FAILURE); + } +} + +/* Error handling is now performed as specified by Posix.2, draft 11 + (identical to that of ksh-88). The special handling is enabled if + the first character of the option string is a colon; this handling + disables diagnostic messages concerning missing option arguments + and invalid option characters. The handling is as follows. + + INVALID OPTIONS: + name -> "?" + if (special_error) then + OPTARG = option character found + no error output + else + OPTARG unset + diagnostic message + fi + + MISSING OPTION ARGUMENT; + if (special_error) then + name -> ":" + OPTARG = option character found + else + name -> "?" + OPTARG unset + diagnostic message + fi + */ + +static int +dogetopts (argc, argv) + int argc; + char **argv; +{ + int ret, special_error, old_opterr, i, n; + char strval[2], numval[16]; + char *optstr; /* list of options */ + char *name; /* variable to get flag val */ + char *t; + + if (argc < 3) + { + builtin_usage (); + return (EX_USAGE); + } + + /* argv[0] is "getopts". */ + + optstr = argv[1]; + name = argv[2]; + argc -= 2; + argv += 2; + + special_error = optstr[0] == ':'; + + if (special_error) + { + old_opterr = sh_opterr; + optstr++; + sh_opterr = 0; /* suppress diagnostic messages */ + } + + if (argc > 1) + { + sh_getopt_restore_state (argv); + t = argv[0]; + argv[0] = dollar_vars[0]; + ret = sh_getopt (argc, argv, optstr); + argv[0] = t; + } + else if (rest_of_args == (WORD_LIST *)NULL) + { + for (i = 0; i < 10 && dollar_vars[i]; i++) + ; + + sh_getopt_restore_state (dollar_vars); + ret = sh_getopt (i, dollar_vars, optstr); + } + else + { + register WORD_LIST *words; + char **v; + + i = number_of_args () + 1; /* +1 for $0 */ + v = strvec_create (i + 1); + for (i = 0; i < 10 && dollar_vars[i]; i++) + v[i] = dollar_vars[i]; + for (words = rest_of_args; words; words = words->next, i++) + v[i] = words->word->word; + v[i] = (char *)NULL; + sh_getopt_restore_state (v); + ret = sh_getopt (i, v, optstr); + free (v); + } + + if (special_error) + sh_opterr = old_opterr; + + /* Set the OPTIND variable in any case, to handle "--" skipping. It's + highly unlikely that 14 digits will be too few. */ + if (sh_optind < 10) + { + numval[14] = sh_optind + '0'; + numval[15] = '\0'; + i = 14; + } + else + { + numval[i = 15] = '\0'; + n = sh_optind; + do + { + numval[--i] = (n % 10) + '0'; + } + while (n /= 10); + } + bind_variable ("OPTIND", numval + i, 0); + + /* If an error occurred, decide which one it is and set the return + code appropriately. In all cases, the option character in error + is in OPTOPT. If an invalid option was encountered, OPTARG is + NULL. If a required option argument was missing, OPTARG points + to a NULL string (that is, sh_optarg[0] == 0). */ + if (ret == '?') + { + if (sh_optarg == NULL) + ret = G_INVALID_OPT; + else if (sh_optarg[0] == '\0') + ret = G_ARG_MISSING; + } + + if (ret == G_EOF) + { + getopts_unbind_variable ("OPTARG"); + getopts_bind_variable (name, "?"); + return (EXECUTION_FAILURE); + } + + if (ret == G_INVALID_OPT) + { + /* Invalid option encountered. */ + ret = getopts_bind_variable (name, "?"); + + if (special_error) + { + strval[0] = (char)sh_optopt; + strval[1] = '\0'; + bind_variable ("OPTARG", strval, 0); + } + else + getopts_unbind_variable ("OPTARG"); + + return (ret); + } + + if (ret == G_ARG_MISSING) + { + /* Required argument missing. */ + if (special_error) + { + ret = getopts_bind_variable (name, ":"); + + strval[0] = (char)sh_optopt; + strval[1] = '\0'; + bind_variable ("OPTARG", strval, 0); + } + else + { + ret = getopts_bind_variable (name, "?"); + getopts_unbind_variable ("OPTARG"); + } + return (ret); + } + + bind_variable ("OPTARG", sh_optarg, 0); + + strval[0] = (char) ret; + strval[1] = '\0'; + return (getopts_bind_variable (name, strval)); +} + +/* The getopts builtin. Build an argv, and call dogetopts with it. */ +int +getopts_builtin (list) + WORD_LIST *list; +{ + char **av; + int ac, ret; + + if (list == 0) + { + builtin_usage (); + return EX_USAGE; + } + + reset_internal_getopt (); + if ((ret = internal_getopt (list, "")) != -1) + { + if (ret == GETOPT_HELP) + builtin_help (); + else + builtin_usage (); + return (EX_USAGE); + } + list = loptend; + + av = make_builtin_argv (list, &ac); + ret = dogetopts (ac, av); + free ((char *)av); + + return (ret); +} diff --git a/third_party/bash/builtins_hash.c b/third_party/bash/builtins_hash.c new file mode 100644 index 000000000..39fc71948 --- /dev/null +++ b/third_party/bash/builtins_hash.c @@ -0,0 +1,264 @@ +/* hash.c, created from hash.def. */ +#line 22 "./hash.def" + +#line 46 "./hash.def" + +#include "config.h" + +#include + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "builtins.h" +#include "execute_cmd.h" +#include "flags.h" +#include "findcmd.h" +#include "hashcmd.h" +#include "common.h" +#include "bashgetopt.h" + +extern int dot_found_in_search; + +static int add_hashed_command PARAMS((char *, int)); +static int print_hash_info PARAMS((BUCKET_CONTENTS *)); +static int print_portable_hash_info PARAMS((BUCKET_CONTENTS *)); +static int print_hashed_commands PARAMS((int)); +static int list_hashed_filename_targets PARAMS((WORD_LIST *, int)); + +/* Print statistics on the current state of hashed commands. If LIST is + not empty, then rehash (or hash in the first place) the specified + commands. */ +int +hash_builtin (list) + WORD_LIST *list; +{ + int expunge_hash_table, list_targets, list_portably, delete, opt; + char *w, *pathname; + + if (hashing_enabled == 0) + { + builtin_error (_("hashing disabled")); + return (EXECUTION_FAILURE); + } + + expunge_hash_table = list_targets = list_portably = delete = 0; + pathname = (char *)NULL; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "dlp:rt")) != -1) + { + switch (opt) + { + case 'd': + delete = 1; + break; + case 'l': + list_portably = 1; + break; + case 'p': + pathname = list_optarg; + break; + case 'r': + expunge_hash_table = 1; + break; + case 't': + list_targets = 1; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + /* hash -t requires at least one argument. */ + if (list == 0 && (delete || list_targets)) + { + sh_needarg (delete ? "-d" : "-t"); + return (EXECUTION_FAILURE); + } + + /* We want hash -r to be silent, but hash -- to print hashing info, so + we test expunge_hash_table. */ + if (list == 0 && expunge_hash_table == 0) + { + opt = print_hashed_commands (list_portably); + if (opt == 0 && posixly_correct == 0 && + (list_portably == 0 || shell_compatibility_level <= 50)) + printf (_("%s: hash table empty\n"), this_command_name); + + return (sh_chkwrite (EXECUTION_SUCCESS)); + } + + if (expunge_hash_table) + phash_flush (); + + /* If someone runs `hash -r -t xyz' he will be disappointed. */ + if (list_targets) + return (list_hashed_filename_targets (list, list_portably)); + +#if defined (RESTRICTED_SHELL) + if (restricted && pathname) + { + if (strchr (pathname, '/')) + { + sh_restricted (pathname); + return (EXECUTION_FAILURE); + } + /* If we are changing the hash table in a restricted shell, make sure the + target pathname can be found using a $PATH search. */ + w = find_user_command (pathname); + if (w == 0 || *w == 0 || executable_file (w) == 0) + { + sh_notfound (pathname); + free (w); + return (EXECUTION_FAILURE); + } + free (w); + } +#endif + + for (opt = EXECUTION_SUCCESS; list; list = list->next) + { + /* Add, remove or rehash the specified commands. */ + w = list->word->word; + if (absolute_program (w)) + continue; + else if (pathname) + { + if (file_isdir (pathname)) + { +#ifdef EISDIR + builtin_error ("%s: %s", pathname, strerror (EISDIR)); +#else + builtin_error (_("%s: is a directory"), pathname); +#endif + opt = EXECUTION_FAILURE; + } + else + phash_insert (w, pathname, 0, 0); + } + else if (delete) + { + if (phash_remove (w)) + { + sh_notfound (w); + opt = EXECUTION_FAILURE; + } + } + else if (add_hashed_command (w, 0)) + opt = EXECUTION_FAILURE; + } + + fflush (stdout); + return (opt); +} + +static int +add_hashed_command (w, quiet) + char *w; + int quiet; +{ + int rv; + char *full_path; + + rv = 0; + if (find_function (w) == 0 && find_shell_builtin (w) == 0) + { + phash_remove (w); + full_path = find_user_command (w); + if (full_path && executable_file (full_path)) + phash_insert (w, full_path, dot_found_in_search, 0); + else + { + if (quiet == 0) + sh_notfound (w); + rv++; + } + FREE (full_path); + } + return (rv); +} + +/* Print information about current hashed info. */ +static int +print_hash_info (item) + BUCKET_CONTENTS *item; +{ + printf ("%4d\t%s\n", item->times_found, pathdata(item)->path); + return 0; +} + +static int +print_portable_hash_info (item) + BUCKET_CONTENTS *item; +{ + char *fp, *fn; + + fp = printable_filename (pathdata(item)->path, 1); + fn = printable_filename (item->key, 1); + printf ("builtin hash -p %s %s\n", fp, fn); + if (fp != pathdata(item)->path) + free (fp); + if (fn != item->key) + free (fn); + return 0; +} + +static int +print_hashed_commands (fmt) + int fmt; +{ + if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0) + return (0); + + if (fmt == 0) + printf (_("hits\tcommand\n")); + hash_walk (hashed_filenames, fmt ? print_portable_hash_info : print_hash_info); + return (1); +} + +static int +list_hashed_filename_targets (list, fmt) + WORD_LIST *list; + int fmt; +{ + int all_found, multiple; + char *target; + WORD_LIST *l; + + all_found = 1; + multiple = list->next != 0; + + for (l = list; l; l = l->next) + { + target = phash_search (l->word->word); + if (target == 0) + { + all_found = 0; + sh_notfound (l->word->word); + continue; + } + if (fmt) + printf ("builtin hash -p %s %s\n", target, l->word->word); + else + { + if (multiple) + printf ("%s\t", l->word->word); + printf ("%s\n", target); + } + free (target); + } + + return (all_found ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +} diff --git a/third_party/bash/builtins_help.c b/third_party/bash/builtins_help.c new file mode 100644 index 000000000..cbb015c46 --- /dev/null +++ b/third_party/bash/builtins_help.c @@ -0,0 +1,512 @@ +/* help.c, created from help.def. */ +#line 22 "./help.def" + +#line 45 "./help.def" + +#include "config.h" + +#if defined (HELP_BUILTIN) +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include + +#include "filecntl.h" +#include + +#include "bashintl.h" + +#include "shell.h" +#include "builtins.h" +#include "execute_cmd.h" +#include "pathexp.h" +#include "common.h" +#include "bashgetopt.h" + +#include "strmatch.h" +#include "glob.h" + +#ifndef errno +extern int errno; +#endif + +extern const char * const bash_copyright; +extern const char * const bash_license; + +static void show_builtin_command_help PARAMS((void)); +static int open_helpfile PARAMS((char *)); +static void show_desc PARAMS((char *, int)); +static void show_manpage PARAMS((char *, int)); +static void show_longdoc PARAMS((int)); + +/* Print out a list of the known functions in the shell, and what they do. + If LIST is supplied, print out the list which matches for each pattern + specified. */ +int +help_builtin (list) + WORD_LIST *list; +{ + register int i; + char *pattern, *name; + int plen, match_found, sflag, dflag, mflag, m, pass, this_found; + + dflag = sflag = mflag = 0; + reset_internal_getopt (); + while ((i = internal_getopt (list, "dms")) != -1) + { + switch (i) + { + case 'd': + dflag = 1; + break; + case 'm': + mflag = 1; + break; + case 's': + sflag = 1; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if (list == 0) + { + show_shell_version (0); + show_builtin_command_help (); + return (EXECUTION_SUCCESS); + } + + /* We should consider making `help bash' do something. */ + + if (glob_pattern_p (list->word->word) == 1) + { + printf ("%s", ngettext ("Shell commands matching keyword `", "Shell commands matching keywords `", (list->next ? 2 : 1))); + print_word_list (list, ", "); + printf ("%s", _("'\n\n")); + } + + for (match_found = 0, pattern = ""; list; list = list->next) + { + pattern = list->word->word; + plen = strlen (pattern); + + for (pass = 1, this_found = 0; pass < 3; pass++) + { + for (i = 0; name = shell_builtins[i].name; i++) + { + QUIT; + + /* First pass: look for exact string or pattern matches. + Second pass: look for prefix matches like bash-4.2 */ + if (pass == 1) + m = (strcmp (pattern, name) == 0) || + (strmatch (pattern, name, FNMATCH_EXTFLAG) != FNM_NOMATCH); + else + m = strncmp (pattern, name, plen) == 0; + + if (m) + { + this_found = 1; + match_found++; + if (dflag) + { + show_desc (name, i); + continue; + } + else if (mflag) + { + show_manpage (name, i); + continue; + } + + printf ("%s: %s\n", name, _(shell_builtins[i].short_doc)); + + if (sflag == 0) + show_longdoc (i); + } + } + if (pass == 1 && this_found == 1) + break; + } + } + + if (match_found == 0) + { + builtin_error (_("no help topics match `%s'. Try `help help' or `man -k %s' or `info %s'."), pattern, pattern, pattern); + return (EXECUTION_FAILURE); + } + + return (sh_chkwrite (EXECUTION_SUCCESS)); +} + +void +builtin_help () +{ + int ind; + ptrdiff_t d; + + current_builtin = builtin_address_internal (this_command_name, 0); + if (current_builtin == 0) + return; + + d = current_builtin - shell_builtins; + +#if defined (__STDC__) + ind = (int)d; +#else + ind = (int)d / sizeof (struct builtin); +#endif + + printf ("%s: %s\n", this_command_name, _(shell_builtins[ind].short_doc)); + show_longdoc (ind); +} + +static int +open_helpfile (name) + char *name; +{ + int fd; + + fd = open (name, O_RDONLY); + if (fd == -1) + { + builtin_error (_("%s: cannot open: %s"), name, strerror (errno)); + return -1; + } + return fd; +} + +/* By convention, enforced by mkbuiltins.c, if separate help files are being + used, the long_doc array contains one string -- the full pathname of the + help file for this builtin. */ +static void +show_longdoc (i) + int i; +{ + register int j; + char * const *doc; + int fd; + + doc = shell_builtins[i].long_doc; + + if (doc && doc[0] && *doc[0] == '/' && doc[1] == (char *)NULL) + { + fd = open_helpfile (doc[0]); + if (fd < 0) + return; + zcatfd (fd, 1, doc[0]); + close (fd); + } + else if (doc) + for (j = 0; doc[j]; j++) + printf ("%*s%s\n", BASE_INDENT, " ", _(doc[j])); +} + +static void +show_desc (name, i) + char *name; + int i; +{ + register int j, r; + char **doc, *line; + int fd, usefile; + + doc = (char **)shell_builtins[i].long_doc; + + usefile = (doc && doc[0] && *doc[0] == '/' && doc[1] == (char *)NULL); + if (usefile) + { + fd = open_helpfile (doc[0]); + if (fd < 0) + return; + r = zmapfd (fd, &line, doc[0]); + close (fd); + /* XXX - handle errors if zmapfd returns < 0 */ + } + else + line = doc ? doc[0] : (char *)NULL; + + printf ("%s - ", name); + for (j = 0; line && line[j]; j++) + { + putchar (line[j]); + if (line[j] == '\n') + break; + } + + fflush (stdout); + + if (usefile) + free (line); +} + +/* Print builtin help in pseudo-manpage format. */ +static void +show_manpage (name, i) + char *name; + int i; +{ + register int j; + char **doc, *line; + int fd, usefile; + + doc = (char **)shell_builtins[i].long_doc; + + usefile = (doc && doc[0] && *doc[0] == '/' && doc[1] == (char *)NULL); + if (usefile) + { + fd = open_helpfile (doc[0]); + if (fd < 0) + return; + zmapfd (fd, &line, doc[0]); + close (fd); + } + else + line = doc ? _(doc[0]) : (char *)NULL; + + /* NAME */ + printf ("NAME\n"); + printf ("%*s%s - ", BASE_INDENT, " ", name); + for (j = 0; line && line[j]; j++) + { + putchar (line[j]); + if (line[j] == '\n') + break; + } + printf ("\n"); + + /* SYNOPSIS */ + printf ("SYNOPSIS\n"); + printf ("%*s%s\n\n", BASE_INDENT, " ", _(shell_builtins[i].short_doc)); + + /* DESCRIPTION */ + printf ("DESCRIPTION\n"); + if (usefile == 0) + { + for (j = 0; doc[j]; j++) + printf ("%*s%s\n", BASE_INDENT, " ", _(doc[j])); + } + else + { + for (j = 0; line && line[j]; j++) + { + putchar (line[j]); + if (line[j] == '\n') + printf ("%*s", BASE_INDENT, " "); + } + } + putchar ('\n'); + + /* SEE ALSO */ + printf ("SEE ALSO\n"); + printf ("%*sbash(1)\n\n", BASE_INDENT, " "); + + /* IMPLEMENTATION */ + printf ("IMPLEMENTATION\n"); + printf ("%*s", BASE_INDENT, " "); + show_shell_version (0); + printf ("%*s", BASE_INDENT, " "); + printf ("%s\n", _(bash_copyright)); + printf ("%*s", BASE_INDENT, " "); + printf ("%s\n", _(bash_license)); + + fflush (stdout); + if (usefile) + free (line); +} + +static void +dispcolumn (i, buf, bufsize, width, height) + int i; + char *buf; + size_t bufsize; + int width, height; +{ + int j; + int dispcols; + char *helpdoc; + + /* first column */ + helpdoc = _(shell_builtins[i].short_doc); + + buf[0] = (shell_builtins[i].flags & BUILTIN_ENABLED) ? ' ' : '*'; + strncpy (buf + 1, helpdoc, width - 2); + buf[width - 2] = '>'; /* indicate truncation */ + buf[width - 1] = '\0'; + printf ("%s", buf); + if (((i << 1) >= num_shell_builtins) || (i+height >= num_shell_builtins)) + { + printf ("\n"); + return; + } + + dispcols = strlen (buf); + /* two spaces */ + for (j = dispcols; j < width; j++) + putc (' ', stdout); + + /* second column */ + helpdoc = _(shell_builtins[i+height].short_doc); + + buf[0] = (shell_builtins[i+height].flags & BUILTIN_ENABLED) ? ' ' : '*'; + strncpy (buf + 1, helpdoc, width - 3); + buf[width - 3] = '>'; /* indicate truncation */ + buf[width - 2] = '\0'; + + printf ("%s\n", buf); +} + +#if defined (HANDLE_MULTIBYTE) +static void +wdispcolumn (i, buf, bufsize, width, height) + int i; + char *buf; + size_t bufsize; + int width, height; +{ + int j; + int dispcols, dispchars; + char *helpdoc; + wchar_t *wcstr; + size_t slen, n; + + /* first column */ + helpdoc = _(shell_builtins[i].short_doc); + + wcstr = 0; + slen = mbstowcs ((wchar_t *)0, helpdoc, 0); + if (slen == -1) + { + dispcolumn (i, buf, bufsize, width, height); + return; + } + + /* No bigger than the passed max width */ + if (slen >= width) + slen = width - 2; + wcstr = (wchar_t *)xmalloc (sizeof (wchar_t) * (width + 2)); + n = mbstowcs (wcstr+1, helpdoc, slen + 1); + wcstr[n+1] = L'\0'; + + /* Turn tabs and newlines into spaces for column display, since wcwidth + returns -1 for them */ + for (j = 1; j < n; j++) + if (wcstr[j] == L'\n' || wcstr[j] == L'\t') + wcstr[j] = L' '; + + /* dispchars == number of characters that will be displayed */ + dispchars = wcsnwidth (wcstr+1, slen, width - 2); + /* dispcols == number of columns required to display DISPCHARS */ + dispcols = wcswidth (wcstr+1, dispchars) + 1; /* +1 for ' ' or '*' */ + + wcstr[0] = (shell_builtins[i].flags & BUILTIN_ENABLED) ? L' ' : L'*'; + + if (dispcols >= width-2) + { + wcstr[dispchars] = L'>'; /* indicate truncation */ + wcstr[dispchars+1] = L'\0'; + } + + printf ("%ls", wcstr); + if (((i << 1) >= num_shell_builtins) || (i+height >= num_shell_builtins)) + { + printf ("\n"); + free (wcstr); + return; + } + + /* at least one space */ + for (j = dispcols; j < width; j++) + putc (' ', stdout); + + /* second column */ + helpdoc = _(shell_builtins[i+height].short_doc); + slen = mbstowcs ((wchar_t *)0, helpdoc, 0); + if (slen == -1) + { + /* for now */ + printf ("%c%s\n", (shell_builtins[i+height].flags & BUILTIN_ENABLED) ? ' ' : '*', helpdoc); + free (wcstr); + return; + } + + /* Reuse wcstr since it is already width wide chars long */ + if (slen >= width) + slen = width - 2; + n = mbstowcs (wcstr+1, helpdoc, slen + 1); + wcstr[n+1] = L'\0'; /* make sure null-terminated */ + + /* Turn tabs and newlines into spaces for column display */ + for (j = 1; j < n; j++) + if (wcstr[j] == L'\n' || wcstr[j] == L'\t') + wcstr[j] = L' '; + + /* dispchars == number of characters that will be displayed */ + dispchars = wcsnwidth (wcstr+1, slen, width - 2); + dispcols = wcswidth (wcstr+1, dispchars) + 1; /* +1 for ' ' or '*' */ + + wcstr[0] = (shell_builtins[i+height].flags & BUILTIN_ENABLED) ? L' ' : L'*'; + + /* The dispchars-1 is there for terminals that behave strangely when you + have \n in the nth column for terminal width n; this is what bash-4.3 + did. */ + if (dispcols >= width - 2) + { + wcstr[dispchars-1] = L'>'; /* indicate truncation */ + wcstr[dispchars] = L'\0'; + } + + printf ("%ls\n", wcstr); + + free (wcstr); +} +#endif /* HANDLE_MULTIBYTE */ + +static void +show_builtin_command_help () +{ + int i, j; + int height, width; + char *t, blurb[128]; + + printf ( +_("These shell commands are defined internally. Type `help' to see this list.\n\ +Type `help name' to find out more about the function `name'.\n\ +Use `info bash' to find out more about the shell in general.\n\ +Use `man -k' or `info' to find out more about commands not in this list.\n\ +\n\ +A star (*) next to a name means that the command is disabled.\n\ +\n")); + + width = default_columns (); + + width /= 2; + if (width > sizeof (blurb)) + width = sizeof (blurb); + if (width <= 3) + width = 40; + height = (num_shell_builtins + 1) / 2; /* number of rows */ + + for (i = 0; i < height; i++) + { + QUIT; + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + wdispcolumn (i, blurb, sizeof (blurb), width, height); + else +#endif + dispcolumn (i, blurb, sizeof (blurb), width, height); + } +} +#endif /* HELP_BUILTIN */ diff --git a/third_party/bash/builtins_history.c b/third_party/bash/builtins_history.c new file mode 100644 index 000000000..7a6a833e1 --- /dev/null +++ b/third_party/bash/builtins_history.c @@ -0,0 +1,411 @@ +/* history.c, created from history.def. */ +#line 22 "./history.def" + +#line 58 "./history.def" + +#include "config.h" + +#if defined (HISTORY) +#include "bashtypes.h" +#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "posixstat.h" +#include "filecntl.h" +#include +#include +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "flags.h" +#include "parser.h" +#include "bashhist.h" +#include "third_party/readline/history.h" +#include "bashgetopt.h" +#include "common.h" + +#if !defined (errno) +extern int errno; +#endif + +static char *histtime PARAMS((HIST_ENTRY *, const char *)); +static int display_history PARAMS((WORD_LIST *)); +static void push_history PARAMS((WORD_LIST *)); +static int expand_and_print_history PARAMS((WORD_LIST *)); + +#define AFLAG 0x01 +#define RFLAG 0x02 +#define WFLAG 0x04 +#define NFLAG 0x08 +#define SFLAG 0x10 +#define PFLAG 0x20 +#define CFLAG 0x40 +#define DFLAG 0x80 + +#ifndef TIMELEN_MAX +# define TIMELEN_MAX 128 +#endif + +int +history_builtin (list) + WORD_LIST *list; +{ + int flags, opt, result, old_history_lines, obase, ind; + char *filename, *delete_arg, *range; + intmax_t delete_offset; + + flags = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "acd:npsrw")) != -1) + { + switch (opt) + { + case 'a': + flags |= AFLAG; + break; + case 'c': + flags |= CFLAG; + break; + case 'n': + flags |= NFLAG; + break; + case 'r': + flags |= RFLAG; + break; + case 'w': + flags |= WFLAG; + break; + case 's': + flags |= SFLAG; + break; + case 'd': + flags |= DFLAG; + delete_arg = list_optarg; + break; + case 'p': +#if defined (BANG_HISTORY) + flags |= PFLAG; +#endif + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + opt = flags & (AFLAG|RFLAG|WFLAG|NFLAG); + if (opt && opt != AFLAG && opt != RFLAG && opt != WFLAG && opt != NFLAG) + { + builtin_error (_("cannot use more than one of -anrw")); + return (EXECUTION_FAILURE); + } + + /* clear the history, but allow other arguments to add to it again. */ + if (flags & CFLAG) + { + bash_clear_history (); + if (list == 0) + return (EXECUTION_SUCCESS); + } + + if (flags & SFLAG) + { + if (list) + push_history (list); + return (EXECUTION_SUCCESS); + } +#if defined (BANG_HISTORY) + else if (flags & PFLAG) + { + if (list) + return (expand_and_print_history (list)); + return (sh_chkwrite (EXECUTION_SUCCESS)); + } +#endif + else if ((flags & DFLAG) && (range = strchr ((delete_arg[0] == '-') ? delete_arg + 1 : delete_arg, '-'))) + { + intmax_t delete_start, delete_end; + *range++ = '\0'; + if (legal_number (delete_arg, &delete_start) == 0 || legal_number (range, &delete_end) == 0) + { + range[-1] = '-'; + sh_erange (delete_arg, _("history position")); + return (EXECUTION_FAILURE); + } + if (delete_arg[0] == '-' && delete_start < 0) + /* the_history[history_length] == 0x0, so this is correct */ + delete_start += history_length; + /* numbers as displayed by display_history are offset by history_base */ + else if (delete_start > 0) + delete_start -= history_base; + + if (delete_start < 0 || delete_start >= history_length) + { + sh_erange (delete_arg, _("history position")); + return (EXECUTION_FAILURE); + } + + if (range[0] == '-' && delete_end < 0) + delete_end += history_length; + else if (delete_end > 0) + delete_end -= history_base; + + if (delete_end < 0 || delete_end >= history_length) + { + sh_erange (range, _("history position")); + return (EXECUTION_FAILURE); + } + /* XXX - print error if end < start? */ + result = bash_delete_history_range (delete_start, delete_end); + if (where_history () > history_length) + history_set_pos (history_length); + return (result ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + } + else if (flags & DFLAG) + { + if (legal_number (delete_arg, &delete_offset) == 0) + { + sh_erange (delete_arg, _("history position")); + return (EXECUTION_FAILURE); + } + /* check for negative offsets, count back from end of list */ + if (delete_arg[0] == '-' && delete_offset < 0) + { + /* since the_history[history_length] == 0x0, this calculation means + that history -d -1 will delete the last history entry, which at + this point is the history -d -1 we just added. */ + ind = history_length + delete_offset; + if (ind < 0) /* offset by history_base below */ + { + sh_erange (delete_arg, _("history position")); + return (EXECUTION_FAILURE); + } + opt = ind + history_base; /* compensate for opt - history_base below */ + } + else if ((delete_offset < history_base) || (delete_offset >= (history_base + history_length))) + { + sh_erange (delete_arg, _("history position")); + return (EXECUTION_FAILURE); + } + else + opt = delete_offset; + + /* Positive arguments from numbers as displayed by display_history need + to be offset by history_base */ + result = bash_delete_histent (opt - history_base); + /* Since remove_history changes history_length, this can happen if + we delete the last history entry. */ + if (where_history () > history_length) + history_set_pos (history_length); + return (result ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + } + else if ((flags & (AFLAG|RFLAG|NFLAG|WFLAG|CFLAG)) == 0) + { + result = display_history (list); + return (sh_chkwrite (result)); + } + + filename = list ? list->word->word : get_string_value ("HISTFILE"); + result = EXECUTION_SUCCESS; + +#if defined (RESTRICTED_SHELL) + if (restricted && strchr (filename, '/')) + { + sh_restricted (filename); + return (EXECUTION_FAILURE); + } +#endif + + if (flags & AFLAG) /* Append session's history to file. */ + result = maybe_append_history (filename); + else if (flags & WFLAG) /* Write entire history. */ + result = write_history (filename); + else if (flags & RFLAG) /* Read entire file. */ + { + result = read_history (filename); + history_lines_in_file = history_lines_read_from_file; + /* history_lines_in_file = where_history () + history_base - 1; */ + } + else if (flags & NFLAG) /* Read `new' history from file. */ + { + /* Read all of the lines in the file that we haven't already read. */ + old_history_lines = history_lines_in_file; + obase = history_base; + + using_history (); + result = read_history_range (filename, history_lines_in_file, -1); + using_history (); + + history_lines_in_file = history_lines_read_from_file; + /* history_lines_in_file = where_history () + history_base - 1; */ + + /* If we're rewriting the history file at shell exit rather than just + appending the lines from this session to it, the question is whether + we reset history_lines_this_session to 0, losing any history entries + we had before we read the new entries from the history file, or + whether we count the new entries we just read from the file as + history lines added during this session. + Right now, we do the latter. This will cause these history entries + to be written to the history file along with any intermediate entries + we add when we do a `history -a', but the alternative is losing + them altogether. */ + if (force_append_history == 0) + history_lines_this_session += history_lines_in_file - old_history_lines + + history_base - obase; + } + + return (result ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} + +/* Accessors for HIST_ENTRY lists that are called HLIST. */ +#define histline(i) (hlist[(i)]->line) +#define histdata(i) (hlist[(i)]->data) + +static char * +histtime (hlist, histtimefmt) + HIST_ENTRY *hlist; + const char *histtimefmt; +{ + static char timestr[TIMELEN_MAX]; + time_t t; + struct tm *tm; + + t = history_get_time (hlist); + tm = t ? localtime (&t) : 0; + if (t && tm) + strftime (timestr, sizeof (timestr), histtimefmt, tm); + else if (hlist->timestamp && hlist->timestamp[0]) + snprintf (timestr, sizeof (timestr), _("%s: invalid timestamp"), + (hlist->timestamp[0] == '#') ? hlist->timestamp + 1: hlist->timestamp); + else + strcpy (timestr, "??"); + return timestr; +} + +static int +display_history (list) + WORD_LIST *list; +{ + register int i; + intmax_t limit; + HIST_ENTRY **hlist; + char *histtimefmt, *timestr; + + if (list) + { + if (get_numeric_arg (list, 0, &limit) == 0) + return (EXECUTION_FAILURE); + + if (limit < 0) + limit = -limit; + } + else + limit = -1; + + hlist = history_list (); + + if (hlist) + { + for (i = 0; hlist[i]; i++) + ; + + if (0 <= limit && limit < i) + i -= limit; + else + i = 0; + + histtimefmt = get_string_value ("HISTTIMEFORMAT"); + + while (hlist[i]) + { + QUIT; + + timestr = (histtimefmt && *histtimefmt) ? histtime (hlist[i], histtimefmt) : (char *)NULL; + printf ("%5d%c %s%s\n", i + history_base, + histdata(i) ? '*' : ' ', + ((timestr && *timestr) ? timestr : ""), + histline(i)); + i++; + } + } + + return (EXECUTION_SUCCESS); +} + +/* Remove the last entry in the history list and add each argument in + LIST to the history. */ +static void +push_history (list) + WORD_LIST *list; +{ + char *s; + + /* Delete the last history entry if it was a single entry added to the + history list (generally the `history -s' itself), or if `history -s' + is being used in a compound command and the compound command was + added to the history as a single element (command-oriented history). + If you don't want history -s to remove the compound command from the + history, change #if 0 to #if 1 below. */ +#if 0 + if (remember_on_history && hist_last_line_pushed == 0 && + hist_last_line_added && bash_delete_last_history () == 0) +#else + if (remember_on_history && hist_last_line_pushed == 0 && + (hist_last_line_added || + (current_command_line_count > 0 && current_command_first_line_saved && command_oriented_history)) + && bash_delete_last_history () == 0) +#endif + return; + + s = string_list (list); + /* Call check_add_history with FORCE set to 1 to skip the check against + current_command_line_count. If history -s is used in a compound + command, the above code will delete the compound command's history + entry and this call will add the line to the history as a separate + entry. Without FORCE=1, if current_command_line_count were > 1, the + line would be appended to the entry before the just-deleted entry. */ + check_add_history (s, 1); /* obeys HISTCONTROL, HISTIGNORE */ + + hist_last_line_pushed = 1; /* XXX */ + free (s); +} + +#if defined (BANG_HISTORY) +static int +expand_and_print_history (list) + WORD_LIST *list; +{ + char *s; + int r, result; + + if (hist_last_line_pushed == 0 && hist_last_line_added && bash_delete_last_history () == 0) + return EXECUTION_FAILURE; + result = EXECUTION_SUCCESS; + while (list) + { + r = history_expand (list->word->word, &s); + if (r < 0) + { + builtin_error (_("%s: history expansion failed"), list->word->word); + result = EXECUTION_FAILURE; + } + else + { + fputs (s, stdout); + putchar ('\n'); + } + FREE (s); + list = list->next; + } + fflush (stdout); + return result; +} +#endif /* BANG_HISTORY */ +#endif /* HISTORY */ diff --git a/third_party/bash/builtins_jobs.c b/third_party/bash/builtins_jobs.c new file mode 100644 index 000000000..e8cd2eec0 --- /dev/null +++ b/third_party/bash/builtins_jobs.c @@ -0,0 +1,240 @@ +/* jobs.c, created from jobs.def. */ +#line 22 "./jobs.def" + +#line 48 "./jobs.def" + +#include "config.h" + +#if defined (JOB_CONTROL) +#include "bashtypes.h" +#include +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "jobs.h" +#include "execute_cmd.h" +#include "bashgetopt.h" +#include "common.h" + +#define JSTATE_ANY 0x0 +#define JSTATE_RUNNING 0x1 +#define JSTATE_STOPPED 0x2 + +static int execute_list_with_replacements PARAMS((WORD_LIST *)); + +/* The `jobs' command. Prints outs a list of active jobs. If the + argument `-l' is given, then the process id's are printed also. + If the argument `-p' is given, print the process group leader's + pid only. If `-n' is given, only processes that have changed + status since the last notification are printed. If -x is given, + replace all job specs with the pid of the appropriate process + group leader and execute the command. The -r and -s options mean + to print info about running and stopped jobs only, respectively. */ +int +jobs_builtin (list) + WORD_LIST *list; +{ + int form, execute, state, opt, any_failed, job; + sigset_t set, oset; + + execute = any_failed = 0; + form = JLIST_STANDARD; + state = JSTATE_ANY; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "lpnxrs")) != -1) + { + switch (opt) + { + case 'l': + form = JLIST_LONG; + break; + case 'p': + form = JLIST_PID_ONLY; + break; + case 'n': + form = JLIST_CHANGED_ONLY; + break; + case 'x': + if (form != JLIST_STANDARD) + { + builtin_error (_("no other options allowed with `-x'")); + return (EXECUTION_FAILURE); + } + execute++; + break; + case 'r': + state = JSTATE_RUNNING; + break; + case 's': + state = JSTATE_STOPPED; + break; + + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + if (execute) + return (execute_list_with_replacements (list)); + + if (!list) + { + switch (state) + { + case JSTATE_ANY: + list_all_jobs (form); + break; + case JSTATE_RUNNING: + list_running_jobs (form); + break; + case JSTATE_STOPPED: + list_stopped_jobs (form); + break; + } + return (EXECUTION_SUCCESS); + } + + while (list) + { + BLOCK_CHILD (set, oset); + job = get_job_spec (list); + + if ((job == NO_JOB) || jobs == 0 || get_job_by_jid (job) == 0) + { + sh_badjob (list->word->word); + any_failed++; + } + else if (job != DUP_JOB) + list_one_job ((JOB *)NULL, form, 0, job); + + UNBLOCK_CHILD (oset); + list = list->next; + } + return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} + +static int +execute_list_with_replacements (list) + WORD_LIST *list; +{ + register WORD_LIST *l; + int job, result; + COMMAND *command; + JOB *j; + + /* First do the replacement of job specifications with pids. */ + for (l = list; l; l = l->next) + { + if (l->word->word[0] == '%') /* we have a winner */ + { + job = get_job_spec (l); + + /* A bad job spec is not really a job spec! Pass it through. */ + if (INVALID_JOB (job)) + continue; + + j = get_job_by_jid (job); + free (l->word->word); + l->word->word = itos (j->pgrp); + } + } + + /* Next make a new simple command and execute it. */ + begin_unwind_frame ("jobs_builtin"); + + command = make_bare_simple_command (); + command->value.Simple->words = copy_word_list (list); + command->value.Simple->redirects = (REDIRECT *)NULL; + command->flags |= CMD_INHIBIT_EXPANSION; + command->value.Simple->flags |= CMD_INHIBIT_EXPANSION; + + add_unwind_protect (dispose_command, command); + result = execute_command (command); + dispose_command (command); + + discard_unwind_frame ("jobs_builtin"); + return (result); +} +#endif /* JOB_CONTROL */ + +#line 231 "./jobs.def" + +#if defined (JOB_CONTROL) +int +disown_builtin (list) + WORD_LIST *list; +{ + int opt, job, retval, nohup_only, running_jobs, all_jobs; + sigset_t set, oset; + intmax_t pid_value; + + nohup_only = running_jobs = all_jobs = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "ahr")) != -1) + { + switch (opt) + { + case 'a': + all_jobs = 1; + break; + case 'h': + nohup_only = 1; + break; + case 'r': + running_jobs = 1; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + retval = EXECUTION_SUCCESS; + + /* `disown -a' or `disown -r' */ + if (list == 0 && (all_jobs || running_jobs)) + { + if (nohup_only) + nohup_all_jobs (running_jobs); + else + delete_all_jobs (running_jobs); + return (EXECUTION_SUCCESS); + } + + do + { + BLOCK_CHILD (set, oset); + job = (list && legal_number (list->word->word, &pid_value) && pid_value == (pid_t) pid_value) + ? get_job_by_pid ((pid_t) pid_value, 0, 0) + : get_job_spec (list); + + if (job == NO_JOB || jobs == 0 || INVALID_JOB (job)) + { + sh_badjob (list ? list->word->word : _("current")); + retval = EXECUTION_FAILURE; + } + else if (nohup_only) + nohup_job (job); + else + delete_job (job, 1); + UNBLOCK_CHILD (oset); + + if (list) + list = list->next; + } + while (list); + + return (retval); +} +#endif /* JOB_CONTROL */ diff --git a/third_party/bash/builtins_kill.c b/third_party/bash/builtins_kill.c new file mode 100644 index 000000000..8a1e73de1 --- /dev/null +++ b/third_party/bash/builtins_kill.c @@ -0,0 +1,235 @@ +/* kill.c, created from kill.def. */ +#line 22 "./kill.def" + +#line 46 "./kill.def" + +#include "config.h" + +#include +#include +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include + +#include "shell.h" +#include "trap.h" +#include "jobs.h" +#include "common.h" + +/* Not all systems declare ERRNO in errno.h... and some systems #define it! */ +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +static void kill_error PARAMS((pid_t, int)); + +#if !defined (CONTINUE_AFTER_KILL_ERROR) +# define CONTINUE_OR_FAIL return (EXECUTION_FAILURE) +#else +# define CONTINUE_OR_FAIL goto continue_killing +#endif /* CONTINUE_AFTER_KILL_ERROR */ + +/* Here is the kill builtin. We only have it so that people can type + kill -KILL %1? No, if you fill up the process table this way you + can still kill some. */ +int +kill_builtin (list) + WORD_LIST *list; +{ + int sig, any_succeeded, listing, saw_signal, dflags; + char *sigspec, *word; + pid_t pid; + intmax_t pid_value; + + if (list == 0) + { + builtin_usage (); + return (EX_USAGE); + } + CHECK_HELPOPT (list); + + any_succeeded = listing = saw_signal = 0; + sig = SIGTERM; + sigspec = "TERM"; + + dflags = DSIG_NOCASE | ((posixly_correct == 0) ? DSIG_SIGPREFIX : 0); + /* Process options. */ + while (list) + { + word = list->word->word; + + if (ISOPTION (word, 'l') || ISOPTION (word, 'L')) + { + listing++; + list = list->next; + } + else if (ISOPTION (word, 's') || ISOPTION (word, 'n')) + { + list = list->next; + if (list) + { + sigspec = list->word->word; +use_sigspec: + if (sigspec[0] == '0' && sigspec[1] == '\0') + sig = 0; + else + sig = decode_signal (sigspec, dflags); + list = list->next; + saw_signal++; + } + else + { + sh_needarg (word); + return (EXECUTION_FAILURE); + } + } + else if (word[0] == '-' && word[1] == 's' && ISALPHA (word[2])) + { + sigspec = word + 2; + goto use_sigspec; + } + else if (word[0] == '-' && word[1] == 'n' && ISDIGIT (word[2])) + { + sigspec = word + 2; + goto use_sigspec; + } + else if (ISOPTION (word, '-')) + { + list = list->next; + break; + } + else if (ISOPTION (word, '?')) + { + builtin_usage (); + return (EX_USAGE); + } + /* If this is a signal specification then process it. We only process + the first one seen; other arguments may signify process groups (e.g, + -num == process group num). */ + else if (*word == '-' && saw_signal == 0) + { + sigspec = word + 1; + sig = decode_signal (sigspec, dflags); + saw_signal++; + list = list->next; + } + else + break; + } + + if (listing) + return (display_signal_list (list, 0)); + + /* OK, we are killing processes. */ + if (sig == NO_SIG) + { + sh_invalidsig (sigspec); + return (EXECUTION_FAILURE); + } + + if (list == 0) + { + builtin_usage (); + return (EX_USAGE); + } + + while (list) + { + word = list->word->word; + + if (*word == '-') + word++; + + /* Use the entire argument in case of minus sign presence. */ + if (*word && legal_number (list->word->word, &pid_value) && (pid_value == (pid_t)pid_value)) + { + pid = (pid_t) pid_value; + + if (kill_pid (pid, sig, pid < -1) < 0) + { + if (errno == EINVAL) + sh_invalidsig (sigspec); + else + kill_error (pid, errno); + CONTINUE_OR_FAIL; + } + else + any_succeeded++; + } +#if defined (JOB_CONTROL) + else if (*list->word->word && *list->word->word != '%') + { + builtin_error (_("%s: arguments must be process or job IDs"), list->word->word); + CONTINUE_OR_FAIL; + } + else if (*word) + /* Posix.2 says you can kill without job control active (4.32.4) */ + { /* Must be a job spec. Check it out. */ + int job; + sigset_t set, oset; + JOB *j; + + BLOCK_CHILD (set, oset); + job = get_job_spec (list); + + if (INVALID_JOB (job)) + { + if (job != DUP_JOB) + sh_badjob (list->word->word); + UNBLOCK_CHILD (oset); + CONTINUE_OR_FAIL; + } + + j = get_job_by_jid (job); + /* Job spec used. Kill the process group. If the job was started + without job control, then its pgrp == shell_pgrp, so we have + to be careful. We take the pid of the first job in the pipeline + in that case. */ + pid = IS_JOBCONTROL (job) ? j->pgrp : j->pipe->pid; + + UNBLOCK_CHILD (oset); + + if (kill_pid (pid, sig, 1) < 0) + { + if (errno == EINVAL) + sh_invalidsig (sigspec); + else + kill_error (pid, errno); + CONTINUE_OR_FAIL; + } + else + any_succeeded++; + } +#endif /* !JOB_CONTROL */ + else + { + sh_badpid (list->word->word); + CONTINUE_OR_FAIL; + } + continue_killing: + list = list->next; + } + + return (any_succeeded ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +} + +static void +kill_error (pid, e) + pid_t pid; + int e; +{ + char *x; + + x = strerror (e); + if (x == 0) + x = _("Unknown error"); + builtin_error ("(%ld) - %s", (long)pid, x); +} diff --git a/third_party/bash/builtins_let.c b/third_party/bash/builtins_let.c new file mode 100644 index 000000000..ae2971270 --- /dev/null +++ b/third_party/bash/builtins_let.c @@ -0,0 +1,68 @@ +/* let.c, created from let.def. */ +#line 66 "./let.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "common.h" + +/* Arithmetic LET function. */ +int +let_builtin (list) + WORD_LIST *list; +{ + intmax_t ret; + int expok; + + CHECK_HELPOPT (list); + + /* Skip over leading `--' argument. */ + if (list && list->word && ISOPTION (list->word->word, '-')) + list = list->next; + + if (list == 0) + { + builtin_error (_("expression expected")); + return (EXECUTION_FAILURE); + } + + for (; list; list = list->next) + { + ret = evalexp (list->word->word, EXP_EXPANDED, &expok); + if (expok == 0) + return (EXECUTION_FAILURE); + } + + return ((ret == 0) ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} + +#ifdef INCLUDE_UNUSED +int +exp_builtin (list) + WORD_LIST *list; +{ + char *exp; + intmax_t ret; + int expok; + + if (list == 0) + { + builtin_error (_("expression expected")); + return (EXECUTION_FAILURE); + } + + exp = string_list (list); + ret = evalexp (exp, EXP_EXPANDED, &expok); + (void)free (exp); + return (((ret == 0) || (expok == 0)) ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} +#endif diff --git a/third_party/bash/builtins_mapfile.c b/third_party/bash/builtins_mapfile.c new file mode 100644 index 000000000..2ceafffea --- /dev/null +++ b/third_party/bash/builtins_mapfile.c @@ -0,0 +1,304 @@ +/* mapfile.c, created from mapfile.def. */ +#line 23 "./mapfile.def" + +#line 59 "./mapfile.def" + +#line 67 "./mapfile.def" + +#include "config.h" + +#include "builtins.h" +#include "bashtypes.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include +#include + +#include "bashintl.h" +#include "shell.h" +#include "common.h" +#include "bashgetopt.h" + +#if !defined (errno) +extern int errno; +#endif + +#if defined (ARRAY_VARS) + +static int run_callback PARAMS((const char *, unsigned int, const char *)); + +#define DEFAULT_ARRAY_NAME "MAPFILE" +#define DEFAULT_VARIABLE_NAME "MAPLINE" /* not used right now */ + +/* The value specifying how frequently `mapfile' calls the callback. */ +#define DEFAULT_QUANTUM 5000 + +/* Values for FLAGS */ +#define MAPF_CLEARARRAY 0x01 +#define MAPF_CHOP 0x02 + +static int delim; + +static int +run_callback (callback, curindex, curline) + const char *callback; + unsigned int curindex; + const char *curline; +{ + unsigned int execlen; + char *execstr, *qline; + int flags; + + qline = sh_single_quote (curline); + execlen = strlen (callback) + strlen (qline) + 10; + /* 1 for each space between %s and %d, + another 1 for the last nul char for C string. */ + execlen += 3; + execstr = xmalloc (execlen); + + flags = SEVAL_NOHIST; +#if 0 + if (interactive) + flags |= SEVAL_INTERACT; +#endif + snprintf (execstr, execlen, "%s %d %s", callback, curindex, qline); + free (qline); + return evalstring (execstr, NULL, flags); +} + +static void +do_chop(line, delim) + char *line; + unsigned char delim; +{ + int length; + + length = strlen (line); + if (length && (unsigned char)line[length-1] == delim) + line[length-1] = '\0'; +} + +static int +mapfile (fd, line_count_goal, origin, nskip, callback_quantum, callback, array_name, delim, flags) + int fd; + long line_count_goal, origin, nskip, callback_quantum; + char *callback, *array_name; + int delim; + int flags; +{ + char *line; + size_t line_length; + unsigned int array_index, line_count; + SHELL_VAR *entry; + struct stat sb; + int unbuffered_read; + + line = NULL; + line_length = 0; + unbuffered_read = 0; + + /* The following check should be done before reading any lines. Doing it + here allows us to call bind_array_element instead of bind_array_variable + and skip the variable lookup on every call. */ + entry = builtin_find_indexed_array (array_name, flags & MAPF_CLEARARRAY); + if (entry == 0) + return EXECUTION_FAILURE; + +#ifndef __CYGWIN__ + /* If the delimiter is a newline, turn on unbuffered reads for pipes + (terminals are ok). If the delimiter is not a newline, unbuffered reads + for every file descriptor that's not a regular file. */ + if (delim == '\n') + unbuffered_read = (lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE); + else + unbuffered_read = (fstat (fd, &sb) != 0) || (S_ISREG (sb.st_mode) == 0); +#else + unbuffered_read = 1; +#endif + + zreset (); + + /* Skip any lines at beginning of file? */ + for (line_count = 0; line_count < nskip; line_count++) + if (zgetline (fd, &line, &line_length, delim, unbuffered_read) < 0) + break; + + line = 0; + line_length = 0; + + /* Reset the buffer for bash own stream */ + for (array_index = origin, line_count = 1; + zgetline (fd, &line, &line_length, delim, unbuffered_read) != -1; + array_index++) + { + /* Remove trailing newlines? */ + if (flags & MAPF_CHOP) + do_chop (line, delim); + + /* Has a callback been registered and if so is it time to call it? */ + if (callback && line_count && (line_count % callback_quantum) == 0) + { + /* Reset the buffer for bash own stream. */ + if (unbuffered_read == 0) + zsyncfd (fd); + + run_callback (callback, array_index, line); + } + + /* XXX - bad things can happen if the callback modifies ENTRY, e.g., + unsetting it or changing it to a non-indexed-array type. */ + bind_array_element (entry, array_index, line, 0); + + /* Have we exceeded # of lines to store? */ + line_count++; + if (line_count_goal != 0 && line_count > line_count_goal) + break; + } + + free (line); + + if (unbuffered_read == 0) + zsyncfd (fd); + + return EXECUTION_SUCCESS; +} + +int +mapfile_builtin (list) + WORD_LIST *list; +{ + int opt, code, fd, flags; + intmax_t intval; + long lines, origin, nskip, callback_quantum; + char *array_name, *callback; + + fd = 0; + lines = origin = nskip = 0; + flags = MAPF_CLEARARRAY; + callback_quantum = DEFAULT_QUANTUM; + callback = 0; + delim = '\n'; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "d:u:n:O:tC:c:s:")) != -1) + { + switch (opt) + { + case 'd': + delim = *list_optarg; + break; + case 'u': + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (int)intval) + { + builtin_error (_("%s: invalid file descriptor specification"), list_optarg); + return (EXECUTION_FAILURE); + } + else + fd = intval; + + if (sh_validfd (fd) == 0) + { + builtin_error (_("%d: invalid file descriptor: %s"), fd, strerror (errno)); + return (EXECUTION_FAILURE); + } + break; + + case 'n': + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (unsigned)intval) + { + builtin_error (_("%s: invalid line count"), list_optarg); + return (EXECUTION_FAILURE); + } + else + lines = intval; + break; + + case 'O': + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (unsigned)intval) + { + builtin_error (_("%s: invalid array origin"), list_optarg); + return (EXECUTION_FAILURE); + } + else + origin = intval; + flags &= ~MAPF_CLEARARRAY; + break; + case 't': + flags |= MAPF_CHOP; + break; + case 'C': + callback = list_optarg; + break; + case 'c': + code = legal_number (list_optarg, &intval); + if (code == 0 || intval <= 0 || intval != (unsigned)intval) + { + builtin_error (_("%s: invalid callback quantum"), list_optarg); + return (EXECUTION_FAILURE); + } + else + callback_quantum = intval; + break; + case 's': + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (unsigned)intval) + { + builtin_error (_("%s: invalid line count"), list_optarg); + return (EXECUTION_FAILURE); + } + else + nskip = intval; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if (list == 0) + array_name = DEFAULT_ARRAY_NAME; + else if (list->word == 0 || list->word->word == 0) + { + builtin_error ("internal error: getting variable name"); + return (EXECUTION_FAILURE); + } + else if (list->word->word[0] == '\0') + { + builtin_error (_("empty array variable name")); + return (EX_USAGE); + } + else + array_name = list->word->word; + + if (legal_identifier (array_name) == 0) + { + sh_invalidid (array_name); + return (EXECUTION_FAILURE); + } + + return mapfile (fd, lines, origin, nskip, callback_quantum, callback, array_name, delim, flags); +} + +#else + +int +mapfile_builtin (list) + WORD_LIST *list; +{ + builtin_error (_("array variable support required")); + return (EXECUTION_FAILURE); +} + +#endif /* ARRAY_VARS */ diff --git a/third_party/bash/builtins_printf.c b/third_party/bash/builtins_printf.c new file mode 100644 index 000000000..2ec4e1ba8 --- /dev/null +++ b/third_party/bash/builtins_printf.c @@ -0,0 +1,1303 @@ +/* printf.c, created from printf.def. */ +#line 22 "./printf.def" + +#line 57 "./printf.def" + +#include "config.h" + +#include "bashtypes.h" + +#include +#if defined (HAVE_LIMITS_H) +# include +#else + /* Assume 32-bit ints. */ +# define INT_MAX 2147483647 +# define INT_MIN (-2147483647-1) +#endif + +#if defined (PREFER_STDARG) +# include +#else +# include +#endif + +#include +#include "chartypes.h" + +#ifdef HAVE_INTTYPES_H +# include +#endif + +#include "posixtime.h" +#include "bashansi.h" +#include "bashintl.h" + +#define NEED_STRFTIME_DECL + +#include "shell.h" +#include "shmbutil.h" +#include "stdc.h" +#include "bashgetopt.h" +#include "common.h" + +#if defined (PRI_MACROS_BROKEN) +# undef PRIdMAX +#endif + +#if !defined (PRIdMAX) +# if HAVE_LONG_LONG +# define PRIdMAX "lld" +# else +# define PRIdMAX "ld" +# endif +#endif + +#if !defined (errno) +extern int errno; +#endif + +#define PC(c) \ + do { \ + char b[2]; \ + tw++; \ + b[0] = c; b[1] = '\0'; \ + if (vflag) \ + vbadd (b, 1); \ + else \ + putchar (c); \ + QUIT; \ + } while (0) + +#define PF(f, func) \ + do { \ + int nw; \ + clearerr (stdout); \ + if (have_fieldwidth && have_precision) \ + nw = vflag ? vbprintf (f, fieldwidth, precision, func) : printf (f, fieldwidth, precision, func); \ + else if (have_fieldwidth) \ + nw = vflag ? vbprintf (f, fieldwidth, func) : printf (f, fieldwidth, func); \ + else if (have_precision) \ + nw = vflag ? vbprintf (f, precision, func) : printf (f, precision, func); \ + else \ + nw = vflag ? vbprintf (f, func) : printf (f, func); \ + tw += nw; \ + QUIT; \ + if (ferror (stdout)) \ + { \ + sh_wrerror (); \ + clearerr (stdout); \ + return (EXECUTION_FAILURE); \ + } \ + } while (0) + +/* We free the buffer used by mklong() if it's `too big'. */ +#define PRETURN(value) \ + do \ + { \ + QUIT; \ + if (vflag) \ + { \ + SHELL_VAR *v; \ + v = builtin_bind_variable (vname, vbuf, bindflags); \ + stupidly_hack_special_variables (vname); \ + if (v == 0 || readonly_p (v) || noassign_p (v)) \ + return (EXECUTION_FAILURE); \ + } \ + if (conv_bufsize > 4096 ) \ + { \ + free (conv_buf); \ + conv_bufsize = 0; \ + conv_buf = 0; \ + } \ + if (vbsize > 4096) \ + { \ + free (vbuf); \ + vbsize = 0; \ + vbuf = 0; \ + } \ + else if (vbuf) \ + vbuf[0] = 0; \ + if (ferror (stdout) == 0) \ + fflush (stdout); \ + QUIT; \ + if (ferror (stdout)) \ + { \ + sh_wrerror (); \ + clearerr (stdout); \ + return (EXECUTION_FAILURE); \ + } \ + return (value); \ + } \ + while (0) + +#define SKIP1 "#'-+ 0" +#define LENMODS "hjlLtz" + +#ifndef TIMELEN_MAX +# define TIMELEN_MAX 128 +#endif + +extern time_t shell_start_time; + +#if !HAVE_ASPRINTF +extern int asprintf PARAMS((char **, const char *, ...)) __attribute__((__format__ (printf, 2, 3))); +#endif + +#if !HAVE_VSNPRINTF +extern int vsnprintf PARAMS((char *, size_t, const char *, va_list)) __attribute__((__format__ (printf, 3, 0))); +#endif + +static void printf_erange PARAMS((char *)); +static int printstr PARAMS((char *, char *, int, int, int)); +static int tescape PARAMS((char *, char *, int *, int *)); +static char *bexpand PARAMS((char *, int, int *, int *)); +static char *vbadd PARAMS((char *, int)); +static int vbprintf PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); +static char *mklong PARAMS((char *, char *, size_t)); +static int getchr PARAMS((void)); +static char *getstr PARAMS((void)); +static int getint PARAMS((void)); +static intmax_t getintmax PARAMS((void)); +static uintmax_t getuintmax PARAMS((void)); + +#if defined (HAVE_LONG_DOUBLE) && HAVE_DECL_STRTOLD && !defined(STRTOLD_BROKEN) +typedef long double floatmax_t; +# define USE_LONG_DOUBLE 1 +# define FLOATMAX_CONV "L" +# define strtofltmax strtold +#else +typedef double floatmax_t; +# define USE_LONG_DOUBLE 0 +# define FLOATMAX_CONV "" +# define strtofltmax strtod +#endif +static double getdouble PARAMS((void)); +static floatmax_t getfloatmax PARAMS((void)); + +static intmax_t asciicode PARAMS((void)); + +static WORD_LIST *garglist, *orig_arglist; +static int retval; +static int conversion_error; + +/* printf -v var support */ +static int vflag = 0; +static int bindflags = 0; +static char *vbuf, *vname; +static size_t vbsize; +static int vblen; + +static intmax_t tw; + +static char *conv_buf; +static size_t conv_bufsize; + +int +printf_builtin (list) + WORD_LIST *list; +{ + int ch, fieldwidth, precision; + int have_fieldwidth, have_precision, use_Lmod, altform; + char convch, thisch, nextch, *format, *modstart, *precstart, *fmt, *start; +#if defined (HANDLE_MULTIBYTE) + char mbch[25]; /* 25 > MB_LEN_MAX, plus can handle 4-byte UTF-8 and large Unicode characters*/ + int mbind, mblen; +#endif +#if defined (ARRAY_VARS) + int arrayflags; +#endif + + conversion_error = 0; + vflag = 0; + + reset_internal_getopt (); + while ((ch = internal_getopt (list, "v:")) != -1) + { + switch (ch) + { + case 'v': + vname = list_optarg; + bindflags = 0; +#if defined (ARRAY_VARS) + SET_VFLAGS (list_optflags, arrayflags, bindflags); + retval = legal_identifier (vname) || valid_array_reference (vname, arrayflags); +#else + retval = legal_identifier (vname); +#endif + if (retval) + { + vflag = 1; + if (vbsize == 0) + vbuf = xmalloc (vbsize = 16); + vblen = 0; + if (vbuf) + vbuf[0] = 0; + } + else + { + sh_invalidid (vname); + return (EX_USAGE); + } + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; /* skip over possible `--' */ + + if (list == 0) + { + builtin_usage (); + return (EX_USAGE); + } + + /* Allow printf -v var "" to act like var="" */ + if (vflag && list->word->word && list->word->word[0] == '\0') + { + SHELL_VAR *v; + v = builtin_bind_variable (vname, "", 0); + stupidly_hack_special_variables (vname); + return ((v == 0 || readonly_p (v) || noassign_p (v)) ? EXECUTION_FAILURE : EXECUTION_SUCCESS); + } + + if (list->word->word == 0 || list->word->word[0] == '\0') + return (EXECUTION_SUCCESS); + + format = list->word->word; + tw = 0; + retval = EXECUTION_SUCCESS; + + garglist = orig_arglist = list->next; + + /* If the format string is empty after preprocessing, return immediately. */ + if (format == 0 || *format == 0) + return (EXECUTION_SUCCESS); + + /* Basic algorithm is to scan the format string for conversion + specifications -- once one is found, find out if the field + width or precision is a '*'; if it is, gather up value. Note, + format strings are reused as necessary to use up the provided + arguments, arguments of zero/null string are provided to use + up the format string. */ + do + { + tw = 0; + /* find next format specification */ + for (fmt = format; *fmt; fmt++) + { + precision = fieldwidth = 0; + have_fieldwidth = have_precision = altform = 0; + precstart = 0; + + if (*fmt == '\\') + { + fmt++; + /* A NULL third argument to tescape means to bypass the + special processing for arguments to %b. */ +#if defined (HANDLE_MULTIBYTE) + /* Accommodate possible use of \u or \U, which can result in + multibyte characters */ + memset (mbch, '\0', sizeof (mbch)); + fmt += tescape (fmt, mbch, &mblen, (int *)NULL); + for (mbind = 0; mbind < mblen; mbind++) + PC (mbch[mbind]); +#else + fmt += tescape (fmt, &nextch, (int *)NULL, (int *)NULL); + PC (nextch); +#endif + fmt--; /* for loop will increment it for us again */ + continue; + } + + if (*fmt != '%') + { + PC (*fmt); + continue; + } + + /* ASSERT(*fmt == '%') */ + start = fmt++; + + if (*fmt == '%') /* %% prints a % */ + { + PC ('%'); + continue; + } + + /* Found format specification, skip to field width. We check for + alternate form for possible later use. */ + for (; *fmt && strchr(SKIP1, *fmt); ++fmt) + if (*fmt == '#') + altform++; + + /* Skip optional field width. */ + if (*fmt == '*') + { + fmt++; + have_fieldwidth = 1; + fieldwidth = getint (); + } + else + while (DIGIT (*fmt)) + fmt++; + + /* Skip optional '.' and precision */ + if (*fmt == '.') + { + ++fmt; + if (*fmt == '*') + { + fmt++; + have_precision = 1; + precision = getint (); + } + else + { + /* Negative precisions are allowed but treated as if the + precision were missing; I would like to allow a leading + `+' in the precision number as an extension, but lots + of asprintf/fprintf implementations get this wrong. */ +#if 0 + if (*fmt == '-' || *fmt == '+') +#else + if (*fmt == '-') +#endif + fmt++; + if (DIGIT (*fmt)) + precstart = fmt; + while (DIGIT (*fmt)) + fmt++; + } + } + + /* skip possible format modifiers */ + modstart = fmt; + use_Lmod = 0; + while (*fmt && strchr (LENMODS, *fmt)) + { + use_Lmod |= USE_LONG_DOUBLE && *fmt == 'L'; + fmt++; + } + + if (*fmt == 0) + { + builtin_error (_("`%s': missing format character"), start); + PRETURN (EXECUTION_FAILURE); + } + + convch = *fmt; + thisch = modstart[0]; + nextch = modstart[1]; + modstart[0] = convch; + modstart[1] = '\0'; + + QUIT; + switch(convch) + { + case 'c': + { + char p; + + p = getchr (); + PF(start, p); + break; + } + + case 's': + { + char *p; + + p = getstr (); + PF(start, p); + break; + } + + case '(': + { + char *timefmt, timebuf[TIMELEN_MAX], *t; + int n; + intmax_t arg; + time_t secs; + struct tm *tm; + + modstart[1] = nextch; /* restore char after left paren */ + timefmt = xmalloc (strlen (fmt) + 3); + fmt++; /* skip over left paren */ + for (t = timefmt, n = 1; *fmt; ) + { + if (*fmt == '(') + n++; + else if (*fmt == ')') + n--; + if (n == 0) + break; + *t++ = *fmt++; + } + *t = '\0'; + if (*++fmt != 'T') + { + builtin_warning (_("`%c': invalid time format specification"), *fmt); + fmt = start; + free (timefmt); + PC (*fmt); + continue; + } + if (timefmt[0] == '\0') + { + timefmt[0] = '%'; + timefmt[1] = 'X'; /* locale-specific current time - should we use `+'? */ + timefmt[2] = '\0'; + } + /* argument is seconds since the epoch with special -1 and -2 */ + /* default argument is equivalent to -1; special case */ + arg = garglist ? getintmax () : -1; + if (arg == -1) + secs = NOW; /* roughly date +%s */ + else if (arg == -2) + secs = shell_start_time; /* roughly $SECONDS */ + else + secs = arg; +#if defined (HAVE_TZSET) + sv_tz ("TZ"); /* XXX -- just make sure */ +#endif + tm = localtime (&secs); + if (tm == 0) + { + secs = 0; + tm = localtime (&secs); + } + n = tm ? strftime (timebuf, sizeof (timebuf), timefmt, tm) : 0; + free (timefmt); + if (n == 0) + timebuf[0] = '\0'; + else + timebuf[sizeof(timebuf) - 1] = '\0'; + /* convert to %s format that preserves fieldwidth and precision */ + modstart[0] = 's'; + modstart[1] = '\0'; + n = printstr (start, timebuf, strlen (timebuf), fieldwidth, precision); /* XXX - %s for now */ + if (n < 0) + { + if (ferror (stdout) == 0) + { + sh_wrerror (); + clearerr (stdout); + } + PRETURN (EXECUTION_FAILURE); + } + break; + } + + case 'n': + { + char *var; + + var = getstr (); + if (var && *var) + { + if (legal_identifier (var)) + bind_var_to_int (var, tw, 0); + else + { + sh_invalidid (var); + PRETURN (EXECUTION_FAILURE); + } + } + break; + } + + case 'b': /* expand escapes in argument */ + { + char *p, *xp; + int rlen, r; + + p = getstr (); + ch = rlen = r = 0; + xp = bexpand (p, strlen (p), &ch, &rlen); + + if (xp) + { + /* Have to use printstr because of possible NUL bytes + in XP -- printf does not handle that well. */ + r = printstr (start, xp, rlen, fieldwidth, precision); + if (r < 0) + { + if (ferror (stdout) == 0) + { + sh_wrerror (); + clearerr (stdout); + } + retval = EXECUTION_FAILURE; + } + free (xp); + } + + if (ch || r < 0) + PRETURN (retval); + break; + } + + case 'q': /* print with shell quoting */ + case 'Q': + { + char *p, *xp; + int r, mpr; + size_t slen; + + r = 0; + p = getstr (); + /* Decode precision and apply it to the unquoted string. */ + if (convch == 'Q' && precstart) + { + mpr = *precstart++ - '0'; + while (DIGIT (*precstart)) + mpr = (mpr * 10) + (*precstart++ - '0'); + /* Error if precision > INT_MAX here? */ + precision = (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr; + slen = strlen (p); + /* printf precision works in bytes. */ + if (precision < slen) + p[precision] = '\0'; + } + if (p && *p == 0) /* XXX - getstr never returns null */ + xp = savestring ("''"); + else if (ansic_shouldquote (p)) + xp = ansic_quote (p, 0, (int *)0); + else + xp = sh_backslash_quote (p, 0, 3); + if (xp) + { + if (convch == 'Q') + { + slen = strlen (xp); + if (slen > precision) + precision = slen; + } + /* Use printstr to get fieldwidth and precision right. */ + r = printstr (start, xp, strlen (xp), fieldwidth, precision); + if (r < 0) + { + sh_wrerror (); + clearerr (stdout); + } + free (xp); + } + + if (r < 0) + PRETURN (EXECUTION_FAILURE); + break; + } + + case 'd': + case 'i': + { + char *f; + long p; + intmax_t pp; + + p = pp = getintmax (); + if (p != pp) + { + f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2); + PF (f, pp); + } + else + { + /* Optimize the common case where the integer fits + in "long". This also works around some long + long and/or intmax_t library bugs in the common + case, e.g. glibc 2.2 x86. */ + f = mklong (start, "l", 1); + PF (f, p); + } + break; + } + + case 'o': + case 'u': + case 'x': + case 'X': + { + char *f; + unsigned long p; + uintmax_t pp; + + p = pp = getuintmax (); + if (p != pp) + { + f = mklong (start, PRIdMAX, sizeof (PRIdMAX) - 2); + PF (f, pp); + } + else + { + f = mklong (start, "l", 1); + PF (f, p); + } + break; + } + + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': +#if defined (HAVE_PRINTF_A_FORMAT) + case 'a': + case 'A': +#endif + { + char *f; + + if (use_Lmod || posixly_correct == 0) + { + floatmax_t p; + + p = getfloatmax (); + f = mklong (start, "L", 1); + PF (f, p); + } + else /* posixly_correct */ + { + double p; + + p = getdouble (); + f = mklong (start, "", 0); + PF (f, p); + } + + break; + } + + /* We don't output unrecognized format characters; we print an + error message and return a failure exit status. */ + default: + builtin_error (_("`%c': invalid format character"), convch); + PRETURN (EXECUTION_FAILURE); + } + + modstart[0] = thisch; + modstart[1] = nextch; + } + + if (ferror (stdout)) + { + /* PRETURN will print error message. */ + PRETURN (EXECUTION_FAILURE); + } + } + while (garglist && garglist != list->next); + + if (conversion_error) + retval = EXECUTION_FAILURE; + + PRETURN (retval); +} + +static void +printf_erange (s) + char *s; +{ + builtin_error (_("warning: %s: %s"), s, strerror(ERANGE)); +} + +/* We duplicate a lot of what printf(3) does here. */ +static int +printstr (fmt, string, len, fieldwidth, precision) + char *fmt; /* format */ + char *string; /* expanded string argument */ + int len; /* length of expanded string */ + int fieldwidth; /* argument for width of `*' */ + int precision; /* argument for precision of `*' */ +{ +#if 0 + char *s; +#endif + int padlen, nc, ljust, i; + int fw, pr; /* fieldwidth and precision */ + intmax_t mfw, mpr; + + if (string == 0) + string = ""; + +#if 0 + s = fmt; +#endif + if (*fmt == '%') + fmt++; + + ljust = fw = 0; + pr = -1; + mfw = 0; + mpr = -1; + + /* skip flags */ + while (strchr (SKIP1, *fmt)) + { + if (*fmt == '-') + ljust = 1; + fmt++; + } + + /* get fieldwidth, if present. rely on caller to clamp fieldwidth at INT_MAX */ + if (*fmt == '*') + { + fmt++; + fw = fieldwidth; + if (fw < 0) + { + fw = -fw; + ljust = 1; + } + } + else if (DIGIT (*fmt)) + { + mfw = *fmt++ - '0'; + while (DIGIT (*fmt)) + mfw = (mfw * 10) + (*fmt++ - '0'); + /* Error if fieldwidth > INT_MAX here? */ + fw = (mfw < 0 || mfw > INT_MAX) ? INT_MAX : mfw; + } + + /* get precision, if present. doesn't handle negative precisions */ + if (*fmt == '.') + { + fmt++; + if (*fmt == '*') + { + fmt++; + pr = precision; + } + else if (DIGIT (*fmt)) + { + mpr = *fmt++ - '0'; + while (DIGIT (*fmt)) + mpr = (mpr * 10) + (*fmt++ - '0'); + /* Error if precision > INT_MAX here? */ + pr = (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr; + if (pr < precision && precision < INT_MAX) + pr = precision; /* XXX */ + } + else + pr = 0; /* "a null digit string is treated as zero" */ + } + +#if 0 + /* If we remove this, get rid of `s'. */ + if (*fmt != 'b' && *fmt != 'q') + { + internal_error (_("format parsing problem: %s"), s); + fw = pr = 0; + } +#endif + + /* chars from string to print */ + nc = (pr >= 0 && pr <= len) ? pr : len; + + padlen = fw - nc; + if (padlen < 0) + padlen = 0; + if (ljust) + padlen = -padlen; + + /* leading pad characters */ + for (; padlen > 0; padlen--) + PC (' '); + + /* output NC characters from STRING */ + for (i = 0; i < nc; i++) + PC (string[i]); + + /* output any necessary trailing padding */ + for (; padlen < 0; padlen++) + PC (' '); + + return (ferror (stdout) ? -1 : 0); +} + +/* Convert STRING by expanding the escape sequences specified by the + POSIX standard for printf's `%b' format string. If SAWC is non-null, + perform the processing appropriate for %b arguments. In particular, + recognize `\c' and use that as a string terminator. If we see \c, set + *SAWC to 1 before returning. LEN is the length of STRING. */ + +/* Translate a single backslash-escape sequence starting at ESTART (the + character after the backslash) and return the number of characters + consumed by the sequence. CP is the place to return the translated + value. *SAWC is set to 1 if the escape sequence was \c, since that means + to short-circuit the rest of the processing. If SAWC is null, we don't + do the \c short-circuiting, and \c is treated as an unrecognized escape + sequence; we also bypass the other processing specific to %b arguments. */ +static int +tescape (estart, cp, lenp, sawc) + char *estart; + char *cp; + int *lenp, *sawc; +{ + register char *p; + int temp, c, evalue; + unsigned long uvalue; + + p = estart; + if (lenp) + *lenp = 1; + + switch (c = *p++) + { +#if defined (__STDC__) + case 'a': *cp = '\a'; break; +#else + case 'a': *cp = '\007'; break; +#endif + + case 'b': *cp = '\b'; break; + + case 'e': + case 'E': *cp = '\033'; break; /* ESC -- non-ANSI */ + + case 'f': *cp = '\f'; break; + + case 'n': *cp = '\n'; break; + + case 'r': *cp = '\r'; break; + + case 't': *cp = '\t'; break; + + case 'v': *cp = '\v'; break; + + /* The octal escape sequences are `\0' followed by up to three octal + digits (if SAWC), or `\' followed by up to three octal digits (if + !SAWC). As an extension, we allow the latter form even if SAWC. */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + evalue = OCTVALUE (c); + for (temp = 2 + (!evalue && !!sawc); ISOCTAL (*p) && temp--; p++) + evalue = (evalue * 8) + OCTVALUE (*p); + *cp = evalue & 0xFF; + break; + + /* And, as another extension, we allow \xNN, where each N is a + hex digit. */ + case 'x': + for (temp = 2, evalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++) + evalue = (evalue * 16) + HEXVALUE (*p); + if (p == estart + 1) + { + builtin_error (_("missing hex digit for \\x")); + *cp = '\\'; + return 0; + } + *cp = evalue & 0xFF; + break; + +#if defined (HANDLE_MULTIBYTE) + case 'u': + case 'U': + temp = (c == 'u') ? 4 : 8; /* \uNNNN \UNNNNNNNN */ + for (uvalue = 0; ISXDIGIT ((unsigned char)*p) && temp--; p++) + uvalue = (uvalue * 16) + HEXVALUE (*p); + if (p == estart + 1) + { + builtin_error (_("missing unicode digit for \\%c"), c); + *cp = '\\'; + return 0; + } + if (uvalue <= 0x7f) /* <= 0x7f translates directly */ + *cp = uvalue; + else + { + temp = u32cconv (uvalue, cp); + cp[temp] = '\0'; + if (lenp) + *lenp = temp; + } + break; +#endif + + case '\\': /* \\ -> \ */ + *cp = c; + break; + + /* SAWC == 0 means that \', \", and \? are recognized as escape + sequences, though the only processing performed is backslash + removal. */ + case '\'': case '"': case '?': + if (!sawc) + *cp = c; + else + { + *cp = '\\'; + return 0; + } + break; + + case 'c': + if (sawc) + { + *sawc = 1; + break; + } + /* other backslash escapes are passed through unaltered */ + default: + *cp = '\\'; + return 0; + } + return (p - estart); +} + +static char * +bexpand (string, len, sawc, lenp) + char *string; + int len, *sawc, *lenp; +{ + int temp; + char *ret, *r, *s, c; +#if defined (HANDLE_MULTIBYTE) + char mbch[25]; + int mbind, mblen; +#endif + + if (string == 0 || len == 0) + { + if (sawc) + *sawc = 0; + if (lenp) + *lenp = 0; + ret = (char *)xmalloc (1); + ret[0] = '\0'; + return (ret); + } + + ret = (char *)xmalloc (len + 1); + for (r = ret, s = string; s && *s; ) + { + c = *s++; + if (c != '\\' || *s == '\0') + { + *r++ = c; + continue; + } + temp = 0; +#if defined (HANDLE_MULTIBYTE) + memset (mbch, '\0', sizeof (mbch)); + s += tescape (s, mbch, &mblen, &temp); +#else + s += tescape (s, &c, (int *)NULL, &temp); +#endif + if (temp) + { + if (sawc) + *sawc = 1; + break; + } + +#if defined (HANDLE_MULTIBYTE) + for (mbind = 0; mbind < mblen; mbind++) + *r++ = mbch[mbind]; +#else + *r++ = c; +#endif + } + + *r = '\0'; + if (lenp) + *lenp = r - ret; + return ret; +} + +static char * +vbadd (buf, blen) + char *buf; + int blen; +{ + size_t nlen; + + nlen = vblen + blen + 1; + if (nlen >= vbsize) + { + vbsize = ((nlen + 63) >> 6) << 6; + vbuf = (char *)xrealloc (vbuf, vbsize); + } + + if (blen == 1) + vbuf[vblen++] = buf[0]; + else if (blen > 1) + { + FASTCOPY (buf, vbuf + vblen, blen); + vblen += blen; + } + vbuf[vblen] = '\0'; + +#ifdef DEBUG + if (strlen (vbuf) != vblen) + internal_error ("printf:vbadd: vblen (%d) != strlen (vbuf) (%d)", vblen, (int)strlen (vbuf)); +#endif + + return vbuf; +} + +static int +#if defined (PREFER_STDARG) +vbprintf (const char *format, ...) +#else +vbprintf (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + size_t nlen; + int blen; + + SH_VA_START (args, format); + blen = vsnprintf (vbuf + vblen, vbsize - vblen, format, args); + va_end (args); + + nlen = vblen + blen + 1; + if (nlen >= vbsize) + { + vbsize = ((nlen + 63) >> 6) << 6; + vbuf = (char *)xrealloc (vbuf, vbsize); + SH_VA_START (args, format); + blen = vsnprintf (vbuf + vblen, vbsize - vblen, format, args); + va_end (args); + } + + vblen += blen; + vbuf[vblen] = '\0'; + +#ifdef DEBUG + if (strlen (vbuf) != vblen) + internal_error ("printf:vbprintf: vblen (%d) != strlen (vbuf) (%d)", vblen, (int)strlen (vbuf)); +#endif + + return (blen); +} + +static char * +mklong (str, modifiers, mlen) + char *str; + char *modifiers; + size_t mlen; +{ + size_t len, slen; + + slen = strlen (str); + len = slen + mlen + 1; + + if (len > conv_bufsize) + { + conv_bufsize = (((len + 1023) >> 10) << 10); + conv_buf = (char *)xrealloc (conv_buf, conv_bufsize); + } + + FASTCOPY (str, conv_buf, slen - 1); + FASTCOPY (modifiers, conv_buf + slen - 1, mlen); + + conv_buf[len - 2] = str[slen - 1]; + conv_buf[len - 1] = '\0'; + return (conv_buf); +} + +static int +getchr () +{ + int ret; + + if (garglist == 0) + return ('\0'); + + ret = (int)garglist->word->word[0]; + garglist = garglist->next; + return ret; +} + +static char * +getstr () +{ + char *ret; + + if (garglist == 0) + return (""); + + ret = garglist->word->word; + garglist = garglist->next; + return ret; +} + +static int +getint () +{ + intmax_t ret; + + ret = getintmax (); + + if (garglist == 0) + return ret; + + if (ret > INT_MAX) + { + printf_erange (garglist->word->word); + ret = INT_MAX; + } + else if (ret < INT_MIN) + { + printf_erange (garglist->word->word); + ret = INT_MIN; + } + + return ((int)ret); +} + +static intmax_t +getintmax () +{ + intmax_t ret; + char *ep; + + if (garglist == 0) + return (0); + + if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') + return asciicode (); + + errno = 0; + ret = strtoimax (garglist->word->word, &ep, 0); + + if (*ep) + { + sh_invalidnum (garglist->word->word); + /* POSIX.2 says ``...a diagnostic message shall be written to standard + error, and the utility shall not exit with a zero exit status, but + shall continue processing any remaining operands and shall write the + value accumulated at the time the error was detected to standard + output.'' Yecch. */ +#if 0 + ret = 0; /* return partially-converted value from strtoimax */ +#endif + conversion_error = 1; + } + else if (errno == ERANGE) + printf_erange (garglist->word->word); + + garglist = garglist->next; + return (ret); +} + +static uintmax_t +getuintmax () +{ + uintmax_t ret; + char *ep; + + if (garglist == 0) + return (0); + + if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') + return asciicode (); + + errno = 0; + ret = strtoumax (garglist->word->word, &ep, 0); + + if (*ep) + { + sh_invalidnum (garglist->word->word); +#if 0 + /* Same POSIX.2 conversion error requirements as getintmax(). */ + ret = 0; +#endif + conversion_error = 1; + } + else if (errno == ERANGE) + printf_erange (garglist->word->word); + + garglist = garglist->next; + return (ret); +} + +static double +getdouble () +{ + double ret; + char *ep; + + if (garglist == 0) + return (0); + + if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') + return asciicode (); + + errno = 0; + ret = strtod (garglist->word->word, &ep); + + if (*ep) + { + sh_invalidnum (garglist->word->word); + conversion_error = 1; + } + else if (errno == ERANGE) + printf_erange (garglist->word->word); + + garglist = garglist->next; + return (ret); +} + +static floatmax_t +getfloatmax () +{ + floatmax_t ret; + char *ep; + + if (garglist == 0) + return (0); + + if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') + return asciicode (); + + errno = 0; + ret = strtofltmax (garglist->word->word, &ep); + + if (*ep) + { + sh_invalidnum (garglist->word->word); +#if 0 + /* Same thing about POSIX.2 conversion error requirements. */ + ret = 0; +#endif + conversion_error = 1; + } + else if (errno == ERANGE) + printf_erange (garglist->word->word); + + garglist = garglist->next; + return (ret); +} + +/* NO check is needed for garglist here. */ +static intmax_t +asciicode () +{ + register intmax_t ch; +#if defined (HANDLE_MULTIBYTE) + wchar_t wc; + size_t slen; + int mblength; +#endif + DECLARE_MBSTATE; + +#if defined (HANDLE_MULTIBYTE) + slen = strlen (garglist->word->word+1); + wc = 0; + mblength = mbtowc (&wc, garglist->word->word+1, slen); + if (mblength > 0) + ch = wc; /* XXX */ + else +#endif + ch = (unsigned char)garglist->word->word[1]; + + garglist = garglist->next; + return (ch); +} diff --git a/third_party/bash/builtins_pushd.c b/third_party/bash/builtins_pushd.c new file mode 100644 index 000000000..6c9c02352 --- /dev/null +++ b/third_party/bash/builtins_pushd.c @@ -0,0 +1,690 @@ +/* pushd.c, created from pushd.def. */ +#line 22 "./pushd.def" + +#line 55 "./pushd.def" + +#line 84 "./pushd.def" + +#line 115 "./pushd.def" + +#include "config.h" + +#if defined (PUSHD_AND_POPD) +#include +#if defined (HAVE_SYS_PARAM_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include + +#include "tilde.h" + +#include "shell.h" +#include "maxpath.h" +#include "common.h" +#include "builtext.h" + +#ifdef LOADABLE_BUILTIN +# include "builtins.h" +#endif + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* The list of remembered directories. */ +static char **pushd_directory_list = (char **)NULL; + +/* Number of existing slots in this list. */ +static int directory_list_size; + +/* Offset to the end of the list. */ +static int directory_list_offset; + +static void pushd_error PARAMS((int, char *)); +static void clear_directory_stack PARAMS((void)); +static int cd_to_string PARAMS((char *)); +static int change_to_temp PARAMS((char *)); +static void add_dirstack_element PARAMS((char *)); +static int get_dirstack_index PARAMS((intmax_t, int, int *)); + +#define NOCD 0x01 +#define ROTATE 0x02 +#define LONGFORM 0x04 +#define CLEARSTAK 0x08 + +int +pushd_builtin (list) + WORD_LIST *list; +{ + WORD_LIST *orig_list; + char *temp, *current_directory, *top; + int j, flags, skipopt; + intmax_t num; + char direction; + + orig_list = list; + + CHECK_HELPOPT (list); + if (list && list->word && ISOPTION (list->word->word, '-')) + { + list = list->next; + skipopt = 1; + } + else + skipopt = 0; + + /* If there is no argument list then switch current and + top of list. */ + if (list == 0) + { + if (directory_list_offset == 0) + { + builtin_error (_("no other directory")); + return (EXECUTION_FAILURE); + } + + current_directory = get_working_directory ("pushd"); + if (current_directory == 0) + return (EXECUTION_FAILURE); + + j = directory_list_offset - 1; + temp = pushd_directory_list[j]; + pushd_directory_list[j] = current_directory; + j = change_to_temp (temp); + free (temp); + return j; + } + + for (flags = 0; skipopt == 0 && list; list = list->next) + { + if (ISOPTION (list->word->word, 'n')) + { + flags |= NOCD; + } + else if (ISOPTION (list->word->word, '-')) + { + list = list->next; + break; + } + else if (list->word->word[0] == '-' && list->word->word[1] == '\0') + /* Let `pushd -' work like it used to. */ + break; + else if (((direction = list->word->word[0]) == '+') || direction == '-') + { + if (legal_number (list->word->word + 1, &num) == 0) + { + sh_invalidnum (list->word->word); + builtin_usage (); + return (EX_USAGE); + } + + if (direction == '-') + num = directory_list_offset - num; + + if (num > directory_list_offset || num < 0) + { + pushd_error (directory_list_offset, list->word->word); + return (EXECUTION_FAILURE); + } + flags |= ROTATE; + } + else if (*list->word->word == '-') + { + sh_invalidopt (list->word->word); + builtin_usage (); + return (EX_USAGE); + } + else + break; + } + + if (flags & ROTATE) + { + /* Rotate the stack num times. Remember, the current + directory acts like it is part of the stack. */ + temp = get_working_directory ("pushd"); + + if (num == 0) + { + j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS; + free (temp); + return j; + } + + do + { + top = pushd_directory_list[directory_list_offset - 1]; + + for (j = directory_list_offset - 2; j > -1; j--) + pushd_directory_list[j + 1] = pushd_directory_list[j]; + + pushd_directory_list[j + 1] = temp; + + temp = top; + num--; + } + while (num); + + j = ((flags & NOCD) == 0) ? change_to_temp (temp) : EXECUTION_SUCCESS; + free (temp); + return j; + } + + if (list == 0) + return (EXECUTION_SUCCESS); + + /* Change to the directory in list->word->word. Save the current + directory on the top of the stack. */ + current_directory = get_working_directory ("pushd"); + if (current_directory == 0) + return (EXECUTION_FAILURE); + + j = ((flags & NOCD) == 0) ? cd_builtin (skipopt ? orig_list : list) : EXECUTION_SUCCESS; + if (j == EXECUTION_SUCCESS) + { + add_dirstack_element ((flags & NOCD) ? savestring (list->word->word) : current_directory); + dirs_builtin ((WORD_LIST *)NULL); + if (flags & NOCD) + free (current_directory); + return (EXECUTION_SUCCESS); + } + else + { + free (current_directory); + return (EXECUTION_FAILURE); + } +} + +/* Pop the directory stack, and then change to the new top of the stack. + If LIST is non-null it should consist of a word +N or -N, which says + what element to delete from the stack. The default is the top one. */ +int +popd_builtin (list) + WORD_LIST *list; +{ + register int i; + intmax_t which; + int flags; + char direction; + char *which_word; + + CHECK_HELPOPT (list); + + which_word = (char *)NULL; + for (flags = 0, which = 0, direction = '+'; list; list = list->next) + { + if (ISOPTION (list->word->word, 'n')) + { + flags |= NOCD; + } + else if (ISOPTION (list->word->word, '-')) + { + list = list->next; + break; + } + else if (((direction = list->word->word[0]) == '+') || direction == '-') + { + if (legal_number (list->word->word + 1, &which) == 0) + { + sh_invalidnum (list->word->word); + builtin_usage (); + return (EX_USAGE); + } + which_word = list->word->word; + } + else if (*list->word->word == '-') + { + sh_invalidopt (list->word->word); + builtin_usage (); + return (EX_USAGE); + } + else if (*list->word->word) + { + builtin_error (_("%s: invalid argument"), list->word->word); + builtin_usage (); + return (EX_USAGE); + } + else + break; + } + + if (which > directory_list_offset || (which < -directory_list_offset) || (directory_list_offset == 0 && which == 0)) + { + pushd_error (directory_list_offset, which_word ? which_word : ""); + return (EXECUTION_FAILURE); + } + + /* Handle case of no specification, or top of stack specification. */ + if ((direction == '+' && which == 0) || + (direction == '-' && which == directory_list_offset)) + { + i = ((flags & NOCD) == 0) ? cd_to_string (pushd_directory_list[directory_list_offset - 1]) + : EXECUTION_SUCCESS; + if (i != EXECUTION_SUCCESS) + return (i); + free (pushd_directory_list[--directory_list_offset]); + } + else + { + /* Since an offset other than the top directory was specified, + remove that directory from the list and shift the remainder + of the list into place. */ + i = (direction == '+') ? directory_list_offset - which : which; + if (i < 0 || i > directory_list_offset) + { + pushd_error (directory_list_offset, which_word ? which_word : ""); + return (EXECUTION_FAILURE); + } + free (pushd_directory_list[i]); + directory_list_offset--; + + /* Shift the remainder of the list into place. */ + for (; i < directory_list_offset; i++) + pushd_directory_list[i] = pushd_directory_list[i + 1]; + } + + dirs_builtin ((WORD_LIST *)NULL); + return (EXECUTION_SUCCESS); +} + +/* Print the current list of directories on the directory stack. */ +int +dirs_builtin (list) + WORD_LIST *list; +{ + int flags, desired_index, index_flag, vflag; + intmax_t i; + char *temp, *w; + + CHECK_HELPOPT (list); + for (flags = vflag = index_flag = 0, desired_index = -1, w = ""; list; list = list->next) + { + if (ISOPTION (list->word->word, 'l')) + { + flags |= LONGFORM; + } + else if (ISOPTION (list->word->word, 'c')) + { + flags |= CLEARSTAK; + } + else if (ISOPTION (list->word->word, 'v')) + { + vflag |= 2; + } + else if (ISOPTION (list->word->word, 'p')) + { + vflag |= 1; + } + else if (ISOPTION (list->word->word, '-')) + { + list = list->next; + break; + } + else if (*list->word->word == '+' || *list->word->word == '-') + { + int sign; + if (legal_number (w = list->word->word + 1, &i) == 0) + { + sh_invalidnum (list->word->word); + builtin_usage (); + return (EX_USAGE); + } + sign = (*list->word->word == '+') ? 1 : -1; + desired_index = get_dirstack_index (i, sign, &index_flag); + } + else + { + sh_invalidopt (list->word->word); + builtin_usage (); + return (EX_USAGE); + } + } + + if (flags & CLEARSTAK) + { + clear_directory_stack (); + return (EXECUTION_SUCCESS); + } + + if (index_flag && (desired_index < 0 || desired_index > directory_list_offset)) + { + pushd_error (directory_list_offset, w); + return (EXECUTION_FAILURE); + } + +#define DIRSTACK_FORMAT(temp) \ + (flags & LONGFORM) ? temp : polite_directory_format (temp) + + /* The first directory printed is always the current working directory. */ + if (index_flag == 0 || (index_flag == 1 && desired_index == 0)) + { + temp = get_working_directory ("dirs"); + if (temp == 0) + temp = savestring (_("")); + if (vflag & 2) + printf ("%2d %s", 0, DIRSTACK_FORMAT (temp)); + else + printf ("%s", DIRSTACK_FORMAT (temp)); + free (temp); + if (index_flag) + { + putchar ('\n'); + return (sh_chkwrite (EXECUTION_SUCCESS)); + } + } + +#define DIRSTACK_ENTRY(i) \ + (flags & LONGFORM) ? pushd_directory_list[i] \ + : polite_directory_format (pushd_directory_list[i]) + + /* Now print the requested directory stack entries. */ + if (index_flag) + { + if (vflag & 2) + printf ("%2d %s", directory_list_offset - desired_index, + DIRSTACK_ENTRY (desired_index)); + else + printf ("%s", DIRSTACK_ENTRY (desired_index)); + } + else + for (i = directory_list_offset - 1; i >= 0; i--) + if (vflag >= 2) + printf ("\n%2d %s", directory_list_offset - (int)i, DIRSTACK_ENTRY (i)); + else + printf ("%s%s", (vflag & 1) ? "\n" : " ", DIRSTACK_ENTRY (i)); + + putchar ('\n'); + + return (sh_chkwrite (EXECUTION_SUCCESS)); +} + +static void +pushd_error (offset, arg) + int offset; + char *arg; +{ + if (offset == 0) + builtin_error (_("directory stack empty")); + else + sh_erange (arg, _("directory stack index")); +} + +static void +clear_directory_stack () +{ + register int i; + + for (i = 0; i < directory_list_offset; i++) + free (pushd_directory_list[i]); + directory_list_offset = 0; +} + +/* Switch to the directory in NAME. This uses the cd_builtin to do the work, + so if the result is EXECUTION_FAILURE then an error message has already + been printed. */ +static int +cd_to_string (name) + char *name; +{ + WORD_LIST *tlist; + WORD_LIST *dir; + int result; + + dir = make_word_list (make_word (name), NULL); + tlist = make_word_list (make_word ("--"), dir); + result = cd_builtin (tlist); + dispose_words (tlist); + return (result); +} + +static int +change_to_temp (temp) + char *temp; +{ + int tt; + + tt = temp ? cd_to_string (temp) : EXECUTION_FAILURE; + + if (tt == EXECUTION_SUCCESS) + dirs_builtin ((WORD_LIST *)NULL); + + return (tt); +} + +static void +add_dirstack_element (dir) + char *dir; +{ + if (directory_list_offset == directory_list_size) + pushd_directory_list = strvec_resize (pushd_directory_list, directory_list_size += 10); + pushd_directory_list[directory_list_offset++] = dir; +} + +static int +get_dirstack_index (ind, sign, indexp) + intmax_t ind; + int sign, *indexp; +{ + if (indexp) + *indexp = sign > 0 ? 1 : 2; + + /* dirs +0 prints the current working directory. */ + /* dirs -0 prints last element in directory stack */ + if (ind == 0 && sign > 0) + return 0; + else if (ind == directory_list_offset) + { + if (indexp) + *indexp = sign > 0 ? 2 : 1; + return 0; + } + else if (ind >= 0 && ind <= directory_list_offset) + return (sign > 0 ? directory_list_offset - ind : ind); + else + return -1; +} + +/* Used by the tilde expansion code. */ +char * +get_dirstack_from_string (string) + char *string; +{ + int ind, sign, index_flag; + intmax_t i; + + sign = 1; + if (*string == '-' || *string == '+') + { + sign = (*string == '-') ? -1 : 1; + string++; + } + if (legal_number (string, &i) == 0) + return ((char *)NULL); + + index_flag = 0; + ind = get_dirstack_index (i, sign, &index_flag); + if (index_flag && (ind < 0 || ind > directory_list_offset)) + return ((char *)NULL); + if (index_flag == 0 || (index_flag == 1 && ind == 0)) + return (get_string_value ("PWD")); + else + return (pushd_directory_list[ind]); +} + +#ifdef INCLUDE_UNUSED +char * +get_dirstack_element (ind, sign) + intmax_t ind; + int sign; +{ + int i; + + i = get_dirstack_index (ind, sign, (int *)NULL); + return (i < 0 || i > directory_list_offset) ? (char *)NULL + : pushd_directory_list[i]; +} +#endif + +void +set_dirstack_element (ind, sign, value) + intmax_t ind; + int sign; + char *value; +{ + int i; + + i = get_dirstack_index (ind, sign, (int *)NULL); + if (ind == 0 || i < 0 || i > directory_list_offset) + return; + free (pushd_directory_list[i]); + pushd_directory_list[i] = savestring (value); +} + +WORD_LIST * +get_directory_stack (flags) + int flags; +{ + register int i; + WORD_LIST *ret; + char *d, *t; + + for (ret = (WORD_LIST *)NULL, i = 0; i < directory_list_offset; i++) + { + d = (flags&1) ? polite_directory_format (pushd_directory_list[i]) + : pushd_directory_list[i]; + ret = make_word_list (make_word (d), ret); + } + /* Now the current directory. */ + d = get_working_directory ("dirstack"); + i = 0; /* sentinel to decide whether or not to free d */ + if (d == 0) + d = "."; + else + { + t = (flags&1) ? polite_directory_format (d) : d; + /* polite_directory_format sometimes returns its argument unchanged. + If it does not, we can free d right away. If it does, we need to + mark d to be deleted later. */ + if (t != d) + { + free (d); + d = t; + } + else /* t == d, so d is what we want */ + i = 1; + } + ret = make_word_list (make_word (d), ret); + if (i) + free (d); + return ret; /* was (REVERSE_LIST (ret, (WORD_LIST *)); */ +} + +#ifdef LOADABLE_BUILTIN +char * const dirs_doc[] = { +N_("Display the list of currently remembered directories. Directories\n\ + find their way onto the list with the `pushd' command; you can get\n\ + back up through the list with the `popd' command.\n\ + \n\ + Options:\n\ + -c clear the directory stack by deleting all of the elements\n\ + -l do not print tilde-prefixed versions of directories relative\n\ + to your home directory\n\ + -p print the directory stack with one entry per line\n\ + -v print the directory stack with one entry per line prefixed\n\ + with its position in the stack\n\ + \n\ + Arguments:\n\ + +N Displays the Nth entry counting from the left of the list shown by\n\ + dirs when invoked without options, starting with zero.\n\ + \n\ + -N Displays the Nth entry counting from the right of the list shown by\n\ + dirs when invoked without options, starting with zero."), + (char *)NULL +}; + +char * const pushd_doc[] = { +N_("Adds a directory to the top of the directory stack, or rotates\n\ + the stack, making the new top of the stack the current working\n\ + directory. With no arguments, exchanges the top two directories.\n\ + \n\ + Options:\n\ + -n Suppresses the normal change of directory when adding\n\ + directories to the stack, so only the stack is manipulated.\n\ + \n\ + Arguments:\n\ + +N Rotates the stack so that the Nth directory (counting\n\ + from the left of the list shown by `dirs', starting with\n\ + zero) is at the top.\n\ + \n\ + -N Rotates the stack so that the Nth directory (counting\n\ + from the right of the list shown by `dirs', starting with\n\ + zero) is at the top.\n\ + \n\ + dir Adds DIR to the directory stack at the top, making it the\n\ + new current working directory.\n\ + \n\ + The `dirs' builtin displays the directory stack."), + (char *)NULL +}; + +char * const popd_doc[] = { +N_("Removes entries from the directory stack. With no arguments, removes\n\ + the top directory from the stack, and changes to the new top directory.\n\ + \n\ + Options:\n\ + -n Suppresses the normal change of directory when removing\n\ + directories from the stack, so only the stack is manipulated.\n\ + \n\ + Arguments:\n\ + +N Removes the Nth entry counting from the left of the list\n\ + shown by `dirs', starting with zero. For example: `popd +0'\n\ + removes the first directory, `popd +1' the second.\n\ + \n\ + -N Removes the Nth entry counting from the right of the list\n\ + shown by `dirs', starting with zero. For example: `popd -0'\n\ + removes the last directory, `popd -1' the next to last.\n\ + \n\ + The `dirs' builtin displays the directory stack."), + (char *)NULL +}; + +struct builtin pushd_struct = { + "pushd", + pushd_builtin, + BUILTIN_ENABLED, + pushd_doc, + "pushd [+N | -N] [-n] [dir]", + 0 +}; + +struct builtin popd_struct = { + "popd", + popd_builtin, + BUILTIN_ENABLED, + popd_doc, + "popd [+N | -N] [-n]", + 0 +}; + +struct builtin dirs_struct = { + "dirs", + dirs_builtin, + BUILTIN_ENABLED, + dirs_doc, + "dirs [-clpv] [+N] [-N]", + 0 +}; +#endif /* LOADABLE_BUILTIN */ + +#endif /* PUSHD_AND_POPD */ diff --git a/third_party/bash/builtins_read.c b/third_party/bash/builtins_read.c new file mode 100644 index 000000000..fc4346dda --- /dev/null +++ b/third_party/bash/builtins_read.c @@ -0,0 +1,1205 @@ +/* read.c, created from read.def. */ +#line 22 "./read.def" + +#line 70 "./read.def" + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" + +#include + +#include "bashansi.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include + +#ifdef __CYGWIN__ +# include +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "common.h" +#include "bashgetopt.h" +#include "trap.h" + +#include "shtty.h" + +#if defined (READLINE) +#include "bashline.h" +#include "third_party/readline/readline.h" +#endif + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +#include "shmbutil.h" +#include "timer.h" + +#if !defined(errno) +extern int errno; +#endif + +struct ttsave +{ + int fd; + TTYSTRUCT attrs; +}; + +#if defined (READLINE) +static void reset_attempted_completion_function PARAMS((char *)); +static int set_itext PARAMS((void)); +static char *edit_line PARAMS((char *, char *)); +static void set_eol_delim PARAMS((int)); +static void reset_eol_delim PARAMS((char *)); +static void set_readline_timeout PARAMS((sh_timer *t, time_t, long)); +#endif +static SHELL_VAR *bind_read_variable PARAMS((char *, char *, int)); +#if defined (HANDLE_MULTIBYTE) +static int read_mbchar PARAMS((int, char *, int, int, int)); +#endif +static void ttyrestore PARAMS((struct ttsave *)); + +static sighandler sigalrm PARAMS((int)); +static void reset_timeout PARAMS((void)); + +/* Try this to see what the rest of the shell can do with the information. */ +sh_timer *read_timeout; + +static int reading, tty_modified; +static SigHandler *old_alrm; +static unsigned char delim; + +static struct ttsave termsave; + +/* In all cases, SIGALRM just sets a flag that we check periodically. This + avoids problems with the semi-tricky stuff we do with the xfree of + input_string at the top of the unwind-protect list (see below). */ + +/* Set a flag that check_read_timeout can check. This relies on zread or + read_builtin calling trap.c:check_signals() (which calls check_read_timeout()) */ +static sighandler +sigalrm (s) + int s; +{ + /* Display warning if this is called without read_timeout set? */ + if (read_timeout) + read_timeout->alrmflag = 1; +} + +static void +reset_timeout () +{ + /* Cancel alarm before restoring signal handler. */ + if (read_timeout) + shtimer_clear (read_timeout); + read_timeout = 0; +} + +void +check_read_timeout () +{ + if (read_timeout && shtimer_chktimeout (read_timeout)) + sh_longjmp (read_timeout->jmpenv, 1); +} + +int +read_builtin_timeout (fd) + int fd; +{ + if ((read_timeout == 0) || + (read_timeout->fd != fd) || + (read_timeout->tmout.tv_sec == 0 && read_timeout->tmout.tv_usec == 0)) + return 0; + + return ((read_timeout->flags & SHTIMER_ALARM) ? shtimer_alrm (read_timeout) + : shtimer_select (read_timeout)); +} + +/* Read the value of the shell variables whose names follow. + The reading is done from the current input stream, whatever + that may be. Successive words of the input line are assigned + to the variables mentioned in LIST. The last variable in LIST + gets the remainder of the words on the line. If no variables + are mentioned in LIST, then the default variable is $REPLY. */ +int +read_builtin (list) + WORD_LIST *list; +{ + register char *varname; + int size, nr, pass_next, saw_escape, eof, opt, retval, code, print_ps2, nflag; + volatile int i; + int input_is_tty, input_is_pipe, unbuffered_read, skip_ctlesc, skip_ctlnul; + int raw, edit, nchars, silent, have_timeout, ignore_delim, fd; + int lastsig, t_errno; + int mb_cur_max; + unsigned int tmsec, tmusec; + long ival, uval; + intmax_t intval; + char c; + char *input_string, *orig_input_string, *ifs_chars, *prompt, *arrayname; + char *e, *t, *t1, *ps2, *tofree; + struct stat tsb; + SHELL_VAR *var; + TTYSTRUCT ttattrs, ttset; + sigset_t chldset, prevset; +#if defined (ARRAY_VARS) + WORD_LIST *alist; + int vflags; +#endif + int bindflags; +#if defined (READLINE) + char *rlbuf, *itext; + int rlind; + FILE *save_instream; +#endif + + USE_VAR(size); + USE_VAR(i); + USE_VAR(pass_next); + USE_VAR(print_ps2); + USE_VAR(saw_escape); + USE_VAR(input_is_pipe); +/* USE_VAR(raw); */ + USE_VAR(edit); + USE_VAR(tmsec); + USE_VAR(tmusec); + USE_VAR(nchars); + USE_VAR(silent); + USE_VAR(ifs_chars); + USE_VAR(prompt); + USE_VAR(arrayname); +#if defined (READLINE) + USE_VAR(rlbuf); + USE_VAR(rlind); + USE_VAR(itext); +#endif + USE_VAR(list); + USE_VAR(ps2); + USE_VAR(lastsig); + + reading = tty_modified = 0; + read_timeout = 0; + + i = 0; /* Index into the string that we are reading. */ + raw = edit = 0; /* Not reading raw input by default. */ + silent = 0; + arrayname = prompt = (char *)NULL; + fd = 0; /* file descriptor to read from */ + +#if defined (READLINE) + rlbuf = itext = (char *)0; + rlind = 0; +#endif + + mb_cur_max = MB_CUR_MAX; + tmsec = tmusec = 0; /* no timeout */ + nr = nchars = input_is_tty = input_is_pipe = unbuffered_read = have_timeout = 0; + delim = '\n'; /* read until newline */ + ignore_delim = nflag = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "ersa:d:i:n:p:t:u:N:")) != -1) + { + switch (opt) + { + case 'r': + raw = 1; + break; + case 'p': + prompt = list_optarg; + break; + case 's': + silent = 1; + break; + case 'e': +#if defined (READLINE) + edit = 1; +#endif + break; + case 'i': +#if defined (READLINE) + itext = list_optarg; +#endif + break; +#if defined (ARRAY_VARS) + case 'a': + arrayname = list_optarg; + break; +#endif + case 't': + code = uconvert (list_optarg, &ival, &uval, (char **)NULL); + if (code == 0 || ival < 0 || uval < 0) + { + builtin_error (_("%s: invalid timeout specification"), list_optarg); + return (EXECUTION_FAILURE); + } + else + { + have_timeout = 1; + tmsec = ival; + tmusec = uval; + } + break; + case 'N': + ignore_delim = 1; + delim = -1; + case 'n': + nflag = 1; + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (int)intval) + { + sh_invalidnum (list_optarg); + return (EXECUTION_FAILURE); + } + else + nchars = intval; + break; + case 'u': + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (int)intval) + { + builtin_error (_("%s: invalid file descriptor specification"), list_optarg); + return (EXECUTION_FAILURE); + } + else + fd = intval; + if (sh_validfd (fd) == 0) + { + builtin_error (_("%d: invalid file descriptor: %s"), fd, strerror (errno)); + return (EXECUTION_FAILURE); + } + break; + case 'd': + delim = *list_optarg; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + /* `read -t 0 var' tests whether input is available with select/FIONREAD, + and fails if those are unavailable */ + if (have_timeout && tmsec == 0 && tmusec == 0) + return (input_avail (fd) ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + + /* Convenience: check early whether or not the first of possibly several + variable names is a valid identifier, and bail early if so. */ +#if defined (ARRAY_VARS) + if (list) + SET_VFLAGS (list->word->flags, vflags, bindflags); + if (list && legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word, vflags) == 0) +#else + bindflags = 0; + if (list && legal_identifier (list->word->word) == 0) +#endif + { + sh_invalidid (list->word->word); + return (EXECUTION_FAILURE); + } + + /* If we're asked to ignore the delimiter, make sure we do. */ + if (ignore_delim) + delim = -1; + + /* IF IFS is unset, we use the default of " \t\n". */ + ifs_chars = getifs (); + if (ifs_chars == 0) /* XXX - shouldn't happen */ + ifs_chars = ""; + /* If we want to read exactly NCHARS chars, don't split on IFS */ + if (ignore_delim) + ifs_chars = ""; + for (skip_ctlesc = skip_ctlnul = 0, e = ifs_chars; *e; e++) + skip_ctlesc |= *e == CTLESC, skip_ctlnul |= *e == CTLNUL; + + input_string = (char *)xmalloc (size = 112); /* XXX was 128 */ + input_string[0] = '\0'; + + /* More input and options validation */ + if (nflag == 1 && nchars == 0) + { + retval = read (fd, &c, 0); + retval = (retval >= 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; + goto assign_vars; /* bail early if asked to read 0 chars */ + } + + /* $TMOUT, if set, is the default timeout for read. */ + if (have_timeout == 0 && (e = get_string_value ("TMOUT"))) + { + code = uconvert (e, &ival, &uval, (char **)NULL); + if (code == 0 || ival < 0 || uval < 0) + tmsec = tmusec = 0; + else + { + tmsec = ival; + tmusec = uval; + } + } + +#if defined (SIGCHLD) + sigemptyset (&chldset); + sigprocmask (SIG_BLOCK, (sigset_t *)0, &chldset); + sigaddset (&chldset, SIGCHLD); +#endif + + begin_unwind_frame ("read_builtin"); + +#if defined (BUFFERED_INPUT) + if (interactive == 0 && default_buffered_input >= 0 && fd_is_bash_input (fd)) + sync_buffered_stream (default_buffered_input); +#endif + +#if 1 + input_is_tty = isatty (fd); +#else + input_is_tty = 1; +#endif + if (input_is_tty == 0) +#ifndef __CYGWIN__ + input_is_pipe = (lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE); +#else + input_is_pipe = 1; +#endif + + /* If the -p, -e or -s flags were given, but input is not coming from the + terminal, turn them off. */ + if ((prompt || edit || silent) && input_is_tty == 0) + { + prompt = (char *)NULL; +#if defined (READLINE) + itext = (char *)NULL; +#endif + edit = silent = 0; + } + +#if defined (READLINE) + if (edit) + add_unwind_protect (xfree, rlbuf); +#endif + + pass_next = 0; /* Non-zero signifies last char was backslash. */ + saw_escape = 0; /* Non-zero signifies that we saw an escape char */ + + if (tmsec > 0 || tmusec > 0) + { + /* Turn off the timeout if stdin is a regular file (e.g. from + input redirection). */ + if ((fstat (fd, &tsb) < 0) || S_ISREG (tsb.st_mode)) + tmsec = tmusec = 0; + } + + if (tmsec > 0 || tmusec > 0) + { + read_timeout = shtimer_alloc (); + read_timeout->flags = SHTIMER_LONGJMP; + +#if defined (HAVE_SELECT) + read_timeout->flags |= (edit || posixly_correct) ? SHTIMER_ALARM : SHTIMER_SELECT; +#else + read_timeout->flags |= SHTIMER_ALARM; +#endif + read_timeout->fd = fd; + + read_timeout->alrm_handler = sigalrm; + } + + if (tmsec > 0 || tmusec > 0) + { + code = setjmp_nosigs (read_timeout->jmpenv); + if (code) + { + reset_timeout (); + sigprocmask (SIG_SETMASK, &prevset, (sigset_t *)0); + + /* Tricky. The top of the unwind-protect stack is the free of + input_string. We want to run all the rest and use input_string, + so we have to save input_string temporarily, run the unwind- + protects, then restore input_string so we can use it later */ + orig_input_string = 0; + input_string[i] = '\0'; /* make sure it's terminated */ + if (i == 0) + { + t = (char *)xmalloc (1); + t[0] = 0; + } + else + t = savestring (input_string); + + run_unwind_frame ("read_builtin"); + input_string = t; + retval = 128+SIGALRM; + goto assign_vars; + } + if (interactive_shell == 0) + initialize_terminating_signals (); + add_unwind_protect (reset_timeout, (char *)NULL); +#if defined (READLINE) + if (edit) + { + add_unwind_protect (reset_attempted_completion_function, (char *)NULL); + add_unwind_protect (bashline_reset_event_hook, (char *)NULL); + set_readline_timeout (read_timeout, tmsec, tmusec); + } + else +#endif + shtimer_set (read_timeout, tmsec, tmusec); + } + + /* If we've been asked to read only NCHARS chars, or we're using some + character other than newline to terminate the line, do the right + thing to readline or the tty. */ + if (nchars > 0 || delim != '\n') + { +#if defined (READLINE) + if (edit) + { + if (nchars > 0) + { + unwind_protect_int (rl_num_chars_to_read); + rl_num_chars_to_read = nchars; + } + if (delim != '\n') + { + set_eol_delim (delim); + add_unwind_protect (reset_eol_delim, (char *)NULL); + } + } + else +#endif + if (input_is_tty) + { + /* ttsave() */ + termsave.fd = fd; + ttgetattr (fd, &ttattrs); + termsave.attrs = ttattrs; + + ttset = ttattrs; + i = silent ? ttfd_cbreak (fd, &ttset) : ttfd_onechar (fd, &ttset); + if (i < 0) + sh_ttyerror (1); + tty_modified = 1; + add_unwind_protect ((Function *)ttyrestore, (char *)&termsave); + if (interactive_shell == 0) + initialize_terminating_signals (); + } + } + else if (silent) /* turn off echo but leave term in canonical mode */ + { + /* ttsave (); */ + termsave.fd = fd; + ttgetattr (fd, &ttattrs); + termsave.attrs = ttattrs; + + ttset = ttattrs; + i = ttfd_noecho (fd, &ttset); /* ttnoecho (); */ + if (i < 0) + sh_ttyerror (1); + + tty_modified = 1; + add_unwind_protect ((Function *)ttyrestore, (char *)&termsave); + if (interactive_shell == 0) + initialize_terminating_signals (); + } + +#if defined (READLINE) + save_instream = 0; + if (edit && fd != 0) + { + if (bash_readline_initialized == 0) + initialize_readline (); + + unwind_protect_var (rl_instream); + save_instream = rl_instream; + rl_instream = fdopen (fd, "r"); + } +#endif + + /* This *must* be the top unwind-protect on the stack, so the manipulation + of the unwind-protect stack after the realloc() works right. */ + add_unwind_protect (xfree, input_string); + + check_read_timeout (); + /* These only matter if edit == 0 */ + if ((nchars > 0) && (input_is_tty == 0) && ignore_delim) /* read -N */ + unbuffered_read = 2; +#if 0 + else if ((nchars > 0) || (delim != '\n') || input_is_pipe) +#else + else if (((nchars > 0 || delim != '\n') && input_is_tty) || input_is_pipe) + unbuffered_read = 1; +#endif + if (prompt && edit == 0) + { + fprintf (stderr, "%s", prompt); + fflush (stderr); + } + +#if defined (__CYGWIN__) && defined (O_TEXT) + setmode (0, O_TEXT); +#endif + + ps2 = 0; + for (print_ps2 = eof = retval = 0;;) + { + check_read_timeout (); + +#if defined (READLINE) + if (edit) + { + /* If we have a null delimiter, don't treat NULL as ending the line */ + if (rlbuf && rlbuf[rlind] == '\0' && delim != '\0') + { + free (rlbuf); + rlbuf = (char *)0; + } +#if defined (SIGCHLD) + if (tmsec > 0 || tmusec > 0) + sigprocmask (SIG_SETMASK, &chldset, &prevset); +#endif + if (rlbuf == 0) + { + reading = 1; + rlbuf = edit_line (prompt ? prompt : "", itext); + reading = 0; + rlind = 0; + } +#if defined (SIGCHLD) + if (tmsec > 0 || tmusec > 0) + sigprocmask (SIG_SETMASK, &prevset, (sigset_t *)0); +#endif + if (rlbuf == 0) + { + eof = 1; + break; + } + c = rlbuf[rlind++]; + } + else + { +#endif + + if (print_ps2) + { + if (ps2 == 0) + ps2 = get_string_value ("PS2"); + fprintf (stderr, "%s", ps2 ? ps2 : ""); + fflush (stderr); + print_ps2 = 0; + } + + reading = 1; + check_read_timeout (); + errno = 0; + +#if defined (SIGCHLD) + if (tmsec > 0 || tmusec > 0) + sigprocmask (SIG_SETMASK, &chldset, &prevset); +#endif + if (unbuffered_read == 2) + retval = posixly_correct ? zreadintr (fd, &c, 1) : zreadn (fd, &c, nchars - nr); + else if (unbuffered_read) + retval = posixly_correct ? zreadintr (fd, &c, 1) : zread (fd, &c, 1); + else + retval = posixly_correct ? zreadcintr (fd, &c) : zreadc (fd, &c); +#if defined (SIGCHLD) + if (tmsec > 0 || tmusec > 0) + sigprocmask (SIG_SETMASK, &prevset, (sigset_t *)0); +#endif + + reading = 0; + + if (retval <= 0) + { + int t; + + t = errno; + if (retval < 0 && errno == EINTR) + { + check_signals (); /* in case we didn't call zread via zreadc */ + lastsig = LASTSIG(); + if (lastsig == 0) + lastsig = trapped_signal_received; +#if 0 + run_pending_traps (); /* because interrupt_immediately is not set */ +#endif + } + else + lastsig = 0; + if (terminating_signal && tty_modified) + ttyrestore (&termsave); /* fix terminal before exiting */ + CHECK_TERMSIG; + eof = 1; + errno = t; /* preserve it for the error message below */ + break; + } + + QUIT; /* in case we didn't call check_signals() */ +#if defined (READLINE) + } +#endif + + if (retval <= 0) /* XXX shouldn't happen */ + check_read_timeout (); + + /* XXX -- use i + mb_cur_max (at least 4) for multibyte/read_mbchar */ + if (i + (mb_cur_max > 4 ? mb_cur_max : 4) >= size) + { + char *t; + t = (char *)xrealloc (input_string, size += 128); + + /* Only need to change unwind-protect if input_string changes */ + if (t != input_string) + { + input_string = t; + remove_unwind_protect (); + add_unwind_protect (xfree, input_string); + } + } + + /* If the next character is to be accepted verbatim, a backslash + newline pair still disappears from the input. */ + if (pass_next) + { + pass_next = 0; + if (c == '\n') + { + if (skip_ctlesc == 0 && i > 0) + i--; /* back up over the CTLESC */ + if (interactive && input_is_tty && raw == 0) + print_ps2 = 1; + } + else + goto add_char; + continue; + } + + /* This may cause problems if IFS contains CTLESC */ + if (c == '\\' && raw == 0) + { + pass_next++; + if (skip_ctlesc == 0) + { + saw_escape++; + input_string[i++] = CTLESC; + } + continue; + } + + if (ignore_delim == 0 && (unsigned char)c == delim) + break; + + if (c == '\0' && delim != '\0') + continue; /* skip NUL bytes in input */ + + if ((skip_ctlesc == 0 && c == CTLESC) || (skip_ctlnul == 0 && c == CTLNUL)) + { + saw_escape++; + input_string[i++] = CTLESC; + } + +add_char: + input_string[i++] = c; + check_read_timeout (); + +#if defined (HANDLE_MULTIBYTE) + /* XXX - what if C == 127? Can DEL introduce a multibyte sequence? */ + if (mb_cur_max > 1 && is_basic (c) == 0) + { + input_string[i] = '\0'; /* for simplicity and debugging */ + /* If we got input from readline, grab the next multibyte char from + rlbuf. */ +# if defined (READLINE) + if (edit) + { + size_t clen; + clen = mbrlen (rlbuf + rlind - 1, mb_cur_max, (mbstate_t *)NULL); + /* We only deal with valid multibyte sequences longer than one + byte. If we get anything else, we leave the one character + copied and move on to the next. */ + if ((int)clen > 1) + { + memcpy (input_string+i, rlbuf+rlind, clen-1); + i += clen - 1; + rlind += clen - 1; + } + } + else +# endif + if (locale_utf8locale == 0 || ((c & 0x80) != 0)) + i += read_mbchar (fd, input_string, i, c, unbuffered_read); + } +#endif + + nr++; + + if (nchars > 0 && nr >= nchars) + break; + } + input_string[i] = '\0'; + check_read_timeout (); + +#if defined (READLINE) + if (edit) + free (rlbuf); +#endif + + if (retval < 0) + { + t_errno = errno; + if (errno != EINTR) + builtin_error (_("read error: %d: %s"), fd, strerror (errno)); + run_unwind_frame ("read_builtin"); + return ((t_errno != EINTR) ? EXECUTION_FAILURE : 128+lastsig); + } + + if (tmsec > 0 || tmusec > 0) + reset_timeout (); + + if (nchars > 0 || delim != '\n') + { +#if defined (READLINE) + if (edit) + { + if (nchars > 0) + rl_num_chars_to_read = 0; + if (delim != '\n') + reset_eol_delim ((char *)NULL); + } + else +#endif + if (input_is_tty) + ttyrestore (&termsave); + } + else if (silent) + ttyrestore (&termsave); + + if (unbuffered_read == 0) + zsyncfd (fd); + +#if defined (READLINE) + if (save_instream) + rl_instream = save_instream; /* can't portably free it */ +#endif + + discard_unwind_frame ("read_builtin"); + + retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS; + +assign_vars: + +#if defined (ARRAY_VARS) + /* If -a was given, take the string read, break it into a list of words, + an assign them to `arrayname' in turn. */ + if (arrayname) + { + /* pass 1 for flags arg to clear the existing array + 2 to check for a + valid identifier. */ + var = builtin_find_indexed_array (arrayname, 3); + if (var == 0) + { + free (input_string); + return EXECUTION_FAILURE; /* readonly or noassign */ + } + + alist = list_string (input_string, ifs_chars, 0); + if (alist) + { + if (saw_escape) + dequote_list (alist); + else + word_list_remove_quoted_nulls (alist); + assign_array_var_from_word_list (var, alist, 0); + dispose_words (alist); + } + free (input_string); + return (retval); + } +#endif /* ARRAY_VARS */ + + /* If there are no variables, save the text of the line read to the + variable $REPLY. ksh93 strips leading and trailing IFS whitespace, + so that `read x ; echo "$x"' and `read ; echo "$REPLY"' behave the + same way, but I believe that the difference in behaviors is useful + enough to not do it. Without the bash behavior, there is no way + to read a line completely without interpretation or modification + unless you mess with $IFS (e.g., setting it to the empty string). + If you disagree, change the occurrences of `#if 0' to `#if 1' below. */ + if (list == 0) + { +#if 0 + orig_input_string = input_string; + for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++) + ; + input_string = t; + input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape); +#endif + + if (saw_escape) + { + t = dequote_string (input_string); + var = bind_variable ("REPLY", t, 0); + free (t); + } + else + var = bind_variable ("REPLY", input_string, 0); + if (var == 0 || readonly_p (var) || noassign_p (var)) + retval = EXECUTION_FAILURE; + else + VUNSETATTR (var, att_invisible); + + free (input_string); + return (retval); + } + + /* This code implements the Posix.2 spec for splitting the words + read and assigning them to variables. */ + orig_input_string = input_string; + + /* Remove IFS white space at the beginning of the input string. If + $IFS is null, no field splitting is performed. */ + for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++) + ; + input_string = t; + for (; list->next; list = list->next) + { + varname = list->word->word; +#if defined (ARRAY_VARS) + SET_VFLAGS (list->word->flags, vflags, bindflags); + if (legal_identifier (varname) == 0 && valid_array_reference (varname, vflags) == 0) +#else + if (legal_identifier (varname) == 0) +#endif + { + sh_invalidid (varname); + free (orig_input_string); + return (EXECUTION_FAILURE); + } + + /* If there are more variables than words read from the input, + the remaining variables are set to the empty string. */ + if (*input_string) + { + /* This call updates INPUT_STRING. */ + t = get_word_from_string (&input_string, ifs_chars, &e); + if (t) + *e = '\0'; + /* Don't bother to remove the CTLESC unless we added one + somewhere while reading the string. */ + if (t && saw_escape) + { + t1 = dequote_string (t); + var = bind_read_variable (varname, t1, bindflags); + free (t1); + } + else + var = bind_read_variable (varname, t ? t : "", bindflags); + } + else + { + t = (char *)0; + var = bind_read_variable (varname, "", bindflags); + } + + FREE (t); + if (var == 0) + { + free (orig_input_string); + return (EXECUTION_FAILURE); + } + + stupidly_hack_special_variables (varname); + VUNSETATTR (var, att_invisible); + } + + /* Now assign the rest of the line to the last variable argument. */ +#if defined (ARRAY_VARS) + SET_VFLAGS (list->word->flags, vflags, bindflags); + if (legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word, vflags) == 0) +#else + if (legal_identifier (list->word->word) == 0) +#endif + { + sh_invalidid (list->word->word); + free (orig_input_string); + return (EXECUTION_FAILURE); + } + +#if 0 + /* This has to be done this way rather than using string_list + and list_string because Posix.2 says that the last variable gets the + remaining words and their intervening separators. */ + input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape); +#else + /* Check whether or not the number of fields is exactly the same as the + number of variables. */ + tofree = NULL; + if (*input_string) + { + t1 = input_string; + t = get_word_from_string (&input_string, ifs_chars, &e); + if (*input_string == 0) + tofree = input_string = t; + else + { + input_string = strip_trailing_ifs_whitespace (t1, ifs_chars, saw_escape); + tofree = t; + } + } +#endif + + if (saw_escape && input_string && *input_string) + { + t = dequote_string (input_string); + var = bind_read_variable (list->word->word, t, bindflags); + free (t); + } + else + var = bind_read_variable (list->word->word, input_string ? input_string : "", bindflags); + + if (var) + { + stupidly_hack_special_variables (list->word->word); + VUNSETATTR (var, att_invisible); + } + else + retval = EXECUTION_FAILURE; + + FREE (tofree); + free (orig_input_string); + + return (retval); +} + +static SHELL_VAR * +bind_read_variable (name, value, flags) + char *name, *value; + int flags; +{ + SHELL_VAR *v; + + v = builtin_bind_variable (name, value, flags); + return (v == 0 ? v + : ((readonly_p (v) || noassign_p (v)) ? (SHELL_VAR *)NULL : v)); +} + +#if defined (HANDLE_MULTIBYTE) +static int +read_mbchar (fd, string, ind, ch, unbuffered) + int fd; + char *string; + int ind, ch, unbuffered; +{ + char mbchar[MB_LEN_MAX + 1]; + int i, n, r; + char c; + size_t ret; + mbstate_t ps, ps_back; + wchar_t wc; + + memset (&ps, '\0', sizeof (mbstate_t)); + memset (&ps_back, '\0', sizeof (mbstate_t)); + + mbchar[0] = ch; + i = 1; + for (n = 0; n <= MB_LEN_MAX; n++) + { + ps_back = ps; + ret = mbrtowc (&wc, mbchar, i, &ps); + if (ret == (size_t)-2) + { + ps = ps_back; + + /* We don't want to be interrupted during a multibyte char read */ + if (unbuffered == 2) + r = zreadn (fd, &c, 1); + else if (unbuffered) + r = zread (fd, &c, 1); + else + r = zreadc (fd, &c); + if (r <= 0) + goto mbchar_return; + mbchar[i++] = c; + continue; + } + else if (ret == (size_t)-1 || ret == (size_t)0 || ret > (size_t)0) + break; + } + +mbchar_return: + if (i > 1) /* read a multibyte char */ + /* mbchar[0] is already string[ind-1] */ + for (r = 1; r < i; r++) + string[ind+r-1] = mbchar[r]; + return i - 1; +} +#endif + + +static void +ttyrestore (ttp) + struct ttsave *ttp; +{ + ttsetattr (ttp->fd, &(ttp->attrs)); + tty_modified = 0; +} + +void +read_tty_cleanup () +{ + if (tty_modified) + ttyrestore (&termsave); +} + +int +read_tty_modified () +{ + return (tty_modified); +} + +#if defined (READLINE) +static rl_completion_func_t *old_attempted_completion_function = 0; +static rl_hook_func_t *old_startup_hook; +static char *deftext; + +static void +reset_attempted_completion_function (cp) + char *cp; +{ + if (rl_attempted_completion_function == 0 && old_attempted_completion_function) + rl_attempted_completion_function = old_attempted_completion_function; +} + +static int +set_itext () +{ + int r1, r2; + + r1 = r2 = 0; + if (old_startup_hook) + r1 = (*old_startup_hook) (); + if (deftext) + { + r2 = rl_insert_text (deftext); + deftext = (char *)NULL; + rl_startup_hook = old_startup_hook; + old_startup_hook = (rl_hook_func_t *)NULL; + } + return (r1 || r2); +} + +static char * +edit_line (p, itext) + char *p; + char *itext; +{ + char *ret; + int len; + + if (bash_readline_initialized == 0) + initialize_readline (); + + old_attempted_completion_function = rl_attempted_completion_function; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + bashline_set_event_hook (); + if (itext) + { + old_startup_hook = rl_startup_hook; + rl_startup_hook = set_itext; + deftext = itext; + } + + ret = readline (p); + + rl_attempted_completion_function = old_attempted_completion_function; + old_attempted_completion_function = (rl_completion_func_t *)NULL; + bashline_reset_event_hook (); + + if (ret == 0) + { + if (RL_ISSTATE (RL_STATE_TIMEOUT)) + { + sigalrm (SIGALRM); /* simulate receiving SIGALRM */ + check_read_timeout (); + } + return ret; + } + + len = strlen (ret); + ret = (char *)xrealloc (ret, len + 2); + ret[len++] = delim; + ret[len] = '\0'; + return ret; +} + +static void +set_readline_timeout (t, sec, usec) + sh_timer *t; + time_t sec; + long usec; +{ + t->tmout.tv_sec = sec; + t->tmout.tv_usec = usec; + rl_set_timeout (sec, usec); +} + +static int old_delim_ctype; +static rl_command_func_t *old_delim_func; +static int old_newline_ctype; +static rl_command_func_t *old_newline_func; + +static unsigned char delim_char; + +static void +set_eol_delim (c) + int c; +{ + Keymap cmap; + + if (bash_readline_initialized == 0) + initialize_readline (); + cmap = rl_get_keymap (); + + /* Save the old delimiter char binding */ + old_newline_ctype = cmap[RETURN].type; + old_newline_func = cmap[RETURN].function; + old_delim_ctype = cmap[c].type; + old_delim_func = cmap[c].function; + + /* Change newline to self-insert */ + cmap[RETURN].type = ISFUNC; + cmap[RETURN].function = rl_insert; + + /* Bind the delimiter character to accept-line. */ + cmap[c].type = ISFUNC; + cmap[c].function = rl_newline; + + delim_char = c; +} + +static void +reset_eol_delim (cp) + char *cp; +{ + Keymap cmap; + + cmap = rl_get_keymap (); + + cmap[RETURN].type = old_newline_ctype; + cmap[RETURN].function = old_newline_func; + + cmap[delim_char].type = old_delim_ctype; + cmap[delim_char].function = old_delim_func; +} +#endif diff --git a/third_party/bash/builtins_return.c b/third_party/bash/builtins_return.c new file mode 100644 index 000000000..7f5fbd289 --- /dev/null +++ b/third_party/bash/builtins_return.c @@ -0,0 +1,40 @@ +/* return.c, created from return.def. */ +#line 22 "./return.def" + +#line 36 "./return.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "common.h" +#include "bashgetopt.h" + +/* If we are executing a user-defined function then exit with the value + specified as an argument. if no argument is given, then the last + exit status is used. */ +int +return_builtin (list) + WORD_LIST *list; +{ + CHECK_HELPOPT (list); + + return_catch_value = get_exitstat (list); + + if (return_catch_flag) + sh_longjmp (return_catch, 1); + else + { + builtin_error (_("can only `return' from a function or sourced script")); + return (EX_USAGE); + } +} diff --git a/third_party/bash/builtins_set.c b/third_party/bash/builtins_set.c new file mode 100644 index 000000000..ed1db4508 --- /dev/null +++ b/third_party/bash/builtins_set.c @@ -0,0 +1,890 @@ +/* set.c, created from set.def. */ +#line 22 "./set.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "parser.h" +#include "flags.h" +#include "common.h" +#include "bashgetopt.h" + +#if defined (READLINE) +# include "input.h" +# include "bashline.h" +# include "third_party/readline/readline.h" +#endif + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +#line 153 "./set.def" + +typedef int setopt_set_func_t PARAMS((int, char *)); +typedef int setopt_get_func_t PARAMS((char *)); + +static int find_minus_o_option PARAMS((char *)); + +static void print_minus_o_option PARAMS((char *, int, int)); +static void print_all_shell_variables PARAMS((void)); + +static int set_ignoreeof PARAMS((int, char *)); +static int set_posix_mode PARAMS((int, char *)); + +#if defined (READLINE) +static int set_edit_mode PARAMS((int, char *)); +static int get_edit_mode PARAMS((char *)); +#endif + +#if defined (HISTORY) +static int bash_set_history PARAMS((int, char *)); +#endif + +static const char * const on = "on"; +static const char * const off = "off"; + +static int previous_option_value; + +/* A struct used to match long options for set -o to the corresponding + option letter or internal variable. The functions can be called to + dynamically generate values. If you add a new variable name here + that doesn't have a corresponding single-character option letter, make + sure to set the value appropriately in reset_shell_options. */ +const struct { + char *name; + int letter; + int *variable; + setopt_set_func_t *set_func; + setopt_get_func_t *get_func; +} o_options[] = { + { "allexport", 'a', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#if defined (BRACE_EXPANSION) + { "braceexpand",'B', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#endif +#if defined (READLINE) + { "emacs", '\0', (int *)NULL, set_edit_mode, get_edit_mode }, +#endif + { "errexit", 'e', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "errtrace", 'E', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "functrace", 'T', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "hashall", 'h', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#if defined (BANG_HISTORY) + { "histexpand", 'H', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#endif /* BANG_HISTORY */ +#if defined (HISTORY) + { "history", '\0', &enable_history_list, bash_set_history, (setopt_get_func_t *)NULL }, +#endif + { "ignoreeof", '\0', &ignoreeof, set_ignoreeof, (setopt_get_func_t *)NULL }, + { "interactive-comments", '\0', &interactive_comments, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "keyword", 'k', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#if defined (JOB_CONTROL) + { "monitor", 'm', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#endif + { "noclobber", 'C', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "noexec", 'n', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "noglob", 'f', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#if defined (HISTORY) + { "nolog", '\0', &dont_save_function_defs, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#endif +#if defined (JOB_CONTROL) + { "notify", 'b', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#endif /* JOB_CONTROL */ + { "nounset", 'u', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "onecmd", 't', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "physical", 'P', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "pipefail", '\0', &pipefail_opt, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "posix", '\0', &posixly_correct, set_posix_mode, (setopt_get_func_t *)NULL }, + { "privileged", 'p', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + { "verbose", 'v', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +#if defined (READLINE) + { "vi", '\0', (int *)NULL, set_edit_mode, get_edit_mode }, +#endif + { "xtrace", 'x', (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, + {(char *)NULL, 0 , (int *)NULL, (setopt_set_func_t *)NULL, (setopt_get_func_t *)NULL }, +}; + +#define N_O_OPTIONS (sizeof (o_options) / sizeof (o_options[0])) + +#define GET_BINARY_O_OPTION_VALUE(i, name) \ + ((o_options[i].get_func) ? (*o_options[i].get_func) (name) \ + : (*o_options[i].variable)) + +#define SET_BINARY_O_OPTION_VALUE(i, onoff, name) \ + ((o_options[i].set_func) ? (*o_options[i].set_func) (onoff, name) \ + : (*o_options[i].variable = (onoff == FLAG_ON))) + +static int +find_minus_o_option (name) + char *name; +{ + register int i; + + for (i = 0; o_options[i].name; i++) + if (STREQ (name, o_options[i].name)) + return i; + return -1; +} + +int +minus_o_option_value (name) + char *name; +{ + register int i; + int *on_or_off; + + i = find_minus_o_option (name); + if (i < 0) + return (-1); + + if (o_options[i].letter) + { + on_or_off = find_flag (o_options[i].letter); + return ((on_or_off == FLAG_UNKNOWN) ? -1 : *on_or_off); + } + else + return (GET_BINARY_O_OPTION_VALUE (i, name)); +} + +#define MINUS_O_FORMAT "%-15s\t%s\n" + +static void +print_minus_o_option (name, value, pflag) + char *name; + int value, pflag; +{ + if (pflag == 0) + printf (MINUS_O_FORMAT, name, value ? on : off); + else + printf ("set %co %s\n", value ? '-' : '+', name); +} + +void +list_minus_o_opts (mode, reusable) + int mode, reusable; +{ + register int i; + int *on_or_off, value; + + for (i = 0; o_options[i].name; i++) + { + if (o_options[i].letter) + { + value = 0; + on_or_off = find_flag (o_options[i].letter); + if (on_or_off == FLAG_UNKNOWN) + on_or_off = &value; + if (mode == -1 || mode == *on_or_off) + print_minus_o_option (o_options[i].name, *on_or_off, reusable); + } + else + { + value = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name); + if (mode == -1 || mode == value) + print_minus_o_option (o_options[i].name, value, reusable); + } + } +} + +char ** +get_minus_o_opts () +{ + char **ret; + int i; + + ret = strvec_create (N_O_OPTIONS + 1); + for (i = 0; o_options[i].name; i++) + ret[i] = o_options[i].name; + ret[i] = (char *)NULL; + return ret; +} + +char * +get_current_options () +{ + char *temp; + int i, posixopts; + + posixopts = num_posix_options (); /* shopts modified by posix mode */ + /* Make the buffer big enough to hold the set -o options and the shopt + options modified by posix mode. */ + temp = (char *)xmalloc (1 + N_O_OPTIONS + posixopts); + for (i = 0; o_options[i].name; i++) + { + if (o_options[i].letter) + temp[i] = *(find_flag (o_options[i].letter)); + else + temp[i] = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name); + } + + /* Add the shell options that are modified by posix mode to the end of the + bitmap. They will be handled in set_current_options() */ + get_posix_options (temp+i); + temp[i+posixopts] = '\0'; + return (temp); +} + +void +set_current_options (bitmap) + const char *bitmap; +{ + int i, v, cv, *on_or_off; + + if (bitmap == 0) + return; + + for (i = 0; o_options[i].name; i++) + { + v = bitmap[i] ? FLAG_ON : FLAG_OFF; + if (o_options[i].letter) + { + /* We should not get FLAG_UNKNOWN here */ + on_or_off = find_flag (o_options[i].letter); + cv = *on_or_off ? FLAG_ON : FLAG_OFF; + if (v != cv) + change_flag (o_options[i].letter, v); + } + else + { + cv = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name); + cv = cv ? FLAG_ON : FLAG_OFF; + if (v != cv) + SET_BINARY_O_OPTION_VALUE (i, v, o_options[i].name); + } + } + + /* Now reset the variables changed by posix mode */ + set_posix_options (bitmap+i); +} + +static int +set_ignoreeof (on_or_off, option_name) + int on_or_off; + char *option_name; +{ + ignoreeof = on_or_off == FLAG_ON; + unbind_variable_noref ("ignoreeof"); + if (ignoreeof) + bind_variable ("IGNOREEOF", "10", 0); + else + unbind_variable_noref ("IGNOREEOF"); + sv_ignoreeof ("IGNOREEOF"); + return 0; +} + +static int +set_posix_mode (on_or_off, option_name) + int on_or_off; + char *option_name; +{ + /* short-circuit on no-op */ + if ((on_or_off == FLAG_ON && posixly_correct) || + (on_or_off == FLAG_OFF && posixly_correct == 0)) + return 0; + + posixly_correct = on_or_off == FLAG_ON; + if (posixly_correct == 0) + unbind_variable_noref ("POSIXLY_CORRECT"); + else + bind_variable ("POSIXLY_CORRECT", "y", 0); + sv_strict_posix ("POSIXLY_CORRECT"); + return (0); +} + +#if defined (READLINE) +/* Magic. This code `knows' how readline handles rl_editing_mode. */ +static int +set_edit_mode (on_or_off, option_name) + int on_or_off; + char *option_name; +{ + int isemacs; + + if (on_or_off == FLAG_ON) + { + rl_variable_bind ("editing-mode", option_name); + + if (interactive) + with_input_from_stdin (); + no_line_editing = 0; + } + else + { + isemacs = rl_editing_mode == 1; + if ((isemacs && *option_name == 'e') || (!isemacs && *option_name == 'v')) + { + if (interactive) + with_input_from_stream (stdin, "stdin"); + no_line_editing = 1; + } + } + return 1-no_line_editing; +} + +static int +get_edit_mode (name) + char *name; +{ + return (*name == 'e' ? no_line_editing == 0 && rl_editing_mode == 1 + : no_line_editing == 0 && rl_editing_mode == 0); +} +#endif /* READLINE */ + +#if defined (HISTORY) +static int +bash_set_history (on_or_off, option_name) + int on_or_off; + char *option_name; +{ + if (on_or_off == FLAG_ON) + { + enable_history_list = 1; + bash_history_enable (); + if (history_lines_this_session == 0) + load_history (); + } + else + { + enable_history_list = 0; + bash_history_disable (); + } + return (1 - enable_history_list); +} +#endif + +int +set_minus_o_option (on_or_off, option_name) + int on_or_off; + char *option_name; +{ + register int i; + + i = find_minus_o_option (option_name); + if (i < 0) + { + sh_invalidoptname (option_name); + return (EX_USAGE); + } + + if (o_options[i].letter == 0) + { + previous_option_value = GET_BINARY_O_OPTION_VALUE (i, o_options[i].name); + SET_BINARY_O_OPTION_VALUE (i, on_or_off, option_name); + return (EXECUTION_SUCCESS); + } + else + { + if ((previous_option_value = change_flag (o_options[i].letter, on_or_off)) == FLAG_ERROR) + { + sh_invalidoptname (option_name); + return (EXECUTION_FAILURE); + } + else + return (EXECUTION_SUCCESS); + } +} + +static void +print_all_shell_variables () +{ + SHELL_VAR **vars; + + vars = all_shell_variables (); + if (vars) + { + print_var_list (vars); + free (vars); + } + + /* POSIX.2 does not allow function names and definitions to be output when + `set' is invoked without options (PASC Interp #202). */ + if (posixly_correct == 0) + { + vars = all_shell_functions (); + if (vars) + { + print_func_list (vars); + free (vars); + } + } +} + +void +set_shellopts () +{ + char *value; + char tflag[N_O_OPTIONS]; + int vsize, i, vptr, *ip, exported; + SHELL_VAR *v; + + for (vsize = i = 0; o_options[i].name; i++) + { + tflag[i] = 0; + if (o_options[i].letter) + { + ip = find_flag (o_options[i].letter); + if (ip && *ip) + { + vsize += strlen (o_options[i].name) + 1; + tflag[i] = 1; + } + } + else if (GET_BINARY_O_OPTION_VALUE (i, o_options[i].name)) + { + vsize += strlen (o_options[i].name) + 1; + tflag[i] = 1; + } + } + + value = (char *)xmalloc (vsize + 1); + + for (i = vptr = 0; o_options[i].name; i++) + { + if (tflag[i]) + { + strcpy (value + vptr, o_options[i].name); + vptr += strlen (o_options[i].name); + value[vptr++] = ':'; + } + } + + if (vptr) + vptr--; /* cut off trailing colon */ + value[vptr] = '\0'; + + v = find_variable ("SHELLOPTS"); + + /* Turn off the read-only attribute so we can bind the new value, and + note whether or not the variable was exported. */ + if (v) + { + VUNSETATTR (v, att_readonly); + exported = exported_p (v); + } + else + exported = 0; + + v = bind_variable ("SHELLOPTS", value, 0); + + /* Turn the read-only attribute back on, and turn off the export attribute + if it was set implicitly by mark_modified_vars and SHELLOPTS was not + exported before we bound the new value. */ + VSETATTR (v, att_readonly); + if (mark_modified_vars && exported == 0 && exported_p (v)) + VUNSETATTR (v, att_exported); + + free (value); +} + +void +parse_shellopts (value) + char *value; +{ + char *vname; + int vptr; + + vptr = 0; + while (vname = extract_colon_unit (value, &vptr)) + { + set_minus_o_option (FLAG_ON, vname); + free (vname); + } +} + +void +initialize_shell_options (no_shellopts) + int no_shellopts; +{ + char *temp; + SHELL_VAR *var; + + if (no_shellopts == 0) + { + var = find_variable ("SHELLOPTS"); + /* set up any shell options we may have inherited. */ + if (var && imported_p (var)) + { + temp = (array_p (var) || assoc_p (var)) ? (char *)NULL : savestring (value_cell (var)); + if (temp) + { + parse_shellopts (temp); + free (temp); + } + } + } + + /* Set up the $SHELLOPTS variable. */ + set_shellopts (); +} + +/* Reset the values of the -o options that are not also shell flags. This is + called from execute_cmd.c:initialize_subshell() when setting up a subshell + to run an executable shell script without a leading `#!'. */ +void +reset_shell_options () +{ + pipefail_opt = 0; + ignoreeof = 0; + +#if defined (STRICT_POSIX) + posixly_correct = 1; +#else + posixly_correct = 0; +#endif +#if defined (HISTORY) + dont_save_function_defs = 0; + remember_on_history = enable_history_list = 1; /* XXX */ +#endif +} + +/* Set some flags from the word values in the input list. If LIST is empty, + then print out the values of the variables instead. If LIST contains + non-flags, then set $1 - $9 to the successive words of LIST. */ +int +set_builtin (list) + WORD_LIST *list; +{ + int on_or_off, flag_name, force_assignment, opts_changed, rv, r; + register char *arg; + char s[3]; + + if (list == 0) + { + print_all_shell_variables (); + return (sh_chkwrite (EXECUTION_SUCCESS)); + } + + /* Check validity of flag arguments. */ + rv = EXECUTION_SUCCESS; + reset_internal_getopt (); + while ((flag_name = internal_getopt (list, optflags)) != -1) + { + switch (flag_name) + { + case 'i': /* don't allow set -i */ + s[0] = list_opttype; + s[1] = 'i'; + s[2] = '\0'; + sh_invalidopt (s); + builtin_usage (); + return (EX_USAGE); + CASE_HELPOPT; + case '?': + builtin_usage (); + return (list_optopt == '?' ? EXECUTION_SUCCESS : EX_USAGE); + default: + break; + } + } + + /* Do the set command. While the list consists of words starting with + '-' or '+' treat them as flags, otherwise, start assigning them to + $1 ... $n. */ + for (force_assignment = opts_changed = 0; list; ) + { + arg = list->word->word; + + /* If the argument is `--' or `-' then signal the end of the list + and remember the remaining arguments. */ + if (arg[0] == '-' && (!arg[1] || (arg[1] == '-' && !arg[2]))) + { + list = list->next; + + /* `set --' unsets the positional parameters. */ + if (arg[1] == '-') + force_assignment = 1; + + /* Until told differently, the old shell behaviour of + `set - [arg ...]' being equivalent to `set +xv [arg ...]' + stands. Posix.2 says the behaviour is marked as obsolescent. */ + else + { + change_flag ('x', '+'); + change_flag ('v', '+'); + opts_changed = 1; + } + + break; + } + + if ((on_or_off = *arg) && (on_or_off == '-' || on_or_off == '+')) + { + while (flag_name = *++arg) + { + if (flag_name == '?') + { + builtin_usage (); + return (EXECUTION_SUCCESS); + } + else if (flag_name == 'o') /* -+o option-name */ + { + char *option_name; + WORD_LIST *opt; + + opt = list->next; + + if (opt == 0) + { + list_minus_o_opts (-1, (on_or_off == '+')); + rv = sh_chkwrite (rv); + continue; + } + + option_name = opt->word->word; + + if (option_name == 0 || *option_name == '\0' || + *option_name == '-' || *option_name == '+') + { + list_minus_o_opts (-1, (on_or_off == '+')); + continue; + } + list = list->next; /* Skip over option name. */ + + opts_changed = 1; + if ((r = set_minus_o_option (on_or_off, option_name)) != EXECUTION_SUCCESS) + { + set_shellopts (); + return (r); + } + } + else if (change_flag (flag_name, on_or_off) == FLAG_ERROR) + { + s[0] = on_or_off; + s[1] = flag_name; + s[2] = '\0'; + sh_invalidopt (s); + builtin_usage (); + set_shellopts (); + return (EXECUTION_FAILURE); + } + opts_changed = 1; + } + } + else + { + break; + } + list = list->next; + } + + /* Assigning $1 ... $n */ + if (list || force_assignment) + remember_args (list, 1); + /* Set up new value of $SHELLOPTS */ + if (opts_changed) + set_shellopts (); + return (rv); +} + +#line 830 "./set.def" + +#define NEXT_VARIABLE() any_failed++; list = list->next; continue; + +int +unset_builtin (list) + WORD_LIST *list; +{ + int unset_function, unset_variable, unset_array, opt, nameref, any_failed; + int global_unset_func, global_unset_var, vflags, base_vflags, valid_id; + char *name, *tname; + + unset_function = unset_variable = unset_array = nameref = any_failed = 0; + global_unset_func = global_unset_var = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "fnv")) != -1) + { + switch (opt) + { + case 'f': + global_unset_func = 1; + break; + case 'v': + global_unset_var = 1; + break; + case 'n': + nameref = 1; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + if (global_unset_func && global_unset_var) + { + builtin_error (_("cannot simultaneously unset a function and a variable")); + return (EXECUTION_FAILURE); + } + else if (unset_function && nameref) + nameref = 0; + +#if defined (ARRAY_VARS) + base_vflags = assoc_expand_once ? VA_NOEXPAND : 0; +#endif + + while (list) + { + SHELL_VAR *var; + int tem; +#if defined (ARRAY_VARS) + char *t; +#endif + + name = list->word->word; + + unset_function = global_unset_func; + unset_variable = global_unset_var; + +#if defined (ARRAY_VARS) + vflags = builtin_arrayref_flags (list->word, base_vflags); +#endif + +#if defined (ARRAY_VARS) + unset_array = 0; + /* XXX valid array reference second arg was 0 */ + if (!unset_function && nameref == 0 && tokenize_array_reference (name, vflags, &t)) + unset_array = 1; +#endif + /* Get error checking out of the way first. The low-level functions + just perform the unset, relying on the caller to verify. */ + valid_id = legal_identifier (name); + + /* Whether or not we are in posix mode, if neither -f nor -v appears, + skip over trying to unset variables with invalid names and just + treat them as potential shell function names. */ + if (global_unset_func == 0 && global_unset_var == 0 && valid_id == 0) + { + unset_variable = unset_array = 0; + unset_function = 1; + } + + /* Bash allows functions with names which are not valid identifiers + to be created when not in posix mode, so check only when in posix + mode when unsetting a function. */ + if (unset_function == 0 && valid_id == 0) + { + sh_invalidid (name); + NEXT_VARIABLE (); + } + + /* Search for functions here if -f supplied or if NAME cannot be a + variable name. */ + var = unset_function ? find_function (name) + : (nameref ? find_variable_last_nameref (name, 0) : find_variable (name)); + + /* Some variables (but not functions yet) cannot be unset, period. */ + if (var && unset_function == 0 && non_unsettable_p (var)) + { + builtin_error (_("%s: cannot unset"), name); + NEXT_VARIABLE (); + } + + /* if we have a nameref we want to use it */ + if (var && unset_function == 0 && nameref == 0 && STREQ (name, name_cell(var)) == 0) + name = name_cell (var); + + /* Posix.2 says try variables first, then functions. If we would + find a function after unsuccessfully searching for a variable, + note that we're acting on a function now as if -f were + supplied. The readonly check below takes care of it. */ + if (var == 0 && nameref == 0 && unset_variable == 0 && unset_function == 0) + { + if (var = find_function (name)) + unset_function = 1; + } + + /* Posix.2 says that unsetting readonly variables is an error. */ + if (var && readonly_p (var)) + { + builtin_error (_("%s: cannot unset: readonly %s"), + var->name, unset_function ? "function" : "variable"); + NEXT_VARIABLE (); + } + + /* Unless the -f option is supplied, the name refers to a variable. */ +#if defined (ARRAY_VARS) + if (var && unset_array) + { + if (shell_compatibility_level <= 51) + vflags |= VA_ALLOWALL; + + /* Let unbind_array_element decide what to do with non-array vars */ + tem = unbind_array_element (var, t, vflags); /* XXX new third arg */ + if (tem == -2 && array_p (var) == 0 && assoc_p (var) == 0) + { + builtin_error (_("%s: not an array variable"), var->name); + NEXT_VARIABLE (); + } + else if (tem < 0) + any_failed++; + } + else +#endif /* ARRAY_VARS */ + /* If we're trying to unset a nameref variable whose value isn't a set + variable, make sure we still try to unset the nameref's value */ + if (var == 0 && nameref == 0 && unset_function == 0) + { + var = find_variable_last_nameref (name, 0); + if (var && nameref_p (var)) + { +#if defined (ARRAY_VARS) + if (valid_array_reference (nameref_cell (var), 0)) + { + int len; + + tname = savestring (nameref_cell (var)); + if (var = array_variable_part (tname, 0, &t, &len)) + { + /* change to what unbind_array_element now expects */ + if (t[len - 1] == ']') + t[len - 1] = 0; + tem = unbind_array_element (var, t, vflags); /* XXX new third arg */ + } + free (tname); + } + else +#endif + tem = unbind_variable (nameref_cell (var)); + } + else + tem = unbind_variable (name); + } + else + tem = unset_function ? unbind_func (name) : (nameref ? unbind_nameref (name) : unbind_variable (name)); + + /* This is what Posix.2 says: ``If neither -f nor -v + is specified, the name refers to a variable; if a variable by + that name does not exist, a function by that name, if any, + shall be unset.'' */ + if (tem == -1 && nameref == 0 && unset_function == 0 && unset_variable == 0) + tem = unbind_func (name); + + name = list->word->word; /* reset above for namerefs */ + + /* SUSv3, POSIX.1-2001 say: ``Unsetting a variable or function that + was not previously set shall not be considered an error.'' */ + + if (unset_function == 0) + stupidly_hack_special_variables (name); + + list = list->next; + } + + return (any_failed ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} diff --git a/third_party/bash/builtins_setattr.c b/third_party/bash/builtins_setattr.c new file mode 100644 index 000000000..8a9ccefa4 --- /dev/null +++ b/third_party/bash/builtins_setattr.c @@ -0,0 +1,616 @@ +/* setattr.c, created from setattr.def. */ +#line 22 "./setattr.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "flags.h" +#include "common.h" +#include "bashgetopt.h" + +extern sh_builtin_func_t *this_shell_builtin; + +#ifdef ARRAY_VARS +extern int declare_builtin PARAMS((WORD_LIST *)); +#endif + +#define READONLY_OR_EXPORT \ + (this_shell_builtin == readonly_builtin || this_shell_builtin == export_builtin) + +#line 69 "./setattr.def" + +/* For each variable name in LIST, make that variable appear in the + environment passed to simple commands. If there is no LIST, then + print all such variables. An argument of `-n' says to remove the + exported attribute from variables named in LIST. An argument of + -f indicates that the names present in LIST refer to functions. */ +int +export_builtin (list) + register WORD_LIST *list; +{ + return (set_or_show_attributes (list, att_exported, 0)); +} + +#line 103 "./setattr.def" + +/* For each variable name in LIST, make that variable readonly. Given an + empty LIST, print out all existing readonly variables. */ +int +readonly_builtin (list) + register WORD_LIST *list; +{ + return (set_or_show_attributes (list, att_readonly, 0)); +} + +#if defined (ARRAY_VARS) +# define ATTROPTS "aAfnp" +#else +# define ATTROPTS "fnp" +#endif + +/* For each variable name in LIST, make that variable have the specified + ATTRIBUTE. An arg of `-n' says to remove the attribute from the the + remaining names in LIST (doesn't work for readonly). */ +int +set_or_show_attributes (list, attribute, nodefs) + register WORD_LIST *list; + int attribute, nodefs; +{ + register SHELL_VAR *var; + int assign, undo, any_failed, assign_error, opt; + int functions_only, arrays_only, assoc_only; + int aflags; + char *name; +#if defined (ARRAY_VARS) + WORD_LIST *nlist, *tlist; + WORD_DESC *w; + char optw[8]; + int opti; +#endif + + functions_only = arrays_only = assoc_only = 0; + undo = any_failed = assign_error = 0; + /* Read arguments from the front of the list. */ + reset_internal_getopt (); + while ((opt = internal_getopt (list, ATTROPTS)) != -1) + { + switch (opt) + { + case 'n': + undo = 1; + break; + case 'f': + functions_only = 1; + break; +#if defined (ARRAY_VARS) + case 'a': + arrays_only = 1; + break; + case 'A': + assoc_only = 1; + break; +#endif + case 'p': + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if (list) + { + if (attribute & att_exported) + array_needs_making = 1; + + /* Cannot undo readonly status, silently disallowed. */ + if (undo && (attribute & att_readonly)) + attribute &= ~att_readonly; + + while (list) + { + name = list->word->word; + + if (functions_only) /* xxx -f name */ + { + var = find_function (name); + if (var == 0) + { + builtin_error (_("%s: not a function"), name); + any_failed++; + } + else if ((attribute & att_exported) && undo == 0 && exportable_function_name (name) == 0) + { + builtin_error (_("%s: cannot export"), name); + any_failed++; + } + else + SETVARATTR (var, attribute, undo); + + list = list->next; + continue; + } + + /* xxx [-np] name[=value] */ + assign = assignment (name, 0); + + aflags = 0; + if (assign) + { + name[assign] = '\0'; + if (name[assign - 1] == '+') + { + aflags |= ASS_APPEND; + name[assign - 1] = '\0'; + } + } + + if (legal_identifier (name) == 0) + { + sh_invalidid (name); + if (assign) + assign_error++; + else + any_failed++; + list = list->next; + continue; + } + + if (assign) /* xxx [-np] name=value */ + { + name[assign] = '='; + if (aflags & ASS_APPEND) + name[assign - 1] = '+'; +#if defined (ARRAY_VARS) + /* Let's try something here. Turn readonly -a xxx=yyy into + declare -ra xxx=yyy and see what that gets us. */ + if (arrays_only || assoc_only) + { + tlist = list->next; + list->next = (WORD_LIST *)NULL; + /* Add -g to avoid readonly/export creating local variables: + only local/declare/typeset create local variables */ + opti = 0; + optw[opti++] = '-'; + optw[opti++] = 'g'; + if (attribute & att_readonly) + optw[opti++] = 'r'; + if (attribute & att_exported) + optw[opti++] = 'x'; + if (arrays_only) + optw[opti++] = 'a'; + else + optw[opti++] = 'A'; + optw[opti] = '\0'; + + w = make_word (optw); + nlist = make_word_list (w, list); + + opt = declare_builtin (nlist); + if (opt != EXECUTION_SUCCESS) + assign_error++; + list->next = tlist; + dispose_word (w); + free (nlist); + } + else +#endif + /* This word has already been expanded once with command + and parameter expansion. Call do_assignment_no_expand (), + which does not do command or parameter substitution. If + the assignment is not performed correctly, flag an error. */ + if (do_assignment_no_expand (name) == 0) + assign_error++; + name[assign] = '\0'; + if (aflags & ASS_APPEND) + name[assign - 1] = '\0'; + } + + set_var_attribute (name, attribute, undo); + if (assign) /* restore word */ + { + name[assign] = '='; + if (aflags & ASS_APPEND) + name[assign-1] = '+'; + } + list = list->next; + } + } + else + { + SHELL_VAR **variable_list; + register int i; + + if ((attribute & att_function) || functions_only) + { + variable_list = all_shell_functions (); + if (attribute != att_function) + attribute &= ~att_function; /* so declare -xf works, for example */ + } + else + variable_list = all_shell_variables (); + +#if defined (ARRAY_VARS) + if (attribute & att_array) + { + arrays_only++; + if (attribute != att_array) + attribute &= ~att_array; + } + else if (attribute & att_assoc) + { + assoc_only++; + if (attribute != att_assoc) + attribute &= ~att_assoc; + } +#endif + + if (variable_list) + { + for (i = 0; var = variable_list[i]; i++) + { +#if defined (ARRAY_VARS) + if (arrays_only && array_p (var) == 0) + continue; + else if (assoc_only && assoc_p (var) == 0) + continue; +#endif + + /* If we imported a variable that's not a valid identifier, don't + show it in any lists. */ + if ((var->attributes & (att_invisible|att_imported)) == (att_invisible|att_imported)) + continue; + + if ((var->attributes & attribute)) + { + show_var_attributes (var, READONLY_OR_EXPORT, nodefs); + if (any_failed = sh_chkwrite (any_failed)) + break; + } + } + free (variable_list); + } + } + + return (assign_error ? EX_BADASSIGN + : ((any_failed == 0) ? EXECUTION_SUCCESS + : EXECUTION_FAILURE)); +} + +/* Show all variable variables (v == 1) or functions (v == 0) with + attributes. */ +int +show_all_var_attributes (v, nodefs) + int v, nodefs; +{ + SHELL_VAR **variable_list, *var; + int any_failed; + register int i; + + variable_list = v ? all_shell_variables () : all_shell_functions (); + if (variable_list == 0) + return (EXECUTION_SUCCESS); + + for (i = any_failed = 0; var = variable_list[i]; i++) + { + /* There is no equivalent `declare -'. */ + if (variable_context && var->context == variable_context && STREQ (var->name, "-")) + printf ("local -\n"); + else + show_var_attributes (var, READONLY_OR_EXPORT, nodefs); + if (any_failed = sh_chkwrite (any_failed)) + break; + } + free (variable_list); + return (any_failed == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +} + +/* Show all local variable variables with their attributes. This shows unset + local variables (all_local_variables called with 0 argument). */ +int +show_local_var_attributes (v, nodefs) + int v, nodefs; +{ + SHELL_VAR **variable_list, *var; + int any_failed; + register int i; + + variable_list = all_local_variables (0); + if (variable_list == 0) + return (EXECUTION_SUCCESS); + + for (i = any_failed = 0; var = variable_list[i]; i++) + { + /* There is no equivalent `declare -'. */ + if (STREQ (var->name, "-")) + printf ("local -\n"); + else + show_var_attributes (var, READONLY_OR_EXPORT, nodefs); + if (any_failed = sh_chkwrite (any_failed)) + break; + } + free (variable_list); + return (any_failed == 0 ? EXECUTION_SUCCESS : EXECUTION_FAILURE); +} + +int +var_attribute_string (var, pattr, flags) + SHELL_VAR *var; + int pattr; + char *flags; /* filled in with attributes */ +{ + int i; + + i = 0; + + /* pattr == 0 means we are called from `declare'. */ + if (pattr == 0 || posixly_correct == 0) + { +#if defined (ARRAY_VARS) + if (array_p (var)) + flags[i++] = 'a'; + + if (assoc_p (var)) + flags[i++] = 'A'; +#endif + + if (function_p (var)) + flags[i++] = 'f'; + + if (integer_p (var)) + flags[i++] = 'i'; + + if (nameref_p (var)) + flags[i++] = 'n'; + + if (readonly_p (var)) + flags[i++] = 'r'; + + if (trace_p (var)) + flags[i++] = 't'; + + if (exported_p (var)) + flags[i++] = 'x'; + + if (capcase_p (var)) + flags[i++] = 'c'; + + if (lowercase_p (var)) + flags[i++] = 'l'; + + if (uppercase_p (var)) + flags[i++] = 'u'; + } + else + { +#if defined (ARRAY_VARS) + if (array_p (var)) + flags[i++] = 'a'; + + if (assoc_p (var)) + flags[i++] = 'A'; +#endif + + if (function_p (var)) + flags[i++] = 'f'; + } + + flags[i] = '\0'; + return i; +} + +/* Show the attributes for shell variable VAR. If NODEFS is non-zero, + don't show function definitions along with the name. If PATTR is + non-zero, it indicates we're being called from `export' or `readonly'. + In POSIX mode, this prints the name of the calling builtin (`export' + or `readonly') instead of `declare', and doesn't print function defs + when called by `export' or `readonly'. */ +int +show_var_attributes (var, pattr, nodefs) + SHELL_VAR *var; + int pattr, nodefs; +{ + char flags[MAX_ATTRIBUTES], *x; + int i; + + i = var_attribute_string (var, pattr, flags); + + /* If we're printing functions with definitions, print the function def + first, then the attributes, instead of printing output that can't be + reused as input to recreate the current state. */ + if (function_p (var) && nodefs == 0 && (pattr == 0 || posixly_correct == 0)) + { + printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL)); + nodefs++; + if (pattr == 0 && i == 1 && flags[0] == 'f') + return 0; /* don't print `declare -f name' */ + } + + if (pattr == 0 || posixly_correct == 0) + printf ("declare -%s ", i ? flags : "-"); + else if (i) + printf ("%s -%s ", this_command_name, flags); + else + printf ("%s ", this_command_name); + +#if defined (ARRAY_VARS) + if (invisible_p (var) && (array_p (var) || assoc_p (var))) + printf ("%s\n", var->name); + else if (array_p (var)) + print_array_assignment (var, 0); + else if (assoc_p (var)) + print_assoc_assignment (var, 0); + else +#endif + /* force `readonly' and `export' to not print out function definitions + when in POSIX mode. */ + if (nodefs || (function_p (var) && pattr != 0 && posixly_correct)) + printf ("%s\n", var->name); + else if (function_p (var)) + printf ("%s\n", named_function_string (var->name, function_cell (var), FUNC_MULTILINE|FUNC_EXTERNAL)); + else if (invisible_p (var) || var_isset (var) == 0) + printf ("%s\n", var->name); + else + { + if (ansic_shouldquote (value_cell (var))) + x = ansic_quote (value_cell (var), 0, (int *)0); + else + x = sh_double_quote (value_cell (var)); + printf ("%s=%s\n", var->name, x); + free (x); + } + return (0); +} + +int +show_name_attributes (name, nodefs) + char *name; + int nodefs; +{ + SHELL_VAR *var; + + var = find_variable_noref (name); + + if (var) /* show every variable with attributes, even unset ones */ + { + show_var_attributes (var, READONLY_OR_EXPORT, nodefs); + return (0); + } + else + return (1); +} + +int +show_localname_attributes (name, nodefs) + char *name; + int nodefs; +{ + SHELL_VAR *var; + + var = find_variable_noref (name); + + if (var && local_p (var) && var->context == variable_context) /* show every variable with attributes, even unset ones */ + { + show_var_attributes (var, READONLY_OR_EXPORT, nodefs); + return (0); + } + else + return (1); +} + +int +show_func_attributes (name, nodefs) + char *name; + int nodefs; +{ + SHELL_VAR *var; + + var = find_function (name); + + if (var) + { + show_var_attributes (var, READONLY_OR_EXPORT, nodefs); + return (0); + } + else + return (1); +} + +void +set_var_attribute (name, attribute, undo) + char *name; + int attribute, undo; +{ + SHELL_VAR *var, *tv, *v, *refvar; + char *tvalue; + + if (undo) + var = find_variable (name); + else + { + tv = find_tempenv_variable (name); + /* XXX -- need to handle case where tv is a temp variable in a + function-scope context, since function_env has been merged into + the local variables table. */ + if (tv && tempvar_p (tv)) + { + tvalue = var_isset (tv) ? savestring (value_cell (tv)) : savestring (""); + + var = bind_variable (tv->name, tvalue, 0); + if (var == 0) + { + free (tvalue); + return; /* XXX - no error message here */ + } + var->attributes |= tv->attributes & ~att_tempvar; + /* This avoids an error message when propagating a read-only var + later on. */ + if (posixly_correct || shell_compatibility_level <= 44) + { + if (var->context == 0 && (attribute & att_readonly)) + { + /* Don't bother to set the `propagate to the global variables + table' flag if we've just bound the variable in that + table */ + v = find_global_variable (tv->name); + if (v != var) + VSETATTR (tv, att_propagate); + } + else + VSETATTR (tv, att_propagate); + if (var->context != 0) + VSETATTR (var, att_propagate); + } + + SETVARATTR (tv, attribute, undo); /* XXX */ + + stupidly_hack_special_variables (tv->name); + + free (tvalue); + } + else + { + var = find_variable_notempenv (name); + if (var == 0) + { + /* We might have a nameref pointing to something that we can't + resolve to a shell variable. If we do, skip it. We do a little + checking just so we can print an error message. */ + refvar = find_variable_nameref_for_create (name, 0); + if (refvar == INVALID_NAMEREF_VALUE) + return; + /* Otherwise we probably have a nameref pointing to a variable + that hasn't been created yet. bind_variable will take care + of that. */ + } + if (var == 0) + { + var = bind_variable (name, (char *)NULL, 0); + if (var) + VSETATTR (var, att_invisible); + } + else if (var->context != 0) + VSETATTR (var, att_propagate); + } + } + + if (var) + SETVARATTR (var, attribute, undo); + + if (var && (exported_p (var) || (attribute & att_exported))) + array_needs_making++; /* XXX */ +} diff --git a/third_party/bash/builtins_shift.c b/third_party/bash/builtins_shift.c new file mode 100644 index 000000000..669b60e38 --- /dev/null +++ b/third_party/bash/builtins_shift.c @@ -0,0 +1,61 @@ +/* shift.c, created from shift.def. */ +#line 22 "./shift.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "common.h" + +#line 49 "./shift.def" + +int print_shift_error; + +/* Shift the arguments ``left''. Shift DOLLAR_VARS down then take one + off of REST_OF_ARGS and place it into DOLLAR_VARS[9]. If LIST has + anything in it, it is a number which says where to start the + shifting. Return > 0 if `times' > $#, otherwise 0. */ +int +shift_builtin (list) + WORD_LIST *list; +{ + intmax_t times; + int itimes, nargs; + + CHECK_HELPOPT (list); + + if (get_numeric_arg (list, 0, ×) == 0) + return (EXECUTION_FAILURE); + + if (times == 0) + return (EXECUTION_SUCCESS); + else if (times < 0) + { + sh_erange (list ? list->word->word : NULL, _("shift count")); + return (EXECUTION_FAILURE); + } + nargs = number_of_args (); + if (times > nargs) + { + if (print_shift_error) + sh_erange (list ? list->word->word : NULL, _("shift count")); + return (EXECUTION_FAILURE); + } + else if (times == nargs) + clear_dollar_vars (); + else + shift_args (itimes = times); + + invalidate_cached_quoted_dollar_at (); + + return (EXECUTION_SUCCESS); +} diff --git a/third_party/bash/builtins_shopt.c b/third_party/bash/builtins_shopt.c new file mode 100644 index 000000000..3935c8077 --- /dev/null +++ b/third_party/bash/builtins_shopt.c @@ -0,0 +1,900 @@ +/* shopt.c, created from shopt.def. */ +#line 22 "./shopt.def" + +#line 43 "./shopt.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include + +#include "version.h" + +#include "bashintl.h" + +#include "shell.h" +#include "flags.h" +#include "common.h" +#include "bashgetopt.h" + +#if defined (READLINE) +# include "bashline.h" +#endif + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +#define UNSETOPT 0 +#define SETOPT 1 + +#define OPTFMT "%-15s\t%s\n" + +extern int allow_null_glob_expansion, fail_glob_expansion, glob_dot_filenames; +extern int cdable_vars, mail_warning, source_uses_path; +extern int no_exit_on_failed_exec, print_shift_error; +extern int check_hashed_filenames, promptvars; +extern int cdspelling, expand_aliases; +extern int extended_quote; +extern int check_window_size; +extern int glob_ignore_case, match_ignore_case; +extern int hup_on_exit; +extern int xpg_echo; +extern int gnu_error_format; +extern int check_jobs_at_exit; +extern int autocd; +extern int glob_star; +extern int glob_asciirange; +extern int glob_always_skip_dot_and_dotdot; +extern int lastpipe_opt; +extern int inherit_errexit; +extern int localvar_inherit; +extern int localvar_unset; +extern int varassign_redir_autoclose; +extern int singlequote_translations; +extern int patsub_replacement; + +#if defined (EXTENDED_GLOB) +extern int extended_glob; +#endif + +#if defined (READLINE) +extern int hist_verify, history_reediting, perform_hostname_completion; +extern int no_empty_command_completion; +extern int force_fignore; +extern int dircomplete_spelling, dircomplete_expand; +extern int complete_fullquote; + +extern int enable_hostname_completion PARAMS((int)); +#endif + +#if defined (PROGRAMMABLE_COMPLETION) +extern int prog_completion_enabled; +extern int progcomp_alias; +#endif + +#if defined (DEBUGGER) +extern int debugging_mode; +#endif + +#if defined (ARRAY_VARS) +extern int assoc_expand_once; +extern int array_expand_once; +int expand_once_flag; +#endif + +#if defined (SYSLOG_HISTORY) +extern int syslog_history; +#endif + +static void shopt_error PARAMS((char *)); + +static int set_shellopts_after_change PARAMS((char *, int)); +static int set_compatibility_level PARAMS((char *, int)); + +#if defined (RESTRICTED_SHELL) +static int set_restricted_shell PARAMS((char *, int)); +#endif + +#if defined (READLINE) +static int shopt_enable_hostname_completion PARAMS((char *, int)); +static int shopt_set_complete_direxpand PARAMS((char *, int)); +#endif + +#if defined (ARRAY_VARS) +static int set_assoc_expand PARAMS((char *, int)); +#endif + +static int shopt_set_debug_mode PARAMS((char *, int)); + +static int shopt_login_shell; +static int shopt_compat31; +static int shopt_compat32; +static int shopt_compat40; +static int shopt_compat41; +static int shopt_compat42; +static int shopt_compat43; +static int shopt_compat44; + +typedef int shopt_set_func_t PARAMS((char *, int)); + +/* If you add a new variable name here, make sure to set the default value + appropriately in reset_shopt_options. */ + +static struct { + char *name; + int *value; + shopt_set_func_t *set_func; +} shopt_vars[] = { + { "autocd", &autocd, (shopt_set_func_t *)NULL }, +#if defined (ARRAY_VARS) + { "assoc_expand_once", &expand_once_flag, set_assoc_expand }, +#endif + { "cdable_vars", &cdable_vars, (shopt_set_func_t *)NULL }, + { "cdspell", &cdspelling, (shopt_set_func_t *)NULL }, + { "checkhash", &check_hashed_filenames, (shopt_set_func_t *)NULL }, +#if defined (JOB_CONTROL) + { "checkjobs", &check_jobs_at_exit, (shopt_set_func_t *)NULL }, +#endif + { "checkwinsize", &check_window_size, (shopt_set_func_t *)NULL }, +#if defined (HISTORY) + { "cmdhist", &command_oriented_history, (shopt_set_func_t *)NULL }, +#endif + { "compat31", &shopt_compat31, set_compatibility_level }, + { "compat32", &shopt_compat32, set_compatibility_level }, + { "compat40", &shopt_compat40, set_compatibility_level }, + { "compat41", &shopt_compat41, set_compatibility_level }, + { "compat42", &shopt_compat42, set_compatibility_level }, + { "compat43", &shopt_compat43, set_compatibility_level }, + { "compat44", &shopt_compat44, set_compatibility_level }, +#if defined (READLINE) + { "complete_fullquote", &complete_fullquote, (shopt_set_func_t *)NULL}, + { "direxpand", &dircomplete_expand, shopt_set_complete_direxpand }, + { "dirspell", &dircomplete_spelling, (shopt_set_func_t *)NULL }, +#endif + { "dotglob", &glob_dot_filenames, (shopt_set_func_t *)NULL }, + { "execfail", &no_exit_on_failed_exec, (shopt_set_func_t *)NULL }, + { "expand_aliases", &expand_aliases, (shopt_set_func_t *)NULL }, +#if defined (DEBUGGER) + { "extdebug", &debugging_mode, shopt_set_debug_mode }, +#endif +#if defined (EXTENDED_GLOB) + { "extglob", &extended_glob, (shopt_set_func_t *)NULL }, +#endif + { "extquote", &extended_quote, (shopt_set_func_t *)NULL }, + { "failglob", &fail_glob_expansion, (shopt_set_func_t *)NULL }, +#if defined (READLINE) + { "force_fignore", &force_fignore, (shopt_set_func_t *)NULL }, +#endif + { "globasciiranges", &glob_asciirange, (shopt_set_func_t *)NULL }, + { "globskipdots", &glob_always_skip_dot_and_dotdot, (shopt_set_func_t *)NULL }, + { "globstar", &glob_star, (shopt_set_func_t *)NULL }, + { "gnu_errfmt", &gnu_error_format, (shopt_set_func_t *)NULL }, +#if defined (HISTORY) + { "histappend", &force_append_history, (shopt_set_func_t *)NULL }, +#endif +#if defined (READLINE) + { "histreedit", &history_reediting, (shopt_set_func_t *)NULL }, + { "histverify", &hist_verify, (shopt_set_func_t *)NULL }, + { "hostcomplete", &perform_hostname_completion, shopt_enable_hostname_completion }, +#endif + { "huponexit", &hup_on_exit, (shopt_set_func_t *)NULL }, + { "inherit_errexit", &inherit_errexit, (shopt_set_func_t *)NULL }, + { "interactive_comments", &interactive_comments, set_shellopts_after_change }, + { "lastpipe", &lastpipe_opt, (shopt_set_func_t *)NULL }, +#if defined (HISTORY) + { "lithist", &literal_history, (shopt_set_func_t *)NULL }, +#endif + { "localvar_inherit", &localvar_inherit, (shopt_set_func_t *)NULL }, + { "localvar_unset", &localvar_unset, (shopt_set_func_t *)NULL }, + { "login_shell", &shopt_login_shell, set_login_shell }, + { "mailwarn", &mail_warning, (shopt_set_func_t *)NULL }, +#if defined (READLINE) + { "no_empty_cmd_completion", &no_empty_command_completion, (shopt_set_func_t *)NULL }, +#endif + { "nocaseglob", &glob_ignore_case, (shopt_set_func_t *)NULL }, + { "nocasematch", &match_ignore_case, (shopt_set_func_t *)NULL }, + { "noexpand_translation", &singlequote_translations, (shopt_set_func_t *)NULL }, + { "nullglob", &allow_null_glob_expansion, (shopt_set_func_t *)NULL }, + { "patsub_replacement", &patsub_replacement, (shopt_set_func_t *)NULL }, +#if defined (PROGRAMMABLE_COMPLETION) + { "progcomp", &prog_completion_enabled, (shopt_set_func_t *)NULL }, +# if defined (ALIAS) + { "progcomp_alias", &progcomp_alias, (shopt_set_func_t *)NULL }, +# endif +#endif + { "promptvars", &promptvars, (shopt_set_func_t *)NULL }, +#if defined (RESTRICTED_SHELL) + { "restricted_shell", &restricted_shell, set_restricted_shell }, +#endif + { "shift_verbose", &print_shift_error, (shopt_set_func_t *)NULL }, + { "sourcepath", &source_uses_path, (shopt_set_func_t *)NULL }, +#if defined (SYSLOG_HISTORY) && defined (SYSLOG_SHOPT) + { "syslog_history", &syslog_history, (shopt_set_func_t *)NULL }, +#endif + { "varredir_close", &varassign_redir_autoclose, (shopt_set_func_t *)NULL }, + { "xpg_echo", &xpg_echo, (shopt_set_func_t *)NULL }, + { (char *)0, (int *)0, (shopt_set_func_t *)NULL } +}; + +#define N_SHOPT_OPTIONS (sizeof (shopt_vars) / sizeof (shopt_vars[0])) + +#define GET_SHOPT_OPTION_VALUE(i) (*shopt_vars[i].value) + +static const char * const on = "on"; +static const char * const off = "off"; + +static int find_shopt PARAMS((char *)); +static int toggle_shopts PARAMS((int, WORD_LIST *, int)); +static void print_shopt PARAMS((char *, int, int)); +static int list_shopts PARAMS((WORD_LIST *, int)); +static int list_some_shopts PARAMS((int, int)); +static int list_shopt_o_options PARAMS((WORD_LIST *, int)); +static int list_some_o_options PARAMS((int, int)); +static int set_shopt_o_options PARAMS((int, WORD_LIST *, int)); + +#define SFLAG 0x01 +#define UFLAG 0x02 +#define QFLAG 0x04 +#define OFLAG 0x08 +#define PFLAG 0x10 + +int +shopt_builtin (list) + WORD_LIST *list; +{ + int opt, flags, rval; + + flags = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "psuoq")) != -1) + { + switch (opt) + { + case 's': + flags |= SFLAG; + break; + case 'u': + flags |= UFLAG; + break; + case 'q': + flags |= QFLAG; + break; + case 'o': + flags |= OFLAG; + break; + case 'p': + flags |= PFLAG; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + if ((flags & (SFLAG|UFLAG)) == (SFLAG|UFLAG)) + { + builtin_error (_("cannot set and unset shell options simultaneously")); + return (EXECUTION_FAILURE); + } + + rval = EXECUTION_SUCCESS; + if ((flags & OFLAG) && ((flags & (SFLAG|UFLAG)) == 0)) /* shopt -o */ + rval = list_shopt_o_options (list, flags); + else if (list && (flags & OFLAG)) /* shopt -so args */ + rval = set_shopt_o_options ((flags & SFLAG) ? FLAG_ON : FLAG_OFF, list, flags & QFLAG); + else if (flags & OFLAG) /* shopt -so */ + rval = list_some_o_options ((flags & SFLAG) ? 1 : 0, flags); + else if (list && (flags & (SFLAG|UFLAG))) /* shopt -su args */ + rval = toggle_shopts ((flags & SFLAG) ? SETOPT : UNSETOPT, list, flags & QFLAG); + else if ((flags & (SFLAG|UFLAG)) == 0) /* shopt [args] */ + rval = list_shopts (list, flags); + else /* shopt -su */ + rval = list_some_shopts ((flags & SFLAG) ? SETOPT : UNSETOPT, flags); + return (rval); +} + +/* Reset the options managed by `shopt' to the values they would have at + shell startup. Variables from shopt_vars. */ +void +reset_shopt_options () +{ + autocd = cdable_vars = cdspelling = 0; + check_hashed_filenames = CHECKHASH_DEFAULT; + check_window_size = CHECKWINSIZE_DEFAULT; + allow_null_glob_expansion = glob_dot_filenames = 0; + no_exit_on_failed_exec = 0; + expand_aliases = 0; + extended_quote = 1; + fail_glob_expansion = 0; + glob_asciirange = GLOBASCII_DEFAULT; + glob_star = 0; + gnu_error_format = 0; + hup_on_exit = 0; + inherit_errexit = 0; + interactive_comments = 1; + lastpipe_opt = 0; + localvar_inherit = localvar_unset = 0; + mail_warning = 0; + glob_ignore_case = match_ignore_case = 0; + print_shift_error = 0; + source_uses_path = promptvars = 1; + varassign_redir_autoclose = 0; + singlequote_translations = 0; + patsub_replacement = 1; + +#if defined (JOB_CONTROL) + check_jobs_at_exit = 0; +#endif + +#if defined (EXTENDED_GLOB) + extended_glob = EXTGLOB_DEFAULT; +#endif + +#if defined (ARRAY_VARS) + expand_once_flag = assoc_expand_once = 0; +#endif + +#if defined (HISTORY) + literal_history = 0; + force_append_history = 0; + command_oriented_history = 1; +#endif + +#if defined (SYSLOG_HISTORY) +# if defined (SYSLOG_SHOPT) + syslog_history = SYSLOG_SHOPT; +# else + syslog_history = 1; +# endif /* SYSLOG_SHOPT */ +#endif + +#if defined (READLINE) + complete_fullquote = 1; + force_fignore = 1; + hist_verify = history_reediting = 0; + perform_hostname_completion = 1; +# if DIRCOMPLETE_EXPAND_DEFAULT + dircomplete_expand = 1; +# else + dircomplete_expand = 0; +#endif + dircomplete_spelling = 0; + no_empty_command_completion = 0; +#endif + +#if defined (PROGRAMMABLE_COMPLETION) + prog_completion_enabled = 1; +# if defined (ALIAS) + progcomp_alias = 0; +# endif +#endif + +#if defined (DEFAULT_ECHO_TO_XPG) || defined (STRICT_POSIX) + xpg_echo = 1; +#else + xpg_echo = 0; +#endif /* DEFAULT_ECHO_TO_XPG */ + + shopt_login_shell = login_shell; +} + +static int +find_shopt (name) + char *name; +{ + int i; + + for (i = 0; shopt_vars[i].name; i++) + if (STREQ (name, shopt_vars[i].name)) + return i; + return -1; +} + +static void +shopt_error (s) + char *s; +{ + builtin_error (_("%s: invalid shell option name"), s); +} + +static int +toggle_shopts (mode, list, quiet) + int mode; + WORD_LIST *list; + int quiet; +{ + WORD_LIST *l; + int ind, rval; + SHELL_VAR *v; + + for (l = list, rval = EXECUTION_SUCCESS; l; l = l->next) + { + ind = find_shopt (l->word->word); + if (ind < 0) + { + shopt_error (l->word->word); + rval = EXECUTION_FAILURE; + } + else + { + *shopt_vars[ind].value = mode; /* 1 for set, 0 for unset */ + if (shopt_vars[ind].set_func) + (*shopt_vars[ind].set_func) (shopt_vars[ind].name, mode); + } + } + + /* Don't set $BASHOPTS here if it hasn't already been initialized */ + if (v = find_variable ("BASHOPTS")) + set_bashopts (); + return (rval); +} + +static void +print_shopt (name, val, flags) + char *name; + int val, flags; +{ + if (flags & PFLAG) + printf ("shopt %s %s\n", val ? "-s" : "-u", name); + else + printf (OPTFMT, name, val ? on : off); +} + +/* List the values of all or any of the `shopt' options. Returns 0 if + all were listed or all variables queried were on; 1 otherwise. */ +static int +list_shopts (list, flags) + WORD_LIST *list; + int flags; +{ + WORD_LIST *l; + int i, val, rval; + + if (list == 0) + { + for (i = 0; shopt_vars[i].name; i++) + { + val = *shopt_vars[i].value; + if ((flags & QFLAG) == 0) + print_shopt (shopt_vars[i].name, val, flags); + } + return (sh_chkwrite (EXECUTION_SUCCESS)); + } + + for (l = list, rval = EXECUTION_SUCCESS; l; l = l->next) + { + i = find_shopt (l->word->word); + if (i < 0) + { + shopt_error (l->word->word); + rval = EXECUTION_FAILURE; + continue; + } + val = *shopt_vars[i].value; + if (val == 0) + rval = EXECUTION_FAILURE; + if ((flags & QFLAG) == 0) + print_shopt (l->word->word, val, flags); + } + + return (sh_chkwrite (rval)); +} + +static int +list_some_shopts (mode, flags) + int mode, flags; +{ + int val, i; + + for (i = 0; shopt_vars[i].name; i++) + { + val = *shopt_vars[i].value; + if (((flags & QFLAG) == 0) && mode == val) + print_shopt (shopt_vars[i].name, val, flags); + } + return (sh_chkwrite (EXECUTION_SUCCESS)); +} + +static int +list_shopt_o_options (list, flags) + WORD_LIST *list; + int flags; +{ + WORD_LIST *l; + int val, rval; + + if (list == 0) + { + if ((flags & QFLAG) == 0) + list_minus_o_opts (-1, (flags & PFLAG)); + return (sh_chkwrite (EXECUTION_SUCCESS)); + } + + for (l = list, rval = EXECUTION_SUCCESS; l; l = l->next) + { + val = minus_o_option_value (l->word->word); + if (val == -1) + { + sh_invalidoptname (l->word->word); + rval = EXECUTION_FAILURE; + continue; + } + if (val == 0) + rval = EXECUTION_FAILURE; + if ((flags & QFLAG) == 0) + { + if (flags & PFLAG) + printf ("set %co %s\n", val ? '-' : '+', l->word->word); + else + printf (OPTFMT, l->word->word, val ? on : off); + } + } + return (sh_chkwrite (rval)); +} + +static int +list_some_o_options (mode, flags) + int mode, flags; +{ + if ((flags & QFLAG) == 0) + list_minus_o_opts (mode, (flags & PFLAG)); + return (sh_chkwrite (EXECUTION_SUCCESS)); +} + +static int +set_shopt_o_options (mode, list, quiet) + int mode; + WORD_LIST *list; + int quiet; +{ + WORD_LIST *l; + int rval; + + for (l = list, rval = EXECUTION_SUCCESS; l; l = l->next) + { + if (set_minus_o_option (mode, l->word->word) == EXECUTION_FAILURE) + rval = EXECUTION_FAILURE; + } + set_shellopts (); + return rval; +} + +/* If we set or unset interactive_comments with shopt, make sure the + change is reflected in $SHELLOPTS. */ +static int +set_shellopts_after_change (option_name, mode) + char *option_name; + int mode; +{ + set_shellopts (); + return (0); +} + +static int +shopt_set_debug_mode (option_name, mode) + char *option_name; + int mode; +{ +#if defined (DEBUGGER) + error_trace_mode = function_trace_mode = debugging_mode; + set_shellopts (); + if (debugging_mode) + init_bash_argv (); +#endif + return (0); +} + +#if defined (READLINE) +static int +shopt_enable_hostname_completion (option_name, mode) + char *option_name; + int mode; +{ + return (enable_hostname_completion (mode)); +} +#endif + +static int +set_compatibility_level (option_name, mode) + char *option_name; + int mode; +{ + int ind, oldval; + char *rhs; + + /* If we're unsetting one of the compatibility options, make sure the + current value is in the range of the compatNN space. */ + if (mode == 0) + oldval = shell_compatibility_level; + + /* If we're setting something, redo some of the work we did above in + toggle_shopt(). Unset everything and reset the appropriate option + based on OPTION_NAME. */ + if (mode) + { + shopt_compat31 = shopt_compat32 = 0; + shopt_compat40 = shopt_compat41 = shopt_compat42 = shopt_compat43 = 0; + shopt_compat44 = 0; + ind = find_shopt (option_name); + *shopt_vars[ind].value = mode; + } + + /* Then set shell_compatibility_level based on what remains */ + if (shopt_compat31) + shell_compatibility_level = 31; + else if (shopt_compat32) + shell_compatibility_level = 32; + else if (shopt_compat40) + shell_compatibility_level = 40; + else if (shopt_compat41) + shell_compatibility_level = 41; + else if (shopt_compat42) + shell_compatibility_level = 42; + else if (shopt_compat43) + shell_compatibility_level = 43; + else if (shopt_compat44) + shell_compatibility_level = 44; + else if (oldval > 44 && shell_compatibility_level < DEFAULT_COMPAT_LEVEL) + ; + else + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + + /* Make sure the current compatibility level is reflected in BASH_COMPAT */ + rhs = itos (shell_compatibility_level); + bind_variable ("BASH_COMPAT", rhs, 0); + free (rhs); + + return 0; +} + +/* Set and unset the various compatibility options from the value of + shell_compatibility_level; used by sv_shcompat */ +void +set_compatibility_opts () +{ + shopt_compat31 = shopt_compat32 = 0; + shopt_compat40 = shopt_compat41 = shopt_compat42 = shopt_compat43 = 0; + shopt_compat44 = 0; + switch (shell_compatibility_level) + { + case DEFAULT_COMPAT_LEVEL: + case 51: /* completeness */ + case 50: + break; + case 44: + shopt_compat44 = 1; break; + case 43: + shopt_compat43 = 1; break; + case 42: + shopt_compat42 = 1; break; + case 41: + shopt_compat41 = 1; break; + case 40: + shopt_compat40 = 1; break; + case 32: + shopt_compat32 = 1; break; + case 31: + shopt_compat31 = 1; break; + } +} + +#if defined (READLINE) +static int +shopt_set_complete_direxpand (option_name, mode) + char *option_name; + int mode; +{ + set_directory_hook (); + return 0; +} +#endif + +#if defined (RESTRICTED_SHELL) +/* Don't allow the value of restricted_shell to be modified. */ + +static int +set_restricted_shell (option_name, mode) + char *option_name; + int mode; +{ + static int save_restricted = -1; + + if (save_restricted == -1) + save_restricted = shell_is_restricted (shell_name); + + restricted_shell = save_restricted; + return (0); +} +#endif /* RESTRICTED_SHELL */ + +/* Not static so shell.c can call it to initialize shopt_login_shell */ +int +set_login_shell (option_name, mode) + char *option_name; + int mode; +{ + shopt_login_shell = login_shell != 0; + return (0); +} + +char ** +get_shopt_options () +{ + char **ret; + int n, i; + + n = sizeof (shopt_vars) / sizeof (shopt_vars[0]); + ret = strvec_create (n + 1); + for (i = 0; shopt_vars[i].name; i++) + ret[i] = savestring (shopt_vars[i].name); + ret[i] = (char *)NULL; + return ret; +} + +/* + * External interface for other parts of the shell. NAME is a string option; + * MODE is 0 if we want to unset an option; 1 if we want to set an option. + * REUSABLE is 1 if we want to print output in a form that may be reused. + */ +int +shopt_setopt (name, mode) + char *name; + int mode; +{ + WORD_LIST *wl; + int r; + + wl = add_string_to_list (name, (WORD_LIST *)NULL); + r = toggle_shopts (mode, wl, 0); + dispose_words (wl); + return r; +} + +int +shopt_listopt (name, reusable) + char *name; + int reusable; +{ + int i; + + if (name == 0) + return (list_shopts ((WORD_LIST *)NULL, reusable ? PFLAG : 0)); + + i = find_shopt (name); + if (i < 0) + { + shopt_error (name); + return (EXECUTION_FAILURE); + } + + print_shopt (name, *shopt_vars[i].value, reusable ? PFLAG : 0); + return (sh_chkwrite (EXECUTION_SUCCESS)); +} + +void +set_bashopts () +{ + char *value; + char tflag[N_SHOPT_OPTIONS]; + int vsize, i, vptr, *ip, exported; + SHELL_VAR *v; + + for (vsize = i = 0; shopt_vars[i].name; i++) + { + tflag[i] = 0; + if (GET_SHOPT_OPTION_VALUE (i)) + { + vsize += strlen (shopt_vars[i].name) + 1; + tflag[i] = 1; + } + } + + value = (char *)xmalloc (vsize + 1); + + for (i = vptr = 0; shopt_vars[i].name; i++) + { + if (tflag[i]) + { + strcpy (value + vptr, shopt_vars[i].name); + vptr += strlen (shopt_vars[i].name); + value[vptr++] = ':'; + } + } + + if (vptr) + vptr--; /* cut off trailing colon */ + value[vptr] = '\0'; + + v = find_variable ("BASHOPTS"); + + /* Turn off the read-only attribute so we can bind the new value, and + note whether or not the variable was exported. */ + if (v) + { + VUNSETATTR (v, att_readonly); + exported = exported_p (v); + } + else + exported = 0; + + v = bind_variable ("BASHOPTS", value, 0); + + /* Turn the read-only attribute back on, and turn off the export attribute + if it was set implicitly by mark_modified_vars and SHELLOPTS was not + exported before we bound the new value. */ + VSETATTR (v, att_readonly); + if (mark_modified_vars && exported == 0 && exported_p (v)) + VUNSETATTR (v, att_exported); + + free (value); +} + +void +parse_bashopts (value) + char *value; +{ + char *vname; + int vptr, ind; + + vptr = 0; + while (vname = extract_colon_unit (value, &vptr)) + { + ind = find_shopt (vname); + if (ind >= 0) + { + *shopt_vars[ind].value = 1; + if (shopt_vars[ind].set_func) + (*shopt_vars[ind].set_func) (shopt_vars[ind].name, 1); + } + free (vname); + } +} + +void +initialize_bashopts (no_bashopts) + int no_bashopts; +{ + char *temp; + SHELL_VAR *var; + + if (no_bashopts == 0) + { + var = find_variable ("BASHOPTS"); + /* set up any shell options we may have inherited. */ + if (var && imported_p (var)) + { + temp = (array_p (var) || assoc_p (var)) ? (char *)NULL : savestring (value_cell (var)); + if (temp) + { + parse_bashopts (temp); + free (temp); + } + } + } + + /* Set up the $BASHOPTS variable. */ + set_bashopts (); +} + +#if defined (ARRAY_VARS) +static int +set_assoc_expand (option_name, mode) + char *option_name; + int mode; +{ +#if 0 /* leave this disabled */ + if (shell_compatibility_level <= 51) +#endif + assoc_expand_once = expand_once_flag; + return 0; +} +#endif diff --git a/third_party/bash/builtins_source.c b/third_party/bash/builtins_source.c new file mode 100644 index 000000000..001b900da --- /dev/null +++ b/third_party/bash/builtins_source.c @@ -0,0 +1,154 @@ +/* source.c, created from source.def. */ +#line 22 "./source.def" + +#line 37 "./source.def" + +#line 53 "./source.def" + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" +#include "filecntl.h" +#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "flags.h" +#include "findcmd.h" +#include "common.h" +#include "bashgetopt.h" +#include "trap.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +static void maybe_pop_dollar_vars PARAMS((void)); + +/* If non-zero, `.' uses $PATH to look up the script to be sourced. */ +int source_uses_path = 1; + +/* If non-zero, `.' looks in the current directory if the filename argument + is not found in the $PATH. */ +int source_searches_cwd = 1; + +/* If this . script is supplied arguments, we save the dollar vars and + replace them with the script arguments for the duration of the script's + execution. If the script does not change the dollar vars, we restore + what we saved. If the dollar vars are changed in the script, and we are + not executing a shell function, we leave the new values alone and free + the saved values. */ +static void +maybe_pop_dollar_vars () +{ + if (variable_context == 0 && (dollar_vars_changed () & ARGS_SETBLTIN)) + dispose_saved_dollar_vars (); + else + pop_dollar_vars (); + if (debugging_mode) + pop_args (); /* restore BASH_ARGC and BASH_ARGV */ + set_dollar_vars_unchanged (); + invalidate_cached_quoted_dollar_at (); /* just invalidate to be safe */ +} + +/* Read and execute commands from the file passed as argument. Guess what. + This cannot be done in a subshell, since things like variable assignments + take place in there. So, I open the file, place it into a large string, + close the file, and then execute the string. */ +int +source_builtin (list) + WORD_LIST *list; +{ + int result; + char *filename, *debug_trap, *x; + + if (no_options (list)) + return (EX_USAGE); + list = loptend; + + if (list == 0) + { + builtin_error (_("filename argument required")); + builtin_usage (); + return (EX_USAGE); + } + +#if defined (RESTRICTED_SHELL) + if (restricted && strchr (list->word->word, '/')) + { + sh_restricted (list->word->word); + return (EXECUTION_FAILURE); + } +#endif + + filename = (char *)NULL; + /* XXX -- should this be absolute_pathname? */ + if (posixly_correct && strchr (list->word->word, '/')) + filename = savestring (list->word->word); + else if (absolute_pathname (list->word->word)) + filename = savestring (list->word->word); + else if (source_uses_path) + filename = find_path_file (list->word->word); + if (filename == 0) + { + if (source_searches_cwd == 0) + { + x = printable_filename (list->word->word, 0); + builtin_error (_("%s: file not found"), x); + if (x != list->word->word) + free (x); + if (posixly_correct && interactive_shell == 0 && executing_command_builtin == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + jump_to_top_level (EXITPROG); + } + return (EXECUTION_FAILURE); + } + else + filename = savestring (list->word->word); + } + + begin_unwind_frame ("source"); + add_unwind_protect (xfree, filename); + + if (list->next) + { + push_dollar_vars (); + add_unwind_protect ((Function *)maybe_pop_dollar_vars, (char *)NULL); + if (debugging_mode || shell_compatibility_level <= 44) + init_bash_argv (); /* Initialize BASH_ARGV and BASH_ARGC */ + remember_args (list->next, 1); + if (debugging_mode) + push_args (list->next); /* Update BASH_ARGV and BASH_ARGC */ + } + set_dollar_vars_unchanged (); + + /* Don't inherit the DEBUG trap unless function_trace_mode (overloaded) + is set. XXX - should sourced files inherit the RETURN trap? Functions + don't. */ + debug_trap = TRAP_STRING (DEBUG_TRAP); + if (debug_trap && function_trace_mode == 0) + { + debug_trap = savestring (debug_trap); + add_unwind_protect (xfree, debug_trap); + add_unwind_protect (maybe_set_debug_trap, debug_trap); + restore_default_signal (DEBUG_TRAP); + } + + result = source_file (filename, (list && list->next)); + + run_unwind_frame ("source"); + + return (result); +} diff --git a/third_party/bash/builtins_suspend.c b/third_party/bash/builtins_suspend.c new file mode 100644 index 000000000..fe96213f3 --- /dev/null +++ b/third_party/bash/builtins_suspend.c @@ -0,0 +1,94 @@ +/* suspend.c, created from suspend.def. */ +#line 22 "./suspend.def" + +#line 40 "./suspend.def" + +#include "config.h" + +#if defined (JOB_CONTROL) +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashtypes.h" +#include +#include "bashintl.h" +#include "shell.h" +#include "jobs.h" +#include "common.h" +#include "bashgetopt.h" + +static sighandler suspend_continue PARAMS((int)); + +static SigHandler *old_cont; +#if 0 +static SigHandler *old_stop; +#endif + +/* Continue handler. */ +static sighandler +suspend_continue (sig) + int sig; +{ + set_signal_handler (SIGCONT, old_cont); +#if 0 + set_signal_handler (SIGSTOP, old_stop); +#endif + SIGRETURN (0); +} + +/* Suspending the shell. If -f is the arg, then do the suspend + no matter what. Otherwise, complain if a login shell. */ +int +suspend_builtin (list) + WORD_LIST *list; +{ + int opt, force; + + reset_internal_getopt (); + force = 0; + while ((opt = internal_getopt (list, "f")) != -1) + switch (opt) + { + case 'f': + force++; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + + list = loptend; + no_args (list); + + if (force == 0) + { + if (job_control == 0) + { + sh_nojobs (_("cannot suspend")); + return (EXECUTION_FAILURE); + } + + if (login_shell) + { + builtin_error (_("cannot suspend a login shell")); + return (EXECUTION_FAILURE); + } + } + + /* XXX - should we put ourselves back into the original pgrp now? If so, + call end_job_control() here and do the right thing in suspend_continue + (that is, call restart_job_control()). */ + old_cont = (SigHandler *)set_signal_handler (SIGCONT, suspend_continue); +#if 0 + old_stop = (SigHandler *)set_signal_handler (SIGSTOP, SIG_DFL); +#endif + killpg (shell_pgrp, SIGSTOP); + return (EXECUTION_SUCCESS); +} + +#endif /* JOB_CONTROL */ diff --git a/third_party/bash/builtins_test.c b/third_party/bash/builtins_test.c new file mode 100644 index 000000000..05f757d68 --- /dev/null +++ b/third_party/bash/builtins_test.c @@ -0,0 +1,52 @@ +/* test.c, created from test.def. */ +#line 22 "./test.def" + +#line 104 "./test.def" + +#line 114 "./test.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "test.h" +#include "common.h" + +/* TEST/[ builtin. */ +int +test_builtin (list) + WORD_LIST *list; +{ + char **argv; + int argc, result; + + /* We let Matthew Bradburn and Kevin Braunsdorf's code do the + actual test command. So turn the list of args into an array + of strings, since that is what their code wants. */ + if (list == 0) + { + if (this_command_name[0] == '[' && !this_command_name[1]) + { + builtin_error (_("missing `]'")); + return (EX_BADUSAGE); + } + + return (EXECUTION_FAILURE); + } + + argv = make_builtin_argv (list, &argc); + result = test_command (argc, argv); + free ((char *)argv); + + return (result); +} diff --git a/third_party/bash/builtins_times.c b/third_party/bash/builtins_times.c new file mode 100644 index 000000000..34de78d61 --- /dev/null +++ b/third_party/bash/builtins_times.c @@ -0,0 +1,90 @@ +/* times.c, created from times.def. */ +#line 22 "./times.def" + +#line 34 "./times.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "bashtypes.h" +#include "shell.h" + +#include "posixtime.h" + +#if defined (HAVE_SYS_TIMES_H) +# include +#endif /* HAVE_SYS_TIMES_H */ + +#if defined (HAVE_SYS_RESOURCE_H) && !defined (RLIMTYPE) +# include +#endif + +#include "common.h" + +/* Print the totals for system and user time used. */ +int +times_builtin (list) + WORD_LIST *list; +{ +#if defined (HAVE_GETRUSAGE) && defined (HAVE_TIMEVAL) && defined (RUSAGE_SELF) + struct rusage self, kids; + + USE_VAR(list); + + if (no_options (list)) + return (EX_USAGE); + + getrusage (RUSAGE_SELF, &self); + getrusage (RUSAGE_CHILDREN, &kids); /* terminated child processes */ + + print_timeval (stdout, &self.ru_utime); + putchar (' '); + print_timeval (stdout, &self.ru_stime); + putchar ('\n'); + print_timeval (stdout, &kids.ru_utime); + putchar (' '); + print_timeval (stdout, &kids.ru_stime); + putchar ('\n'); + +#else +# if defined (HAVE_TIMES) + /* This uses the POSIX.1/XPG5 times(2) interface, which fills in a + `struct tms' with values of type clock_t. */ + struct tms t; + + USE_VAR(list); + + if (no_options (list)) + return (EX_USAGE); + + times (&t); + + print_clock_t (stdout, t.tms_utime); + putchar (' '); + print_clock_t (stdout, t.tms_stime); + putchar ('\n'); + print_clock_t (stdout, t.tms_cutime); + putchar (' '); + print_clock_t (stdout, t.tms_cstime); + putchar ('\n'); + +# else /* !HAVE_TIMES */ + + USE_VAR(list); + + if (no_options (list)) + return (EX_USAGE); + printf ("0.00 0.00\n0.00 0.00\n"); + +# endif /* HAVE_TIMES */ +#endif /* !HAVE_TIMES */ + + return (sh_chkwrite (EXECUTION_SUCCESS)); +} diff --git a/third_party/bash/builtins_trap.c b/third_party/bash/builtins_trap.c new file mode 100644 index 000000000..6d150a406 --- /dev/null +++ b/third_party/bash/builtins_trap.c @@ -0,0 +1,263 @@ +/* trap.c, created from trap.def. */ +#line 22 "./trap.def" + +#line 58 "./trap.def" + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashtypes.h" +#include +#include +#include "bashansi.h" + +#include "shell.h" +#include "trap.h" +#include "common.h" +#include "bashgetopt.h" + +static void showtrap PARAMS((int, int)); +static int display_traps PARAMS((WORD_LIST *, int)); + +/* The trap command: + + trap + trap + trap -l + trap -p [sigspec ...] + trap [--] + + Set things up so that ARG is executed when SIGNAL(s) N is received. + If ARG is the empty string, then ignore the SIGNAL(s). If there is + no ARG, then set the trap for SIGNAL(s) to its original value. Just + plain "trap" means to print out the list of commands associated with + each signal number. Single arg of "-l" means list the signal names. */ + +/* Possible operations to perform on the list of signals.*/ +#define SET 0 /* Set this signal to first_arg. */ +#define REVERT 1 /* Revert to this signals original value. */ +#define IGNORE 2 /* Ignore this signal. */ + +int +trap_builtin (list) + WORD_LIST *list; +{ + int list_signal_names, display, result, opt; + + list_signal_names = display = 0; + result = EXECUTION_SUCCESS; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "lp")) != -1) + { + switch (opt) + { + case 'l': + list_signal_names++; + break; + case 'p': + display++; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + opt = DSIG_NOCASE|DSIG_SIGPREFIX; /* flags for decode_signal */ + + if (list_signal_names) + return (sh_chkwrite (display_signal_list ((WORD_LIST *)NULL, 1))); + else if (display || list == 0) + { + initialize_terminating_signals (); + get_all_original_signals (); + return (sh_chkwrite (display_traps (list, display && posixly_correct))); + } + else + { + char *first_arg; + int operation, sig, first_signal; + + operation = SET; + first_arg = list->word->word; + first_signal = first_arg && *first_arg && all_digits (first_arg) && signal_object_p (first_arg, opt); + + /* Backwards compatibility. XXX - question about whether or not we + should throw an error if an all-digit argument doesn't correspond + to a valid signal number (e.g., if it's `50' on a system with only + 32 signals). */ + if (first_signal) + operation = REVERT; + /* When in posix mode, the historical behavior of looking for a + missing first argument is disabled. To revert to the original + signal handling disposition, use `-' as the first argument. */ + else if (posixly_correct == 0 && first_arg && *first_arg && + (*first_arg != '-' || first_arg[1]) && + signal_object_p (first_arg, opt) && list->next == 0) + operation = REVERT; + else + { + list = list->next; + if (list == 0) + { + builtin_usage (); + return (EX_USAGE); + } + else if (*first_arg == '\0') + operation = IGNORE; + else if (first_arg[0] == '-' && !first_arg[1]) + operation = REVERT; + } + + /* If we're in a command substitution, we haven't freed the trap strings + (though we reset the signal handlers). If we're setting a trap to + handle a signal here, free the rest of the trap strings since they + don't apply any more. */ + if (subshell_environment & SUBSHELL_RESETTRAP) + { + free_trap_strings (); + subshell_environment &= ~SUBSHELL_RESETTRAP; + } + + while (list) + { + sig = decode_signal (list->word->word, opt); + + if (sig == NO_SIG) + { + sh_invalidsig (list->word->word); + result = EXECUTION_FAILURE; + } + else + { + switch (operation) + { + case SET: + set_signal (sig, first_arg); + break; + + case REVERT: + restore_default_signal (sig); + + /* Signals that the shell treats specially need special + handling. */ + switch (sig) + { + case SIGINT: + /* XXX - should we do this if original disposition + was SIG_IGN? */ + if (interactive) + set_signal_handler (SIGINT, sigint_sighandler); + /* special cases for interactive == 0 */ + else if (interactive_shell && (sourcelevel||running_trap||parse_and_execute_level)) + set_signal_handler (SIGINT, sigint_sighandler); + else + set_signal_handler (SIGINT, termsig_sighandler); + break; + + case SIGQUIT: + /* Always ignore SIGQUIT. */ + set_signal_handler (SIGQUIT, SIG_IGN); + break; + case SIGTERM: +#if defined (JOB_CONTROL) + case SIGTTIN: + case SIGTTOU: + case SIGTSTP: +#endif /* JOB_CONTROL */ + if (interactive) + set_signal_handler (sig, SIG_IGN); + break; + } + break; + + case IGNORE: + ignore_signal (sig); + break; + } + } + list = list->next; + } + } + + return (result); +} + +static void +showtrap (i, show_default) + int i, show_default; +{ + char *t, *p, *sn; + int free_t; + + free_t = 1; + p = trap_list[i]; + if (p == (char *)DEFAULT_SIG && signal_is_hard_ignored (i) == 0) + { + if (show_default) + t = "-"; + else + return; + free_t = 0; + } + else if (signal_is_hard_ignored (i)) + t = (char *)NULL; + else + t = (p == (char *)IGNORE_SIG) ? (char *)NULL : sh_single_quote (p); + + sn = signal_name (i); + /* Make sure that signals whose names are unknown (for whatever reason) + are printed as signal numbers. */ + if (STREQN (sn, "SIGJUNK", 7) || STREQN (sn, "unknown", 7)) + printf ("trap -- %s %d\n", t ? t : "''", i); + else if (posixly_correct) + { + if (STREQN (sn, "SIG", 3)) + printf ("trap -- %s %s\n", t ? t : "''", sn+3); + else + printf ("trap -- %s %s\n", t ? t : "''", sn); + } + else + printf ("trap -- %s %s\n", t ? t : "''", sn); + + if (free_t) + FREE (t); +} + +static int +display_traps (list, show_all) + WORD_LIST *list; + int show_all; +{ + int result, i; + + if (list == 0) + { + for (i = 0; i < BASH_NSIG; i++) + showtrap (i, show_all); + return (EXECUTION_SUCCESS); + } + + for (result = EXECUTION_SUCCESS; list; list = list->next) + { + i = decode_signal (list->word->word, DSIG_NOCASE|DSIG_SIGPREFIX); + if (i == NO_SIG) + { + sh_invalidsig (list->word->word); + result = EXECUTION_FAILURE; + } + else + showtrap (i, show_all); + } + + return (result); +} diff --git a/third_party/bash/builtins_type.c b/third_party/bash/builtins_type.c new file mode 100644 index 000000000..d897d6649 --- /dev/null +++ b/third_party/bash/builtins_type.c @@ -0,0 +1,373 @@ +/* type.c, created from type.def. */ +#line 22 "./type.def" + +#line 52 "./type.def" + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "parser.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "hashcmd.h" + +#if defined (ALIAS) +#include "alias.h" +#endif /* ALIAS */ + +#include "common.h" +#include "bashgetopt.h" + +extern int find_reserved_word PARAMS((char *)); + +/* For each word in LIST, find out what the shell is going to do with + it as a simple command. i.e., which file would this shell use to + execve, or if it is a builtin command, or an alias. Possible flag + arguments: + -t Returns the "type" of the object, one of + `alias', `keyword', `function', `builtin', + or `file'. + + -p Returns the pathname of the file if -type is + a file. + + -a Returns all occurrences of words, whether they + be a filename in the path, alias, function, + or builtin. + + -f Suppress shell function lookup, like `command'. + + -P Force a path search even in the presence of other + definitions. + + Order of evaluation: + alias + keyword + function + builtin + file + */ + +int +type_builtin (list) + WORD_LIST *list; +{ + int dflags, any_failed, opt; + WORD_LIST *this; + + if (list == 0) + return (EXECUTION_SUCCESS); + + dflags = CDESC_SHORTDESC; /* default */ + any_failed = 0; + + /* Handle the obsolescent `-type', `-path', and `-all' by prescanning + the arguments and converting those options to the form that + internal_getopt recognizes. Converts `--type', `--path', and `--all' + also. THIS SHOULD REALLY GO AWAY. */ + for (this = list; this && this->word->word[0] == '-'; this = this->next) + { + char *flag = &(this->word->word[1]); + + if (STREQ (flag, "type") || STREQ (flag, "-type")) + { + this->word->word[1] = 't'; + this->word->word[2] = '\0'; + } + else if (STREQ (flag, "path") || STREQ (flag, "-path")) + { + this->word->word[1] = 'p'; + this->word->word[2] = '\0'; + } + else if (STREQ (flag, "all") || STREQ (flag, "-all")) + { + this->word->word[1] = 'a'; + this->word->word[2] = '\0'; + } + } + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "afptP")) != -1) + { + switch (opt) + { + case 'a': + dflags |= CDESC_ALL; + break; + case 'f': + dflags |= CDESC_NOFUNCS; + break; + case 'p': + dflags |= CDESC_PATH_ONLY; + dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC); + break; + case 't': + dflags |= CDESC_TYPE; + dflags &= ~(CDESC_PATH_ONLY|CDESC_SHORTDESC); + break; + case 'P': /* shorthand for type -ap */ + dflags |= (CDESC_PATH_ONLY|CDESC_FORCE_PATH); + dflags &= ~(CDESC_TYPE|CDESC_SHORTDESC); + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + while (list) + { + int found; + + found = describe_command (list->word->word, dflags); + + if (!found && (dflags & (CDESC_PATH_ONLY|CDESC_TYPE)) == 0) + sh_notfound (list->word->word); + + any_failed += found == 0; + list = list->next; + } + + opt = (any_failed == 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; + return (sh_chkwrite (opt)); +} + +/* + * Describe COMMAND as required by the type and command builtins. + * + * Behavior is controlled by DFLAGS. Flag values are + * CDESC_ALL print all descriptions of a command + * CDESC_SHORTDESC print the description for type and command -V + * CDESC_REUSABLE print in a format that may be reused as input + * CDESC_TYPE print the type for type -t + * CDESC_PATH_ONLY print the path for type -p + * CDESC_FORCE_PATH force a path search for type -P + * CDESC_NOFUNCS skip function lookup for type -f + * CDESC_ABSPATH convert to absolute path, no ./ prefix + * CDESC_STDPATH command -p standard path list + * + * CDESC_ALL says whether or not to look for all occurrences of COMMAND, or + * return after finding it once. + */ +int +describe_command (command, dflags) + char *command; + int dflags; +{ + int found, i, found_file, f, all; + char *full_path, *x, *pathlist; + SHELL_VAR *func; +#if defined (ALIAS) + alias_t *alias; +#endif + + all = (dflags & CDESC_ALL) != 0; + found = found_file = 0; + full_path = (char *)NULL; + +#if defined (ALIAS) + /* Command is an alias? */ + if (((dflags & CDESC_FORCE_PATH) == 0) && expand_aliases && (alias = find_alias (command))) + { + if (dflags & CDESC_TYPE) + puts ("alias"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is aliased to `%s'\n"), command, alias->value); + else if (dflags & CDESC_REUSABLE) + { + x = sh_single_quote (alias->value); + printf ("alias %s=%s\n", command, x); + free (x); + } + + found = 1; + + if (all == 0) + return (1); + } +#endif /* ALIAS */ + + /* Command is a shell reserved word? */ + if (((dflags & CDESC_FORCE_PATH) == 0) && (i = find_reserved_word (command)) >= 0) + { + if (dflags & CDESC_TYPE) + puts ("keyword"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is a shell keyword\n"), command); + else if (dflags & CDESC_REUSABLE) + printf ("%s\n", command); + + found = 1; + + if (all == 0) + return (1); + } + + /* Command is a function? */ + if (((dflags & (CDESC_FORCE_PATH|CDESC_NOFUNCS)) == 0) && (func = find_function (command))) + { + if (dflags & CDESC_TYPE) + puts ("function"); + else if (dflags & CDESC_SHORTDESC) + { + char *result; + + printf (_("%s is a function\n"), command); + + /* We're blowing away THE_PRINTED_COMMAND here... */ + + result = named_function_string (command, function_cell (func), FUNC_MULTILINE|FUNC_EXTERNAL); + printf ("%s\n", result); + } + else if (dflags & CDESC_REUSABLE) + printf ("%s\n", command); + + found = 1; + + if (all == 0) + return (1); + } + + /* Command is a builtin? */ + if (((dflags & CDESC_FORCE_PATH) == 0) && find_shell_builtin (command)) + { + if (dflags & CDESC_TYPE) + puts ("builtin"); + else if (dflags & CDESC_SHORTDESC) + { + if (posixly_correct && find_special_builtin (command) != 0) + printf (_("%s is a special shell builtin\n"), command); + else + printf (_("%s is a shell builtin\n"), command); + } + else if (dflags & CDESC_REUSABLE) + printf ("%s\n", command); + + found = 1; + + if (all == 0) + return (1); + } + + /* Command is a disk file? */ + /* If the command name given is already an absolute command, just + check to see if it is executable. */ + if (absolute_program (command)) + { + f = file_status (command); + if (f & FS_EXECABLE) + { + if (dflags & CDESC_TYPE) + puts ("file"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is %s\n"), command, command); + else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY)) + printf ("%s\n", command); + + /* There's no use looking in the hash table or in $PATH, + because they're not consulted when an absolute program + name is supplied. */ + return (1); + } + } + + /* If the user isn't doing "-a", then we might care about + whether the file is present in our hash table. */ + if (all == 0 || (dflags & CDESC_FORCE_PATH)) + { + if (full_path = phash_search (command)) + { + if (dflags & CDESC_TYPE) + puts ("file"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is hashed (%s)\n"), command, full_path); + else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY)) + printf ("%s\n", full_path); + + free (full_path); + return (1); + } + } + + /* Now search through $PATH. */ + while (1) + { + if (dflags & CDESC_STDPATH) /* command -p, all cannot be non-zero */ + { + pathlist = conf_standard_path (); + full_path = find_in_path (command, pathlist, FS_EXEC_PREFERRED|FS_NODIRS); + free (pathlist); + /* Will only go through this once, since all == 0 if STDPATH set */ + } + else if (all == 0) + full_path = find_user_command (command); + else + full_path = user_command_matches (command, FS_EXEC_ONLY, found_file); /* XXX - should that be FS_EXEC_PREFERRED? */ + + if (full_path == 0) + break; + + /* If we found the command as itself by looking through $PATH, it + probably doesn't exist. Check whether or not the command is an + executable file. If it's not, don't report a match. This is + the default posix mode behavior */ + if (STREQ (full_path, command) || posixly_correct) + { + f = file_status (full_path); + if ((f & FS_EXECABLE) == 0) + { + free (full_path); + full_path = (char *)NULL; + if (all == 0) + break; + } + else if (ABSPATH (full_path)) + ; /* placeholder; don't need to do anything yet */ + else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY|CDESC_SHORTDESC)) + { + f = MP_DOCWD | ((dflags & CDESC_ABSPATH) ? MP_RMDOT : 0); + x = sh_makepath ((char *)NULL, full_path, f); + free (full_path); + full_path = x; + } + } + /* If we require a full path and don't have one, make one */ + else if ((dflags & CDESC_ABSPATH) && ABSPATH (full_path) == 0) + { + x = sh_makepath ((char *)NULL, full_path, MP_DOCWD|MP_RMDOT); + free (full_path); + full_path = x; + } + + found_file++; + found = 1; + + if (dflags & CDESC_TYPE) + puts ("file"); + else if (dflags & CDESC_SHORTDESC) + printf (_("%s is %s\n"), command, full_path); + else if (dflags & (CDESC_REUSABLE|CDESC_PATH_ONLY)) + printf ("%s\n", full_path); + + free (full_path); + full_path = (char *)NULL; + + if (all == 0) + break; + } + + return (found); +} diff --git a/third_party/bash/builtins_ulimit.c b/third_party/bash/builtins_ulimit.c new file mode 100644 index 000000000..445412b93 --- /dev/null +++ b/third_party/bash/builtins_ulimit.c @@ -0,0 +1,741 @@ +/* ulimit.c, created from ulimit.def. */ +#line 22 "./ulimit.def" + +#line 73 "./ulimit.def" + +#if !defined (_MINIX) + +#include "config.h" + +#include "bashtypes.h" +#if defined (HAVE_SYS_PARAM_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include + +#include "bashintl.h" + +#include "shell.h" +#include "common.h" +#include "bashgetopt.h" +#include "pipesize.h" + +#if !defined (errno) +extern int errno; +#endif + +/* For some reason, HPUX chose to make these definitions visible only if + _KERNEL is defined, so we define _KERNEL before including + and #undef it afterward. */ +#if defined (HAVE_RESOURCE) +# include +# if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL) +# define _KERNEL +# endif +# include +# if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL) +# undef _KERNEL +# endif +#elif defined (HAVE_SYS_TIMES_H) +# include +#endif + +#if defined (HAVE_LIMITS_H) +# include +#endif + +/* Check for the most basic symbols. If they aren't present, this + system's isn't very useful to us. */ +#if !defined (RLIMIT_FSIZE) || !defined (HAVE_GETRLIMIT) +# undef HAVE_RESOURCE +#endif + +#if !defined (HAVE_RESOURCE) && defined (HAVE_ULIMIT_H) +# include +#endif + +#if !defined (RLIMTYPE) +# define RLIMTYPE long +# define string_to_rlimtype(s) strtol(s, (char **)NULL, 10) +# define print_rlimtype(num, nl) printf ("%ld%s", num, nl ? "\n" : "") +#endif + +/* Alternate names */ + +/* Some systems use RLIMIT_NOFILE, others use RLIMIT_OFILE */ +#if defined (HAVE_RESOURCE) && defined (RLIMIT_OFILE) && !defined (RLIMIT_NOFILE) +# define RLIMIT_NOFILE RLIMIT_OFILE +#endif /* HAVE_RESOURCE && RLIMIT_OFILE && !RLIMIT_NOFILE */ + +#if defined (HAVE_RESOURCE) && defined (RLIMIT_POSIXLOCKS) && !defined (RLIMIT_LOCKS) +# define RLIMIT_LOCKS RLIMIT_POSIXLOCKS +#endif /* HAVE_RESOURCE && RLIMIT_POSIXLOCKS && !RLIMIT_LOCKS */ + +/* Some systems have these, some do not. */ +#ifdef RLIMIT_FSIZE +# define RLIMIT_FILESIZE RLIMIT_FSIZE +#else +# define RLIMIT_FILESIZE 256 +#endif + +#define RLIMIT_PIPESIZE 257 + +#ifdef RLIMIT_NOFILE +# define RLIMIT_OPENFILES RLIMIT_NOFILE +#else +# define RLIMIT_OPENFILES 258 +#endif + +#ifdef RLIMIT_VMEM +# define RLIMIT_VIRTMEM RLIMIT_VMEM +# define RLIMIT_VMBLKSZ 1024 +#else +# ifdef RLIMIT_AS +# define RLIMIT_VIRTMEM RLIMIT_AS +# define RLIMIT_VMBLKSZ 1024 +# else +# define RLIMIT_VIRTMEM 259 +# define RLIMIT_VMBLKSZ 1 +# endif +#endif + +#ifdef RLIMIT_NPROC +# define RLIMIT_MAXUPROC RLIMIT_NPROC +#else +# define RLIMIT_MAXUPROC 260 +#endif + +#if !defined (RLIMIT_PTHREAD) && defined (RLIMIT_NTHR) +# define RLIMIT_PTHREAD RLIMIT_NTHR +#endif + +#if !defined (RLIM_INFINITY) +# define RLIM_INFINITY 0x7fffffff +#endif + +#if !defined (RLIM_SAVED_CUR) +# define RLIM_SAVED_CUR RLIM_INFINITY +#endif + +#if !defined (RLIM_SAVED_MAX) +# define RLIM_SAVED_MAX RLIM_INFINITY +#endif + +#define LIMIT_HARD 0x01 +#define LIMIT_SOFT 0x02 + +/* "Blocks" are defined as 512 bytes when in Posix mode and 1024 bytes + otherwise. */ +#define POSIXBLK -2 + +#define BLOCKSIZE(x) (((x) == POSIXBLK) ? (posixly_correct ? 512 : 1024) : (x)) + +static int _findlim PARAMS((int)); + +static int ulimit_internal PARAMS((int, char *, int, int)); + +static int get_limit PARAMS((int, RLIMTYPE *, RLIMTYPE *)); +static int set_limit PARAMS((int, RLIMTYPE, int)); + +static void printone PARAMS((int, RLIMTYPE, int)); +static void print_all_limits PARAMS((int)); + +static int set_all_limits PARAMS((int, RLIMTYPE)); + +static int filesize PARAMS((RLIMTYPE *)); +static int pipesize PARAMS((RLIMTYPE *)); +static int getmaxuprc PARAMS((RLIMTYPE *)); +static int getmaxvm PARAMS((RLIMTYPE *, RLIMTYPE *)); + +typedef struct { + int option; /* The ulimit option for this limit. */ + int parameter; /* Parameter to pass to get_limit (). */ + int block_factor; /* Blocking factor for specific limit. */ + const char * const description; /* Descriptive string to output. */ + const char * const units; /* scale */ +} RESOURCE_LIMITS; + +static RESOURCE_LIMITS limits[] = { +#ifdef RLIMIT_NPTS + { 'P', RLIMIT_NPTS, 1, "number of pseudoterminals", (char *)NULL }, +#endif +#ifdef RLIMIT_RTTIME + { 'R', RLIMIT_RTTIME, 1, "real-time non-blocking time", "microseconds" }, +#endif +#ifdef RLIMIT_PTHREAD + { 'T', RLIMIT_PTHREAD, 1, "number of threads", (char *)NULL }, +#endif +#ifdef RLIMIT_SBSIZE + { 'b', RLIMIT_SBSIZE, 1, "socket buffer size", "bytes" }, +#endif +#ifdef RLIMIT_CORE + { 'c', RLIMIT_CORE, POSIXBLK, "core file size", "blocks" }, +#endif +#ifdef RLIMIT_DATA + { 'd', RLIMIT_DATA, 1024, "data seg size", "kbytes" }, +#endif +#ifdef RLIMIT_NICE + { 'e', RLIMIT_NICE, 1, "scheduling priority", (char *)NULL }, +#endif + { 'f', RLIMIT_FILESIZE, POSIXBLK, "file size", "blocks" }, +#ifdef RLIMIT_SIGPENDING + { 'i', RLIMIT_SIGPENDING, 1, "pending signals", (char *)NULL }, +#endif +#ifdef RLIMIT_KQUEUES + { 'k', RLIMIT_KQUEUES, 1, "max kqueues", (char *)NULL }, +#endif +#ifdef RLIMIT_MEMLOCK + { 'l', RLIMIT_MEMLOCK, 1024, "max locked memory", "kbytes" }, +#endif +#ifdef RLIMIT_RSS + { 'm', RLIMIT_RSS, 1024, "max memory size", "kbytes" }, +#endif /* RLIMIT_RSS */ + { 'n', RLIMIT_OPENFILES, 1, "open files", (char *)NULL}, + { 'p', RLIMIT_PIPESIZE, 512, "pipe size", "512 bytes" }, +#ifdef RLIMIT_MSGQUEUE + { 'q', RLIMIT_MSGQUEUE, 1, "POSIX message queues", "bytes" }, +#endif +#ifdef RLIMIT_RTPRIO + { 'r', RLIMIT_RTPRIO, 1, "real-time priority", (char *)NULL }, +#endif +#ifdef RLIMIT_STACK + { 's', RLIMIT_STACK, 1024, "stack size", "kbytes" }, +#endif +#ifdef RLIMIT_CPU + { 't', RLIMIT_CPU, 1, "cpu time", "seconds" }, +#endif /* RLIMIT_CPU */ + { 'u', RLIMIT_MAXUPROC, 1, "max user processes", (char *)NULL }, +#if defined (HAVE_RESOURCE) + { 'v', RLIMIT_VIRTMEM, RLIMIT_VMBLKSZ, "virtual memory", "kbytes" }, +#endif +#ifdef RLIMIT_SWAP + { 'w', RLIMIT_SWAP, 1024, "swap size", "kbytes" }, +#endif +#ifdef RLIMIT_LOCKS + { 'x', RLIMIT_LOCKS, 1, "file locks", (char *)NULL }, +#endif + { -1, -1, -1, (char *)NULL, (char *)NULL } +}; +#define NCMDS (sizeof(limits) / sizeof(limits[0])) + +typedef struct _cmd { + int cmd; + char *arg; +} ULCMD; + +static ULCMD *cmdlist; +static int ncmd; +static int cmdlistsz; + +#if !defined (HAVE_RESOURCE) && !defined (HAVE_ULIMIT) +long +ulimit (cmd, newlim) + int cmd; + long newlim; +{ + errno = EINVAL; + return -1; +} +#endif /* !HAVE_RESOURCE && !HAVE_ULIMIT */ + +static int +_findlim (opt) + int opt; +{ + register int i; + + for (i = 0; limits[i].option > 0; i++) + if (limits[i].option == opt) + return i; + return -1; +} + +static char optstring[4 + 2 * NCMDS]; + +/* Report or set limits associated with certain per-process resources. + See the help documentation in builtins.c for a full description. */ +int +ulimit_builtin (list) + register WORD_LIST *list; +{ + register char *s; + int c, limind, mode, opt, all_limits; + + mode = 0; + + all_limits = 0; + + /* Idea stolen from pdksh -- build option string the first time called. */ + if (optstring[0] == 0) + { + s = optstring; + *s++ = 'a'; *s++ = 'S'; *s++ = 'H'; + for (c = 0; limits[c].option > 0; c++) + { + *s++ = limits[c].option; + *s++ = ';'; + } + *s = '\0'; + } + + /* Initialize the command list. */ + if (cmdlistsz == 0) + cmdlist = (ULCMD *)xmalloc ((cmdlistsz = 16) * sizeof (ULCMD)); + ncmd = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, optstring)) != -1) + { + switch (opt) + { + case 'a': + all_limits++; + break; + + /* -S and -H are modifiers, not real options. */ + case 'S': + mode |= LIMIT_SOFT; + break; + + case 'H': + mode |= LIMIT_HARD; + break; + + CASE_HELPOPT; + case '?': + builtin_usage (); + return (EX_USAGE); + + default: + if (ncmd >= cmdlistsz) + cmdlist = (ULCMD *)xrealloc (cmdlist, (cmdlistsz *= 2) * sizeof (ULCMD)); + cmdlist[ncmd].cmd = opt; + cmdlist[ncmd++].arg = list_optarg; + break; + } + } + list = loptend; + + if (all_limits) + { +#ifdef NOTYET + if (list) /* setting */ + { + if (STREQ (list->word->word, "unlimited") == 0) + { + builtin_error (_("%s: invalid limit argument"), list->word->word); + return (EXECUTION_FAILURE); + } + return (set_all_limits (mode == 0 ? LIMIT_SOFT|LIMIT_HARD : mode, RLIM_INFINITY)); + } +#endif + print_all_limits (mode == 0 ? LIMIT_SOFT : mode); + return (sh_chkwrite (EXECUTION_SUCCESS)); + } + + /* default is `ulimit -f' */ + if (ncmd == 0) + { + cmdlist[ncmd].cmd = 'f'; + /* `ulimit something' is same as `ulimit -f something' */ + cmdlist[ncmd++].arg = list ? list->word->word : (char *)NULL; + if (list) + list = list->next; + } + + /* verify each command in the list. */ + for (c = 0; c < ncmd; c++) + { + limind = _findlim (cmdlist[c].cmd); + if (limind == -1) + { + builtin_error (_("`%c': bad command"), cmdlist[c].cmd); + return (EX_USAGE); + } + } + + /* POSIX compatibility. If the last item in cmdlist does not have an option + argument, but there is an operand (list != 0), treat the operand as if + it were an option argument for that last command. */ + if (list && list->word && cmdlist[ncmd - 1].arg == 0) + { + cmdlist[ncmd - 1].arg = list->word->word; + list = list->next; + } + + for (c = 0; c < ncmd; c++) + if (ulimit_internal (cmdlist[c].cmd, cmdlist[c].arg, mode, ncmd > 1) == EXECUTION_FAILURE) + return (EXECUTION_FAILURE); + + return (EXECUTION_SUCCESS); +} + +static int +ulimit_internal (cmd, cmdarg, mode, multiple) + int cmd; + char *cmdarg; + int mode, multiple; +{ + int opt, limind, setting; + int block_factor; + RLIMTYPE soft_limit, hard_limit, real_limit, limit; + + setting = cmdarg != 0; + limind = _findlim (cmd); + if (mode == 0) + mode = setting ? (LIMIT_HARD|LIMIT_SOFT) : LIMIT_SOFT; + opt = get_limit (limind, &soft_limit, &hard_limit); + if (opt < 0) + { + builtin_error (_("%s: cannot get limit: %s"), limits[limind].description, + strerror (errno)); + return (EXECUTION_FAILURE); + } + + if (setting == 0) /* print the value of the specified limit */ + { + printone (limind, (mode & LIMIT_SOFT) ? soft_limit : hard_limit, multiple); + return (EXECUTION_SUCCESS); + } + + /* Setting the limit. */ + if (STREQ (cmdarg, "hard")) + real_limit = hard_limit; + else if (STREQ (cmdarg, "soft")) + real_limit = soft_limit; + else if (STREQ (cmdarg, "unlimited")) + real_limit = RLIM_INFINITY; + else if (all_digits (cmdarg)) + { + limit = string_to_rlimtype (cmdarg); + block_factor = BLOCKSIZE(limits[limind].block_factor); + real_limit = limit * block_factor; + + if ((real_limit / block_factor) != limit) + { + sh_erange (cmdarg, _("limit")); + return (EXECUTION_FAILURE); + } + } + else + { + sh_invalidnum (cmdarg); + return (EXECUTION_FAILURE); + } + + if (set_limit (limind, real_limit, mode) < 0) + { + builtin_error (_("%s: cannot modify limit: %s"), limits[limind].description, + strerror (errno)); + return (EXECUTION_FAILURE); + } + + return (EXECUTION_SUCCESS); +} + +static int +get_limit (ind, softlim, hardlim) + int ind; + RLIMTYPE *softlim, *hardlim; +{ + RLIMTYPE value; +#if defined (HAVE_RESOURCE) + struct rlimit limit; +#endif + + if (limits[ind].parameter >= 256) + { + switch (limits[ind].parameter) + { + case RLIMIT_FILESIZE: + if (filesize (&value) < 0) + return -1; + break; + case RLIMIT_PIPESIZE: + if (pipesize (&value) < 0) + return -1; + break; + case RLIMIT_OPENFILES: + value = (RLIMTYPE)getdtablesize (); + break; + case RLIMIT_VIRTMEM: + return (getmaxvm (softlim, hardlim)); + case RLIMIT_MAXUPROC: + if (getmaxuprc (&value) < 0) + return -1; + break; + default: + errno = EINVAL; + return -1; + } + *softlim = *hardlim = value; + return (0); + } + else + { +#if defined (HAVE_RESOURCE) + if (getrlimit (limits[ind].parameter, &limit) < 0) + return -1; + *softlim = limit.rlim_cur; + *hardlim = limit.rlim_max; +# if defined (HPUX9) + if (limits[ind].parameter == RLIMIT_FILESIZE) + { + *softlim *= 512; + *hardlim *= 512; /* Ugh. */ + } + else +# endif /* HPUX9 */ + return 0; +#else + errno = EINVAL; + return -1; +#endif + } +} + +static int +set_limit (ind, newlim, mode) + int ind; + RLIMTYPE newlim; + int mode; +{ +#if defined (HAVE_RESOURCE) + struct rlimit limit; + RLIMTYPE val; +#endif + + if (limits[ind].parameter >= 256) + switch (limits[ind].parameter) + { + case RLIMIT_FILESIZE: +#if !defined (HAVE_RESOURCE) + return (ulimit (2, newlim / 512L)); +#else + errno = EINVAL; + return -1; +#endif + + case RLIMIT_OPENFILES: +#if defined (HAVE_SETDTABLESIZE) +# if defined (__CYGWIN__) + /* Grrr... Cygwin declares setdtablesize as void. */ + setdtablesize (newlim); + return 0; +# else + return (setdtablesize (newlim)); +# endif +#endif + case RLIMIT_PIPESIZE: + case RLIMIT_VIRTMEM: + case RLIMIT_MAXUPROC: + default: + errno = EINVAL; + return -1; + } + else + { +#if defined (HAVE_RESOURCE) + if (getrlimit (limits[ind].parameter, &limit) < 0) + return -1; +# if defined (HPUX9) + if (limits[ind].parameter == RLIMIT_FILESIZE) + newlim /= 512; /* Ugh. */ +# endif /* HPUX9 */ + val = (current_user.euid != 0 && newlim == RLIM_INFINITY && + (mode & LIMIT_HARD) == 0 && /* XXX -- test */ + (limit.rlim_cur <= limit.rlim_max)) + ? limit.rlim_max : newlim; + if (mode & LIMIT_SOFT) + limit.rlim_cur = val; + if (mode & LIMIT_HARD) + limit.rlim_max = val; + + return (setrlimit (limits[ind].parameter, &limit)); +#else + errno = EINVAL; + return -1; +#endif + } +} + +static int +getmaxvm (softlim, hardlim) + RLIMTYPE *softlim, *hardlim; +{ +#if defined (HAVE_RESOURCE) + struct rlimit datalim, stacklim; + + if (getrlimit (RLIMIT_DATA, &datalim) < 0) + return -1; + + if (getrlimit (RLIMIT_STACK, &stacklim) < 0) + return -1; + + /* Protect against overflow. */ + *softlim = (datalim.rlim_cur / 1024L) + (stacklim.rlim_cur / 1024L); + *hardlim = (datalim.rlim_max / 1024L) + (stacklim.rlim_max / 1024L); + return 0; +#else + errno = EINVAL; + return -1; +#endif /* HAVE_RESOURCE */ +} + +static int +filesize(valuep) + RLIMTYPE *valuep; +{ +#if !defined (HAVE_RESOURCE) + long result; + if ((result = ulimit (1, 0L)) < 0) + return -1; + else + *valuep = (RLIMTYPE) result * 512; + return 0; +#else + errno = EINVAL; + return -1; +#endif +} + +static int +pipesize (valuep) + RLIMTYPE *valuep; +{ +#if defined (PIPE_BUF) + /* This is defined on Posix systems. */ + *valuep = (RLIMTYPE) PIPE_BUF; + return 0; +#else +# if defined (_POSIX_PIPE_BUF) + *valuep = (RLIMTYPE) _POSIX_PIPE_BUF; + return 0; +# else +# if defined (PIPESIZE) + /* This is defined by running a program from the Makefile. */ + *valuep = (RLIMTYPE) PIPESIZE; + return 0; +# else + errno = EINVAL; + return -1; +# endif /* PIPESIZE */ +# endif /* _POSIX_PIPE_BUF */ +#endif /* PIPE_BUF */ +} + +static int +getmaxuprc (valuep) + RLIMTYPE *valuep; +{ + long maxchild; + + maxchild = getmaxchild (); + if (maxchild < 0) + { + errno = EINVAL; + return -1; + } + else + { + *valuep = (RLIMTYPE) maxchild; + return 0; + } +} + +static void +print_all_limits (mode) + int mode; +{ + register int i; + RLIMTYPE softlim, hardlim; + + if (mode == 0) + mode |= LIMIT_SOFT; + + for (i = 0; limits[i].option > 0; i++) + { + if (get_limit (i, &softlim, &hardlim) == 0) + printone (i, (mode & LIMIT_SOFT) ? softlim : hardlim, 1); + else if (errno != EINVAL) + builtin_error ("%s: cannot get limit: %s", limits[i].description, + strerror (errno)); + } +} + +static void +printone (limind, curlim, pdesc) + int limind; + RLIMTYPE curlim; + int pdesc; +{ + char unitstr[64]; + int factor; + + factor = BLOCKSIZE(limits[limind].block_factor); + if (pdesc) + { + if (limits[limind].units) + sprintf (unitstr, "(%s, -%c) ", limits[limind].units, limits[limind].option); + else + sprintf (unitstr, "(-%c) ", limits[limind].option); + + printf ("%-20s %20s", limits[limind].description, unitstr); + } + if (curlim == RLIM_INFINITY) + puts ("unlimited"); + else if (curlim == RLIM_SAVED_MAX) + puts ("hard"); + else if (curlim == RLIM_SAVED_CUR) + puts ("soft"); + else + print_rlimtype ((curlim / factor), 1); +} + +/* Set all limits to NEWLIM. NEWLIM currently must be RLIM_INFINITY, which + causes all limits to be set as high as possible depending on mode (like + csh `unlimit'). Returns -1 if NEWLIM is invalid, 0 if all limits + were set successfully, and 1 if at least one limit could not be set. + + To raise all soft limits to their corresponding hard limits, use + ulimit -S -a unlimited + To attempt to raise all hard limits to infinity (superuser-only), use + ulimit -H -a unlimited + To attempt to raise all soft and hard limits to infinity, use + ulimit -a unlimited +*/ + +static int +set_all_limits (mode, newlim) + int mode; + RLIMTYPE newlim; +{ + register int i; + int retval = 0; + + if (newlim != RLIM_INFINITY) + { + errno = EINVAL; + return -1; + } + + if (mode == 0) + mode = LIMIT_SOFT|LIMIT_HARD; + + for (retval = i = 0; limits[i].option > 0; i++) + if (set_limit (i, newlim, mode) < 0) + { + builtin_error (_("%s: cannot modify limit: %s"), limits[i].description, + strerror (errno)); + retval = 1; + } + return retval; +} + +#endif /* !_MINIX */ diff --git a/third_party/bash/builtins_umask.c b/third_party/bash/builtins_umask.c new file mode 100644 index 000000000..686053764 --- /dev/null +++ b/third_party/bash/builtins_umask.c @@ -0,0 +1,281 @@ +/* umask.c, created from umask.def. */ +#line 22 "./umask.def" + +#line 41 "./umask.def" + +#include "config.h" + +#include "bashtypes.h" +#include "filecntl.h" +#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +#include +#endif + +#include +#include "chartypes.h" + +#include "bashintl.h" + +#include "shell.h" +#include "posixstat.h" +#include "common.h" +#include "bashgetopt.h" + +/* **************************************************************** */ +/* */ +/* UMASK Builtin and Helpers */ +/* */ +/* **************************************************************** */ + +static void print_symbolic_umask PARAMS((mode_t)); +static int symbolic_umask PARAMS((WORD_LIST *)); + +/* Set or display the mask used by the system when creating files. Flag + of -S means display the umask in a symbolic mode. */ +int +umask_builtin (list) + WORD_LIST *list; +{ + int print_symbolically, opt, umask_value, pflag; + mode_t umask_arg; + + print_symbolically = pflag = 0; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "Sp")) != -1) + { + switch (opt) + { + case 'S': + print_symbolically++; + break; + case 'p': + pflag++; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + if (list) + { + if (DIGIT (*list->word->word)) + { + umask_value = read_octal (list->word->word); + + /* Note that other shells just let you set the umask to zero + by specifying a number out of range. This is a problem + with those shells. We don't change the umask if the input + is lousy. */ + if (umask_value == -1) + { + sh_erange (list->word->word, _("octal number")); + return (EXECUTION_FAILURE); + } + } + else + { + umask_value = symbolic_umask (list); + if (umask_value == -1) + return (EXECUTION_FAILURE); + } + umask_arg = (mode_t)umask_value; + umask (umask_arg); + if (print_symbolically) + print_symbolic_umask (umask_arg); + } + else /* Display the UMASK for this user. */ + { + umask_arg = umask (022); + umask (umask_arg); + + if (pflag) + printf ("umask%s ", (print_symbolically ? " -S" : "")); + if (print_symbolically) + print_symbolic_umask (umask_arg); + else + printf ("%04lo\n", (unsigned long)umask_arg); + } + + return (sh_chkwrite (EXECUTION_SUCCESS)); +} + +/* Print the umask in a symbolic form. In the output, a letter is + printed if the corresponding bit is clear in the umask. */ +static void +#if defined (__STDC__) +print_symbolic_umask (mode_t um) +#else +print_symbolic_umask (um) + mode_t um; +#endif +{ + char ubits[4], gbits[4], obits[4]; /* u=rwx,g=rwx,o=rwx */ + int i; + + i = 0; + if ((um & S_IRUSR) == 0) + ubits[i++] = 'r'; + if ((um & S_IWUSR) == 0) + ubits[i++] = 'w'; + if ((um & S_IXUSR) == 0) + ubits[i++] = 'x'; + ubits[i] = '\0'; + + i = 0; + if ((um & S_IRGRP) == 0) + gbits[i++] = 'r'; + if ((um & S_IWGRP) == 0) + gbits[i++] = 'w'; + if ((um & S_IXGRP) == 0) + gbits[i++] = 'x'; + gbits[i] = '\0'; + + i = 0; + if ((um & S_IROTH) == 0) + obits[i++] = 'r'; + if ((um & S_IWOTH) == 0) + obits[i++] = 'w'; + if ((um & S_IXOTH) == 0) + obits[i++] = 'x'; + obits[i] = '\0'; + + printf ("u=%s,g=%s,o=%s\n", ubits, gbits, obits); +} + +int +parse_symbolic_mode (mode, initial_bits) + char *mode; + int initial_bits; +{ + int who, op, perm, bits, c; + char *s; + + for (s = mode, bits = initial_bits;;) + { + who = op = perm = 0; + + /* Parse the `who' portion of the symbolic mode clause. */ + while (member (*s, "agou")) + { + switch (c = *s++) + { + case 'u': + who |= S_IRWXU; + continue; + case 'g': + who |= S_IRWXG; + continue; + case 'o': + who |= S_IRWXO; + continue; + case 'a': + who |= S_IRWXU | S_IRWXG | S_IRWXO; + continue; + default: + break; + } + } + + /* The operation is now sitting in *s. */ + op = *s++; + switch (op) + { + case '+': + case '-': + case '=': + break; + default: + builtin_error (_("`%c': invalid symbolic mode operator"), op); + return (-1); + } + + /* Parse out the `perm' section of the symbolic mode clause. */ + while (member (*s, "rwx")) + { + c = *s++; + + switch (c) + { + case 'r': + perm |= S_IRUGO; + break; + case 'w': + perm |= S_IWUGO; + break; + case 'x': + perm |= S_IXUGO; + break; + } + } + + /* Now perform the operation or return an error for a + bad permission string. */ + if (!*s || *s == ',') + { + if (who) + perm &= who; + + switch (op) + { + case '+': + bits |= perm; + break; + case '-': + bits &= ~perm; + break; + case '=': + if (who == 0) + who = S_IRWXU | S_IRWXG | S_IRWXO; + bits &= ~who; + bits |= perm; + break; + + /* No other values are possible. */ + } + + if (*s == '\0') + break; + else + s++; /* skip past ',' */ + } + else + { + builtin_error (_("`%c': invalid symbolic mode character"), *s); + return (-1); + } + } + + return (bits); +} + +/* Set the umask from a symbolic mode string similar to that accepted + by chmod. If the -S argument is given, then print the umask in a + symbolic form. */ +static int +symbolic_umask (list) + WORD_LIST *list; +{ + int um, bits; + + /* Get the initial umask. Don't change it yet. */ + um = umask (022); + umask (um); + + /* All work is done with the complement of the umask -- it's + more intuitive and easier to deal with. It is complemented + again before being returned. */ + bits = parse_symbolic_mode (list->word->word, ~um & 0777); + if (bits == -1) + return (-1); + + um = ~bits & 0777; + return (um); +} diff --git a/third_party/bash/builtins_wait.c b/third_party/bash/builtins_wait.c new file mode 100644 index 000000000..ab566d59e --- /dev/null +++ b/third_party/bash/builtins_wait.c @@ -0,0 +1,320 @@ +/* wait.c, created from wait.def. */ +#line 51 "./wait.def" + +#line 66 "./wait.def" + +#include "config.h" + +#include "bashtypes.h" +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "chartypes.h" + +#include "bashansi.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "jobs.h" +#include "trap.h" +#include "sig.h" +#include "common.h" +#include "bashgetopt.h" + +extern int wait_signal_received; + +procenv_t wait_intr_buf; +int wait_intr_flag; + +static int set_waitlist PARAMS((WORD_LIST *)); +static void unset_waitlist PARAMS((void)); + +/* Wait for the pid in LIST to stop or die. If no arguments are given, then + wait for all of the active background processes of the shell and return + 0. If a list of pids or job specs are given, return the exit status of + the last one waited for. */ + +#define WAIT_RETURN(s) \ + do \ + { \ + wait_signal_received = 0; \ + wait_intr_flag = 0; \ + return (s);\ + } \ + while (0) + +int +wait_builtin (list) + WORD_LIST *list; +{ + int status, code, opt, nflag, vflags, bindflags; + volatile int wflags; + char *vname; + SHELL_VAR *pidvar; + struct procstat pstat; + + USE_VAR(list); + + nflag = wflags = vflags = 0; + vname = NULL; + pidvar = (SHELL_VAR *)NULL; + reset_internal_getopt (); + while ((opt = internal_getopt (list, "fnp:")) != -1) + { + switch (opt) + { +#if defined (JOB_CONTROL) + case 'n': + nflag = 1; + break; + case 'f': + wflags |= JWAIT_FORCE; + break; + case 'p': + vname = list_optarg; + vflags = list_optflags; + break; +#endif + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + /* Sanity-check variable name if -p supplied. */ + if (vname) + { +#if defined (ARRAY_VARS) + int arrayflags; + + SET_VFLAGS (vflags, arrayflags, bindflags); + if (legal_identifier (vname) == 0 && valid_array_reference (vname, arrayflags) == 0) +#else + bindflags = 0; + if (legal_identifier (vname) == 0) +#endif + { + sh_invalidid (vname); + WAIT_RETURN (EXECUTION_FAILURE); + } + if (builtin_unbind_variable (vname) == -2) + WAIT_RETURN (EXECUTION_FAILURE); + } + + /* POSIX.2 says: When the shell is waiting (by means of the wait utility) + for asynchronous commands to complete, the reception of a signal for + which a trap has been set shall cause the wait utility to return + immediately with an exit status greater than 128, after which the trap + associated with the signal shall be taken. + + We handle SIGINT here; it's the only one that needs to be treated + specially (I think), since it's handled specially in {no,}jobs.c. */ + wait_intr_flag = 1; + code = setjmp_sigs (wait_intr_buf); + + if (code) + { + last_command_exit_signal = wait_signal_received; + status = 128 + wait_signal_received; + wait_sigint_cleanup (); +#if defined (JOB_CONTROL) + if (wflags & JWAIT_WAITING) + unset_waitlist (); +#endif + WAIT_RETURN (status); + } + + opt = first_pending_trap (); +#if defined (SIGCHLD) + /* We special case SIGCHLD when not in posix mode because we don't break + out of the wait even when the signal is trapped; we run the trap after + the wait completes. See how it's handled in jobs.c:waitchld(). */ + if (opt == SIGCHLD && posixly_correct == 0) + opt = next_pending_trap (opt+1); +#endif + if (opt != -1) + { + last_command_exit_signal = wait_signal_received = opt; + status = opt + 128; + WAIT_RETURN (status); + } + + /* We support jobs or pids. + wait [pid-or-job ...] */ + +#if defined (JOB_CONTROL) + if (nflag) + { + if (list) + { + opt = set_waitlist (list); + if (opt == 0) + WAIT_RETURN (127); + wflags |= JWAIT_WAITING; + } + + status = wait_for_any_job (wflags, &pstat); + if (vname && status >= 0) + builtin_bind_var_to_int (vname, pstat.pid, bindflags); + + if (status < 0) + status = 127; + if (list) + unset_waitlist (); + WAIT_RETURN (status); + } +#endif + + /* But wait without any arguments means to wait for all of the shell's + currently active background processes. */ + if (list == 0) + { + opt = wait_for_background_pids (&pstat); +#if 0 + /* Compatibility with NetBSD sh: don't set VNAME since it doesn't + correspond to the return status. */ + if (vname && opt) + builtin_bind_var_to_int (vname, pstat.pid, bindflags); +#endif + WAIT_RETURN (EXECUTION_SUCCESS); + } + + status = EXECUTION_SUCCESS; + while (list) + { + pid_t pid; + char *w; + intmax_t pid_value; + + w = list->word->word; + if (DIGIT (*w)) + { + if (legal_number (w, &pid_value) && pid_value == (pid_t)pid_value) + { + pid = (pid_t)pid_value; + status = wait_for_single_pid (pid, wflags|JWAIT_PERROR); + /* status > 256 means pid error */ + pstat.pid = (status > 256) ? NO_PID : pid; + pstat.status = (status > 256) ? 127 : status; + if (status > 256) + status = 127; + } + else + { + sh_badpid (w); + pstat.pid = NO_PID; + pstat.status = 127; + WAIT_RETURN (EXECUTION_FAILURE); + } + } +#if defined (JOB_CONTROL) + else if (*w && *w == '%') + /* Must be a job spec. Check it out. */ + { + int job; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + job = get_job_spec (list); + + if (INVALID_JOB (job)) + { + if (job != DUP_JOB) + sh_badjob (list->word->word); + UNBLOCK_CHILD (oset); + status = 127; /* As per Posix.2, section 4.70.2 */ + pstat.pid = NO_PID; + pstat.status = status; + list = list->next; + continue; + } + + /* Job spec used. Wait for the last pid in the pipeline. */ + UNBLOCK_CHILD (oset); + status = wait_for_job (job, wflags, &pstat); + } +#endif /* JOB_CONTROL */ + else + { + sh_badpid (w); + pstat.pid = NO_PID; + pstat.status = 127; + status = EXECUTION_FAILURE; + } + + /* Don't waste time with a longjmp. */ + if (wait_signal_received) + { + last_command_exit_signal = wait_signal_received; + status = 128 + wait_signal_received; + wait_sigint_cleanup (); + WAIT_RETURN (status); + } + + list = list->next; + } + + if (vname && pstat.pid != NO_PID) + builtin_bind_var_to_int (vname, pstat.pid, bindflags); + + WAIT_RETURN (status); +} + +#if defined (JOB_CONTROL) +/* Take each valid pid or jobspec in LIST and mark the corresponding job as + J_WAITING, so wait -n knows which jobs to wait for. Return the number of + jobs we found. */ +static int +set_waitlist (list) + WORD_LIST *list; +{ + sigset_t set, oset; + int job, r, njob; + intmax_t pid; + WORD_LIST *l; + + BLOCK_CHILD (set, oset); + njob = 0; + for (l = list; l; l = l->next) + { + job = NO_JOB; + job = (l && legal_number (l->word->word, &pid) && pid == (pid_t) pid) + ? get_job_by_pid ((pid_t) pid, 0, 0) + : get_job_spec (l); + if (job == NO_JOB || jobs == 0 || INVALID_JOB (job)) + { + sh_badjob (l->word->word); + continue; + } + /* We don't check yet to see if one of the desired jobs has already + terminated, but we could. We wait until wait_for_any_job(). This + has the advantage of validating all the arguments. */ + if ((jobs[job]->flags & J_WAITING) == 0) + { + njob++; + jobs[job]->flags |= J_WAITING; + } + } + UNBLOCK_CHILD (oset); + return (njob); +} + +/* Clean up after a call to wait -n jobs */ +static void +unset_waitlist () +{ + int i; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + for (i = 0; i < js.j_jobslots; i++) + if (jobs[i] && (jobs[i]->flags & J_WAITING)) + jobs[i]->flags &= ~J_WAITING; + UNBLOCK_CHILD (oset); +} +#endif diff --git a/third_party/bash/casemod.c b/third_party/bash/casemod.c new file mode 100644 index 000000000..6f3f21c78 --- /dev/null +++ b/third_party/bash/casemod.c @@ -0,0 +1,271 @@ +/* casemod.c -- functions to change case of strings */ + +/* Copyright (C) 2008-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if defined (HAVE_CONFIG_H) +# include "config.h" +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif /* HAVE_UNISTD_H */ + +#include "stdc.h" + +#include "bashansi.h" +#include "bashintl.h" +#include "bashtypes.h" + +#include +#include +#include "xmalloc.h" + +#include "shmbchar.h" +#include "shmbutil.h" +#include "chartypes.h" +#include "typemax.h" + +#include "strmatch.h" + +#define _to_wupper(wc) (iswlower (wc) ? towupper (wc) : (wc)) +#define _to_wlower(wc) (iswupper (wc) ? towlower (wc) : (wc)) + +#if !defined (HANDLE_MULTIBYTE) +# define cval(s, i, l) ((s)[(i)]) +# define iswalnum(c) (isalnum(c)) +# define TOGGLE(x) (ISUPPER (x) ? tolower ((unsigned char)x) : (TOUPPER (x))) +#else +# define TOGGLE(x) (iswupper (x) ? towlower (x) : (_to_wupper(x))) +#endif + +/* These must agree with the defines in externs.h */ +#define CASE_NOOP 0x0000 +#define CASE_LOWER 0x0001 +#define CASE_UPPER 0x0002 +#define CASE_CAPITALIZE 0x0004 +#define CASE_UNCAP 0x0008 +#define CASE_TOGGLE 0x0010 +#define CASE_TOGGLEALL 0x0020 +#define CASE_UPFIRST 0x0040 +#define CASE_LOWFIRST 0x0080 + +#define CASE_USEWORDS 0x1000 /* modify behavior to act on words in passed string */ + +extern char *substring PARAMS((char *, int, int)); + +#ifndef UCHAR_MAX +# define UCHAR_MAX TYPE_MAXIMUM(unsigned char) +#endif + +#if defined (HANDLE_MULTIBYTE) +static wchar_t +cval (s, i, l) + char *s; + int i, l; +{ + size_t tmp; + wchar_t wc; + mbstate_t mps; + + if (MB_CUR_MAX == 1 || is_basic (s[i])) + return ((wchar_t)s[i]); + if (i >= (l - 1)) + return ((wchar_t)s[i]); + memset (&mps, 0, sizeof (mbstate_t)); + tmp = mbrtowc (&wc, s + i, l - i, &mps); + if (MB_INVALIDCH (tmp) || MB_NULLWCH (tmp)) + return ((wchar_t)s[i]); + return wc; +} +#endif + +/* Modify the case of characters in STRING matching PAT based on the value of + FLAGS. If PAT is null, modify the case of each character */ +char * +sh_modcase (string, pat, flags) + const char *string; + char *pat; + int flags; +{ + int start, next, end, retind; + int inword, c, nc, nop, match, usewords; + char *ret, *s; + wchar_t wc; + int mb_cur_max; +#if defined (HANDLE_MULTIBYTE) + wchar_t nwc; + char mb[MB_LEN_MAX+1]; + int mlen; + size_t m; + mbstate_t state; +#endif + + if (string == 0 || *string == 0) + { + ret = (char *)xmalloc (1); + ret[0] = '\0'; + return ret; + } + +#if defined (HANDLE_MULTIBYTE) + memset (&state, 0, sizeof (mbstate_t)); +#endif + + start = 0; + end = strlen (string); + mb_cur_max = MB_CUR_MAX; + + ret = (char *)xmalloc (2*end + 1); + retind = 0; + + /* See if we are supposed to split on alphanumerics and operate on each word */ + usewords = (flags & CASE_USEWORDS); + flags &= ~CASE_USEWORDS; + + inword = 0; + while (start < end) + { + wc = cval ((char *)string, start, end); + + if (iswalnum (wc) == 0) + inword = 0; + + if (pat) + { + next = start; + ADVANCE_CHAR (string, end, next); + s = substring ((char *)string, start, next); + match = strmatch (pat, s, FNM_EXTMATCH) != FNM_NOMATCH; + free (s); + if (match == 0) + { + /* copy unmatched portion */ + memcpy (ret + retind, string + start, next - start); + retind += next - start; + start = next; + inword = 1; + continue; + } + } + + /* XXX - for now, the toggling operators work on the individual + words in the string, breaking on alphanumerics. Should I + leave the capitalization operators to do that also? */ + if (flags == CASE_CAPITALIZE) + { + if (usewords) + nop = inword ? CASE_LOWER : CASE_UPPER; + else + nop = (start > 0) ? CASE_LOWER : CASE_UPPER; + inword = 1; + } + else if (flags == CASE_UNCAP) + { + if (usewords) + nop = inword ? CASE_UPPER : CASE_LOWER; + else + nop = (start > 0) ? CASE_UPPER : CASE_LOWER; + inword = 1; + } + else if (flags == CASE_UPFIRST) + { + if (usewords) + nop = inword ? CASE_NOOP : CASE_UPPER; + else + nop = (start > 0) ? CASE_NOOP : CASE_UPPER; + inword = 1; + } + else if (flags == CASE_LOWFIRST) + { + if (usewords) + nop = inword ? CASE_NOOP : CASE_LOWER; + else + nop = (start > 0) ? CASE_NOOP : CASE_LOWER; + inword = 1; + } + else if (flags == CASE_TOGGLE) + { + nop = inword ? CASE_NOOP : CASE_TOGGLE; + inword = 1; + } + else + nop = flags; + + /* Can't short-circuit, some locales have multibyte upper and lower + case equivalents of single-byte ascii characters (e.g., Turkish) */ + if (mb_cur_max == 1) + { +singlebyte: + switch (nop) + { + default: + case CASE_NOOP: nc = wc; break; + case CASE_UPPER: nc = TOUPPER (wc); break; + case CASE_LOWER: nc = TOLOWER (wc); break; + case CASE_TOGGLEALL: + case CASE_TOGGLE: nc = TOGGLE (wc); break; + } + ret[retind++] = nc; + } +#if defined (HANDLE_MULTIBYTE) + else + { + m = mbrtowc (&wc, string + start, end - start, &state); + /* Have to go through wide case conversion even for single-byte + chars, to accommodate single-byte characters where the + corresponding upper or lower case equivalent is multibyte. */ + if (MB_INVALIDCH (m)) + { + wc = (unsigned char)string[start]; + goto singlebyte; + } + else if (MB_NULLWCH (m)) + wc = L'\0'; + switch (nop) + { + default: + case CASE_NOOP: nwc = wc; break; + case CASE_UPPER: nwc = _to_wupper (wc); break; + case CASE_LOWER: nwc = _to_wlower (wc); break; + case CASE_TOGGLEALL: + case CASE_TOGGLE: nwc = TOGGLE (wc); break; + } + + /* We don't have to convert `wide' characters that are in the + unsigned char range back to single-byte `multibyte' characters. */ + if ((int)nwc <= UCHAR_MAX && is_basic ((int)nwc)) + ret[retind++] = nwc; + else + { + mlen = wcrtomb (mb, nwc, &state); + if (mlen > 0) + mb[mlen] = '\0'; + /* Don't assume the same width */ + strncpy (ret + retind, mb, mlen); + retind += mlen; + } + } +#endif + + ADVANCE_CHAR (string, end, start); + } + + ret[retind] = '\0'; + return ret; +} diff --git a/third_party/bash/chartypes.h b/third_party/bash/chartypes.h new file mode 100644 index 000000000..d5be4a3ce --- /dev/null +++ b/third_party/bash/chartypes.h @@ -0,0 +1,109 @@ +/* chartypes.h -- extend ctype.h */ + +/* Copyright (C) 2001-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _SH_CHARTYPES_H +#define _SH_CHARTYPES_H + +#include + +#ifndef UCHAR_MAX +# define UCHAR_MAX 255 +#endif +#ifndef CHAR_MAX +# define CHAR_MAX 127 +#endif + +/* use this as a proxy for C89 */ +#if defined (HAVE_STDLIB_H) && defined (HAVE_STRING_H) +# define IN_CTYPE_DOMAIN(c) 1 +#else +# define IN_CTYPE_DOMAIN(c) ((c) >= 0 && (c) <= CHAR_MAX) +#endif + +#if !defined (isspace) && !defined (HAVE_ISSPACE) +# define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\f') +#endif + +#if !defined (isprint) && !defined (HAVE_ISPRINT) +# define isprint(c) (isalpha((unsigned char)c) || isdigit((unsigned char)c) || ispunct((unsigned char)c)) +#endif + +#if defined (isblank) || defined (HAVE_ISBLANK) +# define ISBLANK(c) (IN_CTYPE_DOMAIN (c) && isblank ((unsigned char)c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif + +#if defined (isgraph) || defined (HAVE_ISGRAPH) +# define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isgraph (c)) +#else +# define ISGRAPH(c) (IN_CTYPE_DOMAIN (c) && isprint ((unsigned char)c) && !isspace ((unsigned char)c)) +#endif + +#if !defined (isxdigit) && !defined (HAVE_ISXDIGIT) +# define isxdigit(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F')) +#endif + +#undef ISPRINT + +#define ISPRINT(c) (IN_CTYPE_DOMAIN (c) && isprint ((unsigned char)c)) +#define ISDIGIT(c) (IN_CTYPE_DOMAIN (c) && isdigit ((unsigned char)c)) +#define ISALNUM(c) (IN_CTYPE_DOMAIN (c) && isalnum ((unsigned char)c)) +#define ISALPHA(c) (IN_CTYPE_DOMAIN (c) && isalpha ((unsigned char)c)) +#define ISCNTRL(c) (IN_CTYPE_DOMAIN (c) && iscntrl ((unsigned char)c)) +#define ISLOWER(c) (IN_CTYPE_DOMAIN (c) && islower ((unsigned char)c)) +#define ISPUNCT(c) (IN_CTYPE_DOMAIN (c) && ispunct ((unsigned char)c)) +#define ISSPACE(c) (IN_CTYPE_DOMAIN (c) && isspace ((unsigned char)c)) +#define ISUPPER(c) (IN_CTYPE_DOMAIN (c) && isupper ((unsigned char)c)) +#define ISXDIGIT(c) (IN_CTYPE_DOMAIN (c) && isxdigit ((unsigned char)c)) + +#define ISLETTER(c) (ISALPHA(c)) + +#define DIGIT(c) ((c) >= '0' && (c) <= '9') + +#define ISWORD(c) (ISLETTER(c) || DIGIT(c) || ((c) == '_')) + +#define HEXVALUE(c) \ + (((c) >= 'a' && (c) <= 'f') \ + ? (c)-'a'+10 \ + : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0') + +#ifndef ISOCTAL +# define ISOCTAL(c) ((c) >= '0' && (c) <= '7') +#endif +#define OCTVALUE(c) ((c) - '0') + +#define TODIGIT(c) ((c) - '0') +#define TOCHAR(c) ((c) + '0') + +#define TOLOWER(c) (ISUPPER(c) ? tolower(c) : (c)) +#define TOUPPER(c) (ISLOWER(c) ? toupper(c) : (c)) + +#ifndef TOCTRL + /* letter to control char -- ASCII. The TOUPPER is in there so \ce and + \cE will map to the same character in $'...' expansions. */ +# define TOCTRL(x) ((x) == '?' ? 0x7f : (TOUPPER(x) & 0x1f)) +#endif +#ifndef UNCTRL + /* control char to letter -- ASCII */ +# define UNCTRL(x) (TOUPPER(x ^ 0x40)) +#endif + +#endif /* _SH_CHARTYPES_H */ diff --git a/third_party/bash/clktck.c b/third_party/bash/clktck.c new file mode 100644 index 000000000..5d370acc1 --- /dev/null +++ b/third_party/bash/clktck.c @@ -0,0 +1,61 @@ +/* clktck.c - get the value of CLK_TCK. */ + +/* Copyright (C) 1997 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#if defined (HAVE_SYS_PARAM_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_LIMITS_H) +# include +#endif + +#if !defined (HAVE_SYSCONF) || !defined (_SC_CLK_TCK) +# if !defined (CLK_TCK) +# if defined (HZ) +# define CLK_TCK HZ +# else +# define CLK_TCK 60 +# endif +# endif /* !CLK_TCK */ +#endif /* !HAVE_SYSCONF && !_SC_CLK_TCK */ + +long +get_clk_tck () +{ + static long retval = 0; + + if (retval != 0) + return (retval); + +#if defined (HAVE_SYSCONF) && defined (_SC_CLK_TCK) + retval = sysconf (_SC_CLK_TCK); +#else /* !SYSCONF || !_SC_CLK_TCK */ + retval = CLK_TCK; +#endif /* !SYSCONF || !_SC_CLK_TCK */ + + return (retval); +} diff --git a/third_party/bash/clock.c b/third_party/bash/clock.c new file mode 100644 index 000000000..8b9b5a0cc --- /dev/null +++ b/third_party/bash/clock.c @@ -0,0 +1,87 @@ +/* clock.c - operations on struct tms and clock_t's */ + +/* Copyright (C) 1999 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_TIMES) + +#include +#include "posixtime.h" + +#if defined (HAVE_SYS_TIMES_H) +# include +#endif + +#include +#include "stdc.h" + +#include "bashintl.h" + +#ifndef locale_decpoint +extern int locale_decpoint PARAMS((void)); +#endif + +extern long get_clk_tck PARAMS((void)); + +void +clock_t_to_secs (t, sp, sfp) + clock_t t; + time_t *sp; + int *sfp; +{ + static long clk_tck = -1; + + if (clk_tck == -1) + clk_tck = get_clk_tck (); + + *sfp = t % clk_tck; + *sfp = (*sfp * 1000) / clk_tck; + + *sp = t / clk_tck; + + /* Sanity check */ + if (*sfp >= 1000) + { + *sp += 1; + *sfp -= 1000; + } +} + +/* Print the time defined by a clock_t (returned by the `times' and `time' + system calls) in a standard way to stdio stream FP. This is scaled in + terms of the value of CLK_TCK, which is what is returned by the + `times' call. */ +void +print_clock_t (fp, t) + FILE *fp; + clock_t t; +{ + time_t timestamp; + long minutes; + int seconds, seconds_fraction; + + clock_t_to_secs (t, ×tamp, &seconds_fraction); + + minutes = timestamp / 60; + seconds = timestamp % 60; + + fprintf (fp, "%ldm%d%c%03ds", minutes, seconds, locale_decpoint(), seconds_fraction); +} +#endif /* HAVE_TIMES */ diff --git a/third_party/bash/collsyms.h b/third_party/bash/collsyms.h new file mode 100644 index 000000000..d56df6113 --- /dev/null +++ b/third_party/bash/collsyms.h @@ -0,0 +1,140 @@ +/* collsyms.h -- collating symbol names and their corresponding characters + (in ascii) as given by POSIX.2 in table 2.8. */ + +/* Copyright (C) 1997-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* The upper-case letters, lower-case letters, and digits are omitted from + this table. The digits are not included in the table in the POSIX.2 + spec. The upper and lower case letters are translated by the code + in smatch.c:collsym(). */ + +typedef struct _COLLSYM { + XCHAR *name; + CHAR code; +} __COLLSYM; + +static __COLLSYM POSIXCOLL [] = +{ + { L("NUL"), L('\0') }, + { L("SOH"), L('\001') }, + { L("STX"), L('\002') }, + { L("ETX"), L('\003') }, + { L("EOT"), L('\004') }, + { L("ENQ"), L('\005') }, + { L("ACK"), L('\006') }, +#ifdef __STDC__ + { L("alert"), L('\a') }, +#else + { L("alert"), L('\007') }, +#endif + { L("BS"), L('\010') }, + { L("backspace"), L('\b') }, + { L("HT"), L('\011') }, + { L("tab"), L('\t') }, + { L("LF"), L('\012') }, + { L("newline"), L('\n') }, + { L("VT"), L('\013') }, + { L("vertical-tab"), L('\v') }, + { L("FF"), L('\014') }, + { L("form-feed"), L('\f') }, + { L("CR"), L('\015') }, + { L("carriage-return"), L('\r') }, + { L("SO"), L('\016') }, + { L("SI"), L('\017') }, + { L("DLE"), L('\020') }, + { L("DC1"), L('\021') }, + { L("DC2"), L('\022') }, + { L("DC3"), L('\023') }, + { L("DC4"), L('\024') }, + { L("NAK"), L('\025') }, + { L("SYN"), L('\026') }, + { L("ETB"), L('\027') }, + { L("CAN"), L('\030') }, + { L("EM"), L('\031') }, + { L("SUB"), L('\032') }, + { L("ESC"), L('\033') }, + { L("IS4"), L('\034') }, + { L("FS"), L('\034') }, + { L("IS3"), L('\035') }, + { L("GS"), L('\035') }, + { L("IS2"), L('\036') }, + { L("RS"), L('\036') }, + { L("IS1"), L('\037') }, + { L("US"), L('\037') }, + { L("space"), L(' ') }, + { L("exclamation-mark"), L('!') }, + { L("quotation-mark"), L('"') }, + { L("number-sign"), L('#') }, + { L("dollar-sign"), L('$') }, + { L("percent-sign"), L('%') }, + { L("ampersand"), L('&') }, + { L("apostrophe"), L('\'') }, + { L("left-parenthesis"), L('(') }, + { L("right-parenthesis"), L(')') }, + { L("asterisk"), L('*') }, + { L("plus-sign"), L('+') }, + { L("comma"), L(',') }, + { L("hyphen"), L('-') }, + { L("hyphen-minus"), L('-') }, + { L("minus"), L('-') }, /* extension from POSIX.2 */ + { L("dash"), L('-') }, /* extension from POSIX.2 */ + { L("period"), L('.') }, + { L("full-stop"), L('.') }, + { L("slash"), L('/') }, + { L("solidus"), L('/') }, /* extension from POSIX.2 */ + { L("zero"), L('0') }, + { L("one"), L('1') }, + { L("two"), L('2') }, + { L("three"), L('3') }, + { L("four"), L('4') }, + { L("five"), L('5') }, + { L("six"), L('6') }, + { L("seven"), L('7') }, + { L("eight"), L('8') }, + { L("nine"), L('9') }, + { L("colon"), L(':') }, + { L("semicolon"), L(';') }, + { L("less-than-sign"), L('<') }, + { L("equals-sign"), L('=') }, + { L("greater-than-sign"), L('>') }, + { L("question-mark"), L('?') }, + { L("commercial-at"), L('@') }, + /* upper-case letters omitted */ + { L("left-square-bracket"), L('[') }, + { L("backslash"), L('\\') }, + { L("reverse-solidus"), L('\\') }, + { L("right-square-bracket"), L(']') }, + { L("circumflex"), L('^') }, + { L("circumflex-accent"), L('^') }, /* extension from POSIX.2 */ + { L("underscore"), L('_') }, + { L("grave-accent"), L('`') }, + /* lower-case letters omitted */ + { L("left-brace"), L('{') }, /* extension from POSIX.2 */ + { L("left-curly-bracket"), L('{') }, + { L("vertical-line"), L('|') }, + { L("right-brace"), L('}') }, /* extension from POSIX.2 */ + { L("right-curly-bracket"), L('}') }, + { L("tilde"), L('~') }, + { L("DEL"), L('\177') }, + { 0, 0 }, +}; + +#undef _COLLSYM +#undef __COLLSYM +#undef POSIXCOLL diff --git a/third_party/bash/command.h b/third_party/bash/command.h new file mode 100644 index 000000000..e0dc2f955 --- /dev/null +++ b/third_party/bash/command.h @@ -0,0 +1,409 @@ +/* command.h -- The structures used internally to represent commands, and + the extern declarations of the functions used to create them. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_COMMAND_H_) +#define _COMMAND_H_ + +#include "stdc.h" + +/* Instructions describing what kind of thing to do for a redirection. */ +enum r_instruction { + r_output_direction, r_input_direction, r_inputa_direction, + r_appending_to, r_reading_until, r_reading_string, + r_duplicating_input, r_duplicating_output, r_deblank_reading_until, + r_close_this, r_err_and_out, r_input_output, r_output_force, + r_duplicating_input_word, r_duplicating_output_word, + r_move_input, r_move_output, r_move_input_word, r_move_output_word, + r_append_err_and_out +}; + +/* Redirection flags; values for rflags */ +#define REDIR_VARASSIGN 0x01 + +/* Redirection errors. */ +#define AMBIGUOUS_REDIRECT -1 +#define NOCLOBBER_REDIRECT -2 +#define RESTRICTED_REDIRECT -3 /* can only happen in restricted shells. */ +#define HEREDOC_REDIRECT -4 /* here-doc temp file can't be created */ +#define BADVAR_REDIRECT -5 /* something wrong with {varname}redir */ + +#define CLOBBERING_REDIRECT(ri) \ + (ri == r_output_direction || ri == r_err_and_out) + +#define OUTPUT_REDIRECT(ri) \ + (ri == r_output_direction || ri == r_input_output || ri == r_err_and_out || ri == r_append_err_and_out) + +#define INPUT_REDIRECT(ri) \ + (ri == r_input_direction || ri == r_inputa_direction || ri == r_input_output) + +#define WRITE_REDIRECT(ri) \ + (ri == r_output_direction || \ + ri == r_input_output || \ + ri == r_err_and_out || \ + ri == r_appending_to || \ + ri == r_append_err_and_out || \ + ri == r_output_force) + +/* redirection needs translation */ +#define TRANSLATE_REDIRECT(ri) \ + (ri == r_duplicating_input_word || ri == r_duplicating_output_word || \ + ri == r_move_input_word || ri == r_move_output_word) + +/* Command Types: */ +enum command_type { cm_for, cm_case, cm_while, cm_if, cm_simple, cm_select, + cm_connection, cm_function_def, cm_until, cm_group, + cm_arith, cm_cond, cm_arith_for, cm_subshell, cm_coproc }; + +/* Possible values for the `flags' field of a WORD_DESC. */ +#define W_HASDOLLAR (1 << 0) /* Dollar sign present. */ +#define W_QUOTED (1 << 1) /* Some form of quote character is present. */ +#define W_ASSIGNMENT (1 << 2) /* This word is a variable assignment. */ +#define W_SPLITSPACE (1 << 3) /* Split this word on " " regardless of IFS */ +#define W_NOSPLIT (1 << 4) /* Do not perform word splitting on this word because ifs is empty string. */ +#define W_NOGLOB (1 << 5) /* Do not perform globbing on this word. */ +#define W_NOSPLIT2 (1 << 6) /* Don't split word except for $@ expansion (using spaces) because context does not allow it. */ +#define W_TILDEEXP (1 << 7) /* Tilde expand this assignment word */ +#define W_DOLLARAT (1 << 8) /* UNUSED - $@ and its special handling */ +#define W_ARRAYREF (1 << 9) /* word is a valid array reference */ +#define W_NOCOMSUB (1 << 10) /* Don't perform command substitution on this word */ +#define W_ASSIGNRHS (1 << 11) /* Word is rhs of an assignment statement */ +#define W_NOTILDE (1 << 12) /* Don't perform tilde expansion on this word */ +#define W_NOASSNTILDE (1 << 13) /* don't do tilde expansion like an assignment statement */ +#define W_EXPANDRHS (1 << 14) /* Expanding word in ${paramOPword} */ +#define W_COMPASSIGN (1 << 15) /* Compound assignment */ +#define W_ASSNBLTIN (1 << 16) /* word is a builtin command that takes assignments */ +#define W_ASSIGNARG (1 << 17) /* word is assignment argument to command */ +#define W_HASQUOTEDNULL (1 << 18) /* word contains a quoted null character */ +#define W_DQUOTE (1 << 19) /* UNUSED - word should be treated as if double-quoted */ +#define W_NOPROCSUB (1 << 20) /* don't perform process substitution */ +#define W_SAWQUOTEDNULL (1 << 21) /* word contained a quoted null that was removed */ +#define W_ASSIGNASSOC (1 << 22) /* word looks like associative array assignment */ +#define W_ASSIGNARRAY (1 << 23) /* word looks like a compound indexed array assignment */ +#define W_ARRAYIND (1 << 24) /* word is an array index being expanded */ +#define W_ASSNGLOBAL (1 << 25) /* word is a global assignment to declare (declare/typeset -g) */ +#define W_NOBRACE (1 << 26) /* Don't perform brace expansion */ +#define W_COMPLETE (1 << 27) /* word is being expanded for completion */ +#define W_CHKLOCAL (1 << 28) /* check for local vars on assignment */ +#define W_FORCELOCAL (1 << 29) /* force assignments to be to local variables, non-fatal on assignment errors */ +/* UNUSED (1 << 30) */ + +/* Flags for the `pflags' argument to param_expand() and various + parameter_brace_expand_xxx functions; also used for string_list_dollar_at */ +#define PF_NOCOMSUB 0x01 /* Do not perform command substitution */ +#define PF_IGNUNBOUND 0x02 /* ignore unbound vars even if -u set */ +#define PF_NOSPLIT2 0x04 /* same as W_NOSPLIT2 */ +#define PF_ASSIGNRHS 0x08 /* same as W_ASSIGNRHS */ +#define PF_COMPLETE 0x10 /* same as W_COMPLETE, sets SX_COMPLETE */ +#define PF_EXPANDRHS 0x20 /* same as W_EXPANDRHS */ +#define PF_ALLINDS 0x40 /* array, act as if [@] was supplied */ + +/* Possible values for subshell_environment */ +#define SUBSHELL_ASYNC 0x01 /* subshell caused by `command &' */ +#define SUBSHELL_PAREN 0x02 /* subshell caused by ( ... ) */ +#define SUBSHELL_COMSUB 0x04 /* subshell caused by `command` or $(command) */ +#define SUBSHELL_FORK 0x08 /* subshell caused by executing a disk command */ +#define SUBSHELL_PIPE 0x10 /* subshell from a pipeline element */ +#define SUBSHELL_PROCSUB 0x20 /* subshell caused by <(command) or >(command) */ +#define SUBSHELL_COPROC 0x40 /* subshell from a coproc pipeline */ +#define SUBSHELL_RESETTRAP 0x80 /* subshell needs to reset trap strings on first call to trap */ +#define SUBSHELL_IGNTRAP 0x100 /* subshell should reset trapped signals from trap_handler */ + +/* A structure which represents a word. */ +typedef struct word_desc { + char *word; /* Zero terminated string. */ + int flags; /* Flags associated with this word. */ +} WORD_DESC; + +/* A linked list of words. */ +typedef struct word_list { + struct word_list *next; + WORD_DESC *word; +} WORD_LIST; + + +/* **************************************************************** */ +/* */ +/* Shell Command Structs */ +/* */ +/* **************************************************************** */ + +/* What a redirection descriptor looks like. If the redirection instruction + is ri_duplicating_input or ri_duplicating_output, use DEST, otherwise + use the file in FILENAME. Out-of-range descriptors are identified by a + negative DEST. */ + +typedef union { + int dest; /* Place to redirect REDIRECTOR to, or ... */ + WORD_DESC *filename; /* filename to redirect to. */ +} REDIRECTEE; + +/* Structure describing a redirection. If REDIRECTOR is negative, the parser + (or translator in redir.c) encountered an out-of-range file descriptor. */ +typedef struct redirect { + struct redirect *next; /* Next element, or NULL. */ + REDIRECTEE redirector; /* Descriptor or varname to be redirected. */ + int rflags; /* Private flags for this redirection */ + int flags; /* Flag value for `open'. */ + enum r_instruction instruction; /* What to do with the information. */ + REDIRECTEE redirectee; /* File descriptor or filename */ + char *here_doc_eof; /* The word that appeared in <flags. */ +#define CMD_WANT_SUBSHELL 0x01 /* User wants a subshell: ( command ) */ +#define CMD_FORCE_SUBSHELL 0x02 /* Shell needs to force a subshell. */ +#define CMD_INVERT_RETURN 0x04 /* Invert the exit value. */ +#define CMD_IGNORE_RETURN 0x08 /* Ignore the exit value. For set -e. */ +#define CMD_NO_FUNCTIONS 0x10 /* Ignore functions during command lookup. */ +#define CMD_INHIBIT_EXPANSION 0x20 /* Do not expand the command words. */ +#define CMD_NO_FORK 0x40 /* Don't fork; just call execve */ +#define CMD_TIME_PIPELINE 0x80 /* Time a pipeline */ +#define CMD_TIME_POSIX 0x100 /* time -p; use POSIX.2 time output spec. */ +#define CMD_AMPERSAND 0x200 /* command & */ +#define CMD_STDIN_REDIR 0x400 /* async command needs implicit . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "chartypes.h" +#include "bashtypes.h" +#include "posixstat.h" +#include + +#include + +#if defined (PREFER_STDARG) +# include +#else +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#define NEED_FPURGE_DECL + +#include "shell.h" +#include "maxpath.h" +#include "flags.h" +#include "parser.h" +#include "jobs.h" +#include "builtins.h" +#include "input.h" +#include "execute_cmd.h" +#include "trap.h" +#include "bashgetopt.h" +#include "common.h" +#include "builtext.h" +#include "tilde.h" + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern const char * const bash_getcwd_errstr; + +/* Used by some builtins and the mainline code. */ +sh_builtin_func_t *last_shell_builtin = (sh_builtin_func_t *)NULL; +sh_builtin_func_t *this_shell_builtin = (sh_builtin_func_t *)NULL; + +/* **************************************************************** */ +/* */ +/* Error reporting, usage, and option processing */ +/* */ +/* **************************************************************** */ + +/* This is a lot like report_error (), but it is for shell builtins + instead of shell control structures, and it won't ever exit the + shell. */ + +static void +builtin_error_prolog () +{ + char *name; + + name = get_name_for_error (); + fprintf (stderr, "%s: ", name); + + if (interactive_shell == 0) + fprintf (stderr, _("line %d: "), executing_line_number ()); + + if (this_command_name && *this_command_name) + fprintf (stderr, "%s: ", this_command_name); +} + +void +#if defined (PREFER_STDARG) +builtin_error (const char *format, ...) +#else +builtin_error (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + builtin_error_prolog (); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + va_end (args); + fprintf (stderr, "\n"); +} + +void +#if defined (PREFER_STDARG) +builtin_warning (const char *format, ...) +#else +builtin_warning (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + builtin_error_prolog (); + fprintf (stderr, _("warning: ")); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + va_end (args); + fprintf (stderr, "\n"); +} + +/* Print a usage summary for the currently-executing builtin command. */ +void +builtin_usage () +{ + if (this_command_name && *this_command_name) + fprintf (stderr, _("%s: usage: "), this_command_name); + fprintf (stderr, "%s\n", _(current_builtin->short_doc)); + fflush (stderr); +} + +/* Return if LIST is NULL else barf and jump to top_level. Used by some + builtins that do not accept arguments. */ +void +no_args (list) + WORD_LIST *list; +{ + if (list) + { + builtin_error (_("too many arguments")); + top_level_cleanup (); + jump_to_top_level (DISCARD); + } +} + +/* Check that no options were given to the currently-executing builtin, + and return 0 if there were options. */ +int +no_options (list) + WORD_LIST *list; +{ + int opt; + + reset_internal_getopt (); + if ((opt = internal_getopt (list, "")) != -1) + { + if (opt == GETOPT_HELP) + { + builtin_help (); + return (2); + } + builtin_usage (); + return (1); + } + return (0); +} + +void +sh_needarg (s) + char *s; +{ + builtin_error (_("%s: option requires an argument"), s); +} + +void +sh_neednumarg (s) + char *s; +{ + builtin_error (_("%s: numeric argument required"), s); +} + +void +sh_notfound (s) + char *s; +{ + builtin_error (_("%s: not found"), s); +} + +/* Function called when one of the builtin commands detects an invalid + option. */ +void +sh_invalidopt (s) + char *s; +{ + builtin_error (_("%s: invalid option"), s); +} + +void +sh_invalidoptname (s) + char *s; +{ + builtin_error (_("%s: invalid option name"), s); +} + +void +sh_invalidid (s) + char *s; +{ + builtin_error (_("`%s': not a valid identifier"), s); +} + +void +sh_invalidnum (s) + char *s; +{ + char *msg; + + if (*s == '0' && isdigit ((unsigned char)s[1])) + msg = _("invalid octal number"); + else if (*s == '0' && s[1] == 'x') + msg = _("invalid hex number"); + else + msg = _("invalid number"); + builtin_error ("%s: %s", s, msg); +} + +void +sh_invalidsig (s) + char *s; +{ + builtin_error (_("%s: invalid signal specification"), s); +} + +void +sh_badpid (s) + char *s; +{ + builtin_error (_("`%s': not a pid or valid job spec"), s); +} + +void +sh_readonly (s) + const char *s; +{ + builtin_error (_("%s: readonly variable"), s); +} + +void +sh_noassign (s) + const char *s; +{ + internal_error (_("%s: cannot assign"), s); /* XXX */ +} + +void +sh_erange (s, desc) + char *s, *desc; +{ + if (s) + builtin_error (_("%s: %s out of range"), s, desc ? desc : _("argument")); + else + builtin_error (_("%s out of range"), desc ? desc : _("argument")); +} + +#if defined (JOB_CONTROL) +void +sh_badjob (s) + char *s; +{ + builtin_error (_("%s: no such job"), s); +} + +void +sh_nojobs (s) + char *s; +{ + if (s) + builtin_error (_("%s: no job control"), s); + else + builtin_error (_("no job control")); +} +#endif + +#if defined (RESTRICTED_SHELL) +void +sh_restricted (s) + char *s; +{ + if (s) + builtin_error (_("%s: restricted"), s); + else + builtin_error (_("restricted")); +} +#endif + +void +sh_notbuiltin (s) + char *s; +{ + builtin_error (_("%s: not a shell builtin"), s); +} + +void +sh_wrerror () +{ +#if defined (DONT_REPORT_BROKEN_PIPE_WRITE_ERRORS) && defined (EPIPE) + if (errno != EPIPE) +#endif /* DONT_REPORT_BROKEN_PIPE_WRITE_ERRORS && EPIPE */ + builtin_error (_("write error: %s"), strerror (errno)); +} + +void +sh_ttyerror (set) + int set; +{ + if (set) + builtin_error (_("error setting terminal attributes: %s"), strerror (errno)); + else + builtin_error (_("error getting terminal attributes: %s"), strerror (errno)); +} + +int +sh_chkwrite (s) + int s; +{ + QUIT; + fflush (stdout); + QUIT; + if (ferror (stdout)) + { + sh_wrerror (); + fpurge (stdout); + clearerr (stdout); + return (EXECUTION_FAILURE); + } + return (s); +} + +/* **************************************************************** */ +/* */ +/* Shell positional parameter manipulation */ +/* */ +/* **************************************************************** */ + +/* Convert a WORD_LIST into a C-style argv. Return the number of elements + in the list in *IP, if IP is non-null. A convenience function for + loadable builtins; also used by `test'. */ +char ** +make_builtin_argv (list, ip) + WORD_LIST *list; + int *ip; +{ + char **argv; + + argv = strvec_from_word_list (list, 0, 1, ip); + argv[0] = this_command_name; + return argv; +} + +/* Remember LIST in $1 ... $9, and REST_OF_ARGS. If DESTRUCTIVE is + non-zero, then discard whatever the existing arguments are, else + only discard the ones that are to be replaced. Set POSPARAM_COUNT + to the number of args assigned (length of LIST). */ +void +remember_args (list, destructive) + WORD_LIST *list; + int destructive; +{ + register int i; + + posparam_count = 0; + + for (i = 1; i < 10; i++) + { + if ((destructive || list) && dollar_vars[i]) + { + free (dollar_vars[i]); + dollar_vars[i] = (char *)NULL; + } + + if (list) + { + dollar_vars[posparam_count = i] = savestring (list->word->word); + list = list->next; + } + } + + /* If arguments remain, assign them to REST_OF_ARGS. + Note that copy_word_list (NULL) returns NULL, and + that dispose_words (NULL) does nothing. */ + if (destructive || list) + { + dispose_words (rest_of_args); + rest_of_args = copy_word_list (list); + posparam_count += list_length (list); + } + + if (destructive) + set_dollar_vars_changed (); + + invalidate_cached_quoted_dollar_at (); +} + +void +shift_args (times) + int times; +{ + WORD_LIST *temp; + int count; + + if (times <= 0) /* caller should check */ + return; + + while (times-- > 0) + { + if (dollar_vars[1]) + free (dollar_vars[1]); + + for (count = 1; count < 9; count++) + dollar_vars[count] = dollar_vars[count + 1]; + + if (rest_of_args) + { + temp = rest_of_args; + dollar_vars[9] = savestring (temp->word->word); + rest_of_args = rest_of_args->next; + temp->next = (WORD_LIST *)NULL; + dispose_words (temp); + } + else + dollar_vars[9] = (char *)NULL; + + posparam_count--; + } +} + +int +number_of_args () +{ +#if 0 + register WORD_LIST *list; + int n; + + for (n = 0; n < 9 && dollar_vars[n+1]; n++) + ; + for (list = rest_of_args; list; list = list->next) + n++; + +if (n != posparam_count) + itrace("number_of_args: n (%d) != posparam_count (%d)", n, posparam_count); +#else + return posparam_count; +#endif +} + +static int changed_dollar_vars; + +/* Have the dollar variables been reset to new values since we last + checked? */ +int +dollar_vars_changed () +{ + return (changed_dollar_vars); +} + +void +set_dollar_vars_unchanged () +{ + changed_dollar_vars = 0; +} + +void +set_dollar_vars_changed () +{ + if (variable_context) + changed_dollar_vars |= ARGS_FUNC; + else if (this_shell_builtin == set_builtin) + changed_dollar_vars |= ARGS_SETBLTIN; + else + changed_dollar_vars |= ARGS_INVOC; +} + +/* **************************************************************** */ +/* */ +/* Validating numeric input and arguments */ +/* */ +/* **************************************************************** */ + +/* Read a numeric arg for this_command_name, the name of the shell builtin + that wants it. LIST is the word list that the arg is to come from. + Accept only the numeric argument; report an error if other arguments + follow. If FATAL is 1, call throw_to_top_level, which exits the + shell; if it's 2, call jump_to_top_level (DISCARD), which aborts the + current command; if FATAL is 0, return an indication of an invalid + number by setting *NUMOK == 0 and return -1. */ +int +get_numeric_arg (list, fatal, count) + WORD_LIST *list; + int fatal; + intmax_t *count; +{ + char *arg; + + if (count) + *count = 1; + + if (list && list->word && ISOPTION (list->word->word, '-')) + list = list->next; + + if (list) + { + arg = list->word->word; + if (arg == 0 || (legal_number (arg, count) == 0)) + { + sh_neednumarg (list->word->word ? list->word->word : "`'"); + if (fatal == 0) + return 0; + else if (fatal == 1) /* fatal == 1; abort */ + throw_to_top_level (); + else /* fatal == 2; discard current command */ + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + } + no_args (list->next); + } + + return (1); +} + +/* Get an eight-bit status value from LIST */ +int +get_exitstat (list) + WORD_LIST *list; +{ + int status; + intmax_t sval; + char *arg; + + if (list && list->word && ISOPTION (list->word->word, '-')) + list = list->next; + + if (list == 0) + { + /* If we're not running the DEBUG trap, the return builtin, when not + given any arguments, uses the value of $? before the trap ran. If + given an argument, return uses it. This means that the trap can't + change $?. The DEBUG trap gets to change $?, though, since that is + part of its reason for existing, and because the extended debug mode + does things with the return value. */ + if (this_shell_builtin == return_builtin && running_trap > 0 && running_trap != DEBUG_TRAP+1) + return (trap_saved_exit_value); + return (last_command_exit_value); + } + + arg = list->word->word; + if (arg == 0 || legal_number (arg, &sval) == 0) + { + sh_neednumarg (list->word->word ? list->word->word : "`'"); + return EX_BADUSAGE; + } + no_args (list->next); + + status = sval & 255; + return status; +} + +/* Return the octal number parsed from STRING, or -1 to indicate + that the string contained a bad number. */ +int +read_octal (string) + char *string; +{ + int result, digits; + + result = digits = 0; + while (*string && ISOCTAL (*string)) + { + digits++; + result = (result * 8) + (*string++ - '0'); + if (result > 07777) + return -1; + } + + if (digits == 0 || *string) + result = -1; + + return (result); +} + +/* **************************************************************** */ +/* */ +/* Manipulating the current working directory */ +/* */ +/* **************************************************************** */ + +/* Return a consed string which is the current working directory. + FOR_WHOM is the name of the caller for error printing. */ +char *the_current_working_directory = (char *)NULL; + +char * +get_working_directory (for_whom) + char *for_whom; +{ + if (no_symbolic_links) + { + FREE (the_current_working_directory); + the_current_working_directory = (char *)NULL; + } + + if (the_current_working_directory == 0) + { +#if defined (GETCWD_BROKEN) + the_current_working_directory = getcwd (0, PATH_MAX); +#else + the_current_working_directory = getcwd (0, 0); +#endif + if (the_current_working_directory == 0) + { + fprintf (stderr, _("%s: error retrieving current directory: %s: %s\n"), + (for_whom && *for_whom) ? for_whom : get_name_for_error (), + _(bash_getcwd_errstr), strerror (errno)); + return (char *)NULL; + } + } + + return (savestring (the_current_working_directory)); +} + +/* Make NAME our internal idea of the current working directory. */ +void +set_working_directory (name) + char *name; +{ + FREE (the_current_working_directory); + the_current_working_directory = savestring (name); +} + +/* **************************************************************** */ +/* */ +/* Job control support functions */ +/* */ +/* **************************************************************** */ + +#if defined (JOB_CONTROL) +int +get_job_by_name (name, flags) + const char *name; + int flags; +{ + register int i, wl, cl, match, job; + register PROCESS *p; + register JOB *j; + + job = NO_JOB; + wl = strlen (name); + for (i = js.j_jobslots - 1; i >= 0; i--) + { + j = get_job_by_jid (i); + if (j == 0 || ((flags & JM_STOPPED) && J_JOBSTATE(j) != JSTOPPED)) + continue; + + p = j->pipe; + do + { + if (flags & JM_EXACT) + { + cl = strlen (p->command); + match = STREQN (p->command, name, cl); + } + else if (flags & JM_SUBSTRING) + match = strcasestr (p->command, name) != (char *)0; + else + match = STREQN (p->command, name, wl); + + if (match == 0) + { + p = p->next; + continue; + } + else if (flags & JM_FIRSTMATCH) + return i; /* return first match */ + else if (job != NO_JOB) + { + if (this_shell_builtin) + builtin_error (_("%s: ambiguous job spec"), name); + else + internal_error (_("%s: ambiguous job spec"), name); + return (DUP_JOB); + } + else + job = i; + } + while (p != j->pipe); + } + + return (job); +} + +/* Return the job spec found in LIST. */ +int +get_job_spec (list) + WORD_LIST *list; +{ + register char *word; + int job, jflags; + + if (list == 0) + return (js.j_current); + + word = list->word->word; + + if (*word == '\0') + return (NO_JOB); + + if (*word == '%') + word++; + + if (DIGIT (*word) && all_digits (word)) + { + job = atoi (word); + return ((job < 0 || job > js.j_jobslots) ? NO_JOB : job - 1); + } + + jflags = 0; + switch (*word) + { + case 0: + case '%': + case '+': + return (js.j_current); + + case '-': + return (js.j_previous); + + case '?': /* Substring search requested. */ + jflags |= JM_SUBSTRING; + word++; + /* FALLTHROUGH */ + + default: + return get_job_by_name (word, jflags); + } +} +#endif /* JOB_CONTROL */ + +/* + * NOTE: `kill' calls this function with forcecols == 0 + */ +int +display_signal_list (list, forcecols) + WORD_LIST *list; + int forcecols; +{ + register int i, column; + char *name; + int result, signum, dflags; + intmax_t lsignum; + + result = EXECUTION_SUCCESS; + if (!list) + { + for (i = 1, column = 0; i < NSIG; i++) + { + name = signal_name (i); + if (STREQN (name, "SIGJUNK", 7) || STREQN (name, "Unknown", 7)) + continue; + + if (posixly_correct && !forcecols) + { + /* This is for the kill builtin. POSIX.2 says the signal names + are displayed without the `SIG' prefix. */ + if (STREQN (name, "SIG", 3)) + name += 3; + printf ("%s%s", name, (i == NSIG - 1) ? "" : " "); + } + else + { + printf ("%2d) %s", i, name); + + if (++column < 5) + printf ("\t"); + else + { + printf ("\n"); + column = 0; + } + } + } + + if ((posixly_correct && !forcecols) || column != 0) + printf ("\n"); + return result; + } + + /* List individual signal names or numbers. */ + while (list) + { + if (legal_number (list->word->word, &lsignum)) + { + /* This is specified by Posix.2 so that exit statuses can be + mapped into signal numbers. */ + if (lsignum > 128) + lsignum -= 128; + if (lsignum < 0 || lsignum >= NSIG) + { + sh_invalidsig (list->word->word); + result = EXECUTION_FAILURE; + list = list->next; + continue; + } + + signum = lsignum; + name = signal_name (signum); + if (STREQN (name, "SIGJUNK", 7) || STREQN (name, "Unknown", 7)) + { + list = list->next; + continue; + } + /* POSIX.2 says that `kill -l signum' prints the signal name without + the `SIG' prefix. */ + printf ("%s\n", (this_shell_builtin == kill_builtin && signum > 0) ? name + 3 : name); + } + else + { + dflags = DSIG_NOCASE; + if (posixly_correct == 0 || this_shell_builtin != kill_builtin) + dflags |= DSIG_SIGPREFIX; + signum = decode_signal (list->word->word, dflags); + if (signum == NO_SIG) + { + sh_invalidsig (list->word->word); + result = EXECUTION_FAILURE; + list = list->next; + continue; + } + printf ("%d\n", signum); + } + list = list->next; + } + return (result); +} + +/* **************************************************************** */ +/* */ +/* Finding builtin commands and their functions */ +/* */ +/* **************************************************************** */ + +/* Perform a binary search and return the address of the builtin function + whose name is NAME. If the function couldn't be found, or the builtin + is disabled or has no function associated with it, return NULL. + Return the address of the builtin. + DISABLED_OKAY means find it even if the builtin is disabled. */ +struct builtin * +builtin_address_internal (name, disabled_okay) + char *name; + int disabled_okay; +{ + int hi, lo, mid, j; + + hi = num_shell_builtins - 1; + lo = 0; + + while (lo <= hi) + { + mid = (lo + hi) / 2; + + j = shell_builtins[mid].name[0] - name[0]; + + if (j == 0) + j = strcmp (shell_builtins[mid].name, name); + + if (j == 0) + { + /* It must have a function pointer. It must be enabled, or we + must have explicitly allowed disabled functions to be found, + and it must not have been deleted. */ + if (shell_builtins[mid].function && + ((shell_builtins[mid].flags & BUILTIN_DELETED) == 0) && + ((shell_builtins[mid].flags & BUILTIN_ENABLED) || disabled_okay)) + return (&shell_builtins[mid]); + else + return ((struct builtin *)NULL); + } + if (j > 0) + hi = mid - 1; + else + lo = mid + 1; + } + return ((struct builtin *)NULL); +} + +/* Return the pointer to the function implementing builtin command NAME. */ +sh_builtin_func_t * +find_shell_builtin (name) + char *name; +{ + current_builtin = builtin_address_internal (name, 0); + return (current_builtin ? current_builtin->function : (sh_builtin_func_t *)NULL); +} + +/* Return the address of builtin with NAME, whether it is enabled or not. */ +sh_builtin_func_t * +builtin_address (name) + char *name; +{ + current_builtin = builtin_address_internal (name, 1); + return (current_builtin ? current_builtin->function : (sh_builtin_func_t *)NULL); +} + +/* Return the function implementing the builtin NAME, but only if it is a + POSIX.2 special builtin. */ +sh_builtin_func_t * +find_special_builtin (name) + char *name; +{ + current_builtin = builtin_address_internal (name, 0); + return ((current_builtin && (current_builtin->flags & SPECIAL_BUILTIN)) ? + current_builtin->function : + (sh_builtin_func_t *)NULL); +} + +static int +shell_builtin_compare (sbp1, sbp2) + struct builtin *sbp1, *sbp2; +{ + int result; + + if ((result = sbp1->name[0] - sbp2->name[0]) == 0) + result = strcmp (sbp1->name, sbp2->name); + + return (result); +} + +/* Sort the table of shell builtins so that the binary search will work + in find_shell_builtin. */ +void +initialize_shell_builtins () +{ + qsort (shell_builtins, num_shell_builtins, sizeof (struct builtin), + (QSFUNC *)shell_builtin_compare); +} + +#if !defined (HELP_BUILTIN) +void +builtin_help () +{ + printf ("%s: %s\n", this_command_name, _("help not available in this version")); +} +#endif + +/* **************************************************************** */ +/* */ +/* Variable assignments during builtin commands */ +/* */ +/* **************************************************************** */ + +/* Assign NAME=VALUE, passing FLAGS to the assignment functions. */ +SHELL_VAR * +builtin_bind_variable (name, value, flags) + char *name; + char *value; + int flags; +{ + SHELL_VAR *v; + int vflags, bindflags; + +#if defined (ARRAY_VARS) + /* Callers are responsible for calling this with array references that have + already undergone valid_array_reference checks (read, printf). */ + vflags = assoc_expand_once ? (VA_NOEXPAND|VA_ONEWORD) : 0; + bindflags = flags | (assoc_expand_once ? ASS_NOEXPAND : 0) | ASS_ALLOWALLSUB; + if (flags & ASS_NOEXPAND) + vflags |= VA_NOEXPAND; + if (flags & ASS_ONEWORD) + vflags |= VA_ONEWORD; + + if (valid_array_reference (name, vflags) == 0) + v = bind_variable (name, value, flags); + else + v = assign_array_element (name, value, bindflags, (array_eltstate_t *)0); +#else /* !ARRAY_VARS */ + v = bind_variable (name, value, flags); +#endif /* !ARRAY_VARS */ + + if (v && readonly_p (v) == 0 && noassign_p (v) == 0) + VUNSETATTR (v, att_invisible); + + return v; +} + +SHELL_VAR * +builtin_bind_var_to_int (name, val, flags) + char *name; + intmax_t val; + int flags; +{ + SHELL_VAR *v; + + v = bind_var_to_int (name, val, flags|ASS_ALLOWALLSUB); + return v; +} + +#if defined (ARRAY_VARS) +SHELL_VAR * +builtin_find_indexed_array (array_name, flags) + char *array_name; + int flags; +{ + SHELL_VAR *entry; + + if ((flags & 2) && legal_identifier (array_name) == 0) + { + sh_invalidid (array_name); + return (SHELL_VAR *)NULL; + } + + entry = find_or_make_array_variable (array_name, 1); + /* With flags argument & 1, find_or_make_array_variable checks for readonly + and noassign variables and prints error messages. */ + if (entry == 0) + return entry; + else if (array_p (entry) == 0) + { + builtin_error (_("%s: not an indexed array"), array_name); + return (SHELL_VAR *)NULL; + } + else if (invisible_p (entry)) + VUNSETATTR (entry, att_invisible); /* no longer invisible */ + + if (flags & 1) + array_flush (array_cell (entry)); + + return entry; +} +#endif /* ARRAY_VARS */ + +/* Like check_unbind_variable, but for use by builtins (only matters for + error messages). */ +int +builtin_unbind_variable (vname) + const char *vname; +{ + SHELL_VAR *v; + + v = find_variable (vname); + if (v && readonly_p (v)) + { + builtin_error (_("%s: cannot unset: readonly %s"), vname, "variable"); + return -2; + } + else if (v && non_unsettable_p (v)) + { + builtin_error (_("%s: cannot unset"), vname); + return -2; + } + return (unbind_variable (vname)); +} + +int +builtin_arrayref_flags (w, baseflags) + WORD_DESC *w; + int baseflags; +{ + char *t; + int vflags; + + vflags = baseflags; + + /* Don't require assoc_expand_once if we have an argument that's already + passed through valid_array_reference and been expanded once. That + doesn't protect it from normal expansions like word splitting, so + proper quoting is still required. */ + if (w->flags & W_ARRAYREF) + vflags |= VA_ONEWORD|VA_NOEXPAND; + +# if 0 + /* This is a little sketchier but handles quoted arguments. */ + if (assoc_expand_once && (t = strchr (w->word, '[')) && t[strlen(t) - 1] == ']') + vflags |= VA_ONEWORD|VA_NOEXPAND; +# endif + + return vflags; +} + +/* **************************************************************** */ +/* */ +/* External interface to manipulate shell options */ +/* */ +/* **************************************************************** */ + +#if defined (ARRAY_VARS) +int +set_expand_once (nval, uwp) + int nval, uwp; +{ + int oa; + + oa = assoc_expand_once; + if (shell_compatibility_level > 51) /* XXX - internal */ + { + if (uwp) + unwind_protect_int (assoc_expand_once); + assoc_expand_once = nval; + } + return oa; +} +#endif diff --git a/third_party/bash/common.h b/third_party/bash/common.h new file mode 100644 index 000000000..f5cd87f08 --- /dev/null +++ b/third_party/bash/common.h @@ -0,0 +1,282 @@ +/* common.h -- extern declarations for functions defined in common.c. */ + +/* Copyright (C) 1993-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (__COMMON_H) +# define __COMMON_H + +#include "stdc.h" + +#define ISOPTION(s, c) (s[0] == '-' && s[1] == c && !s[2]) +#define ISHELP(s) (STREQ ((s), "--help")) + +#define CHECK_HELPOPT(l) \ +do { \ + if ((l) && (l)->word && ISHELP((l)->word->word)) \ + { \ + builtin_help (); \ + return (EX_USAGE); \ + } \ +} while (0) + +#define CASE_HELPOPT \ + case GETOPT_HELP: \ + builtin_help (); \ + return (EX_USAGE) + +/* Flag values for parse_and_execute () and parse_string () */ +#define SEVAL_NONINT 0x001 +#define SEVAL_INTERACT 0x002 +#define SEVAL_NOHIST 0x004 +#define SEVAL_NOFREE 0x008 +#define SEVAL_RESETLINE 0x010 +#define SEVAL_PARSEONLY 0x020 +#define SEVAL_NOLONGJMP 0x040 +#define SEVAL_FUNCDEF 0x080 /* only allow function definitions */ +#define SEVAL_ONECMD 0x100 /* only allow a single command */ +#define SEVAL_NOHISTEXP 0x200 /* inhibit history expansion */ + +/* Flags for describe_command, shared between type.def and command.def */ +#define CDESC_ALL 0x001 /* type -a */ +#define CDESC_SHORTDESC 0x002 /* command -V */ +#define CDESC_REUSABLE 0x004 /* command -v */ +#define CDESC_TYPE 0x008 /* type -t */ +#define CDESC_PATH_ONLY 0x010 /* type -p */ +#define CDESC_FORCE_PATH 0x020 /* type -ap or type -P */ +#define CDESC_NOFUNCS 0x040 /* type -f */ +#define CDESC_ABSPATH 0x080 /* convert to absolute path, no ./ */ +#define CDESC_STDPATH 0x100 /* command -p */ + +/* Flags for get_job_by_name */ +#define JM_PREFIX 0x01 /* prefix of job name */ +#define JM_SUBSTRING 0x02 /* substring of job name */ +#define JM_EXACT 0x04 /* match job name exactly */ +#define JM_STOPPED 0x08 /* match stopped jobs only */ +#define JM_FIRSTMATCH 0x10 /* return first matching job */ + +/* Flags for remember_args and value of changed_dollar_vars */ +#define ARGS_NONE 0x0 +#define ARGS_INVOC 0x01 +#define ARGS_FUNC 0x02 +#define ARGS_SETBLTIN 0x04 + +/* Maximum number of attribute letters */ +#define MAX_ATTRIBUTES 16 + +/* Functions from common.c */ +extern void builtin_error PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); +extern void builtin_warning PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); +extern void builtin_usage PARAMS((void)); +extern void no_args PARAMS((WORD_LIST *)); +extern int no_options PARAMS((WORD_LIST *)); + +/* common error message functions */ +extern void sh_needarg PARAMS((char *)); +extern void sh_neednumarg PARAMS((char *)); +extern void sh_notfound PARAMS((char *)); +extern void sh_invalidopt PARAMS((char *)); +extern void sh_invalidoptname PARAMS((char *)); +extern void sh_invalidid PARAMS((char *)); +extern void sh_invalidnum PARAMS((char *)); +extern void sh_invalidsig PARAMS((char *)); +extern void sh_readonly PARAMS((const char *)); +extern void sh_noassign PARAMS((const char *)); +extern void sh_erange PARAMS((char *, char *)); +extern void sh_badpid PARAMS((char *)); +extern void sh_badjob PARAMS((char *)); +extern void sh_nojobs PARAMS((char *)); +extern void sh_restricted PARAMS((char *)); +extern void sh_notbuiltin PARAMS((char *)); +extern void sh_wrerror PARAMS((void)); +extern void sh_ttyerror PARAMS((int)); +extern int sh_chkwrite PARAMS((int)); + +extern char **make_builtin_argv PARAMS((WORD_LIST *, int *)); +extern void remember_args PARAMS((WORD_LIST *, int)); +extern void shift_args PARAMS((int)); +extern int number_of_args PARAMS((void)); + +extern int dollar_vars_changed PARAMS((void)); +extern void set_dollar_vars_unchanged PARAMS((void)); +extern void set_dollar_vars_changed PARAMS((void)); + +extern int get_numeric_arg PARAMS((WORD_LIST *, int, intmax_t *)); +extern int get_exitstat PARAMS((WORD_LIST *)); +extern int read_octal PARAMS((char *)); + +/* Keeps track of the current working directory. */ +extern char *the_current_working_directory; +extern char *get_working_directory PARAMS((char *)); +extern void set_working_directory PARAMS((char *)); + +#if defined (JOB_CONTROL) +extern int get_job_by_name PARAMS((const char *, int)); +extern int get_job_spec PARAMS((WORD_LIST *)); +#endif +extern int display_signal_list PARAMS((WORD_LIST *, int)); + +/* It's OK to declare a function as returning a Function * without + providing a definition of what a `Function' is. */ +extern struct builtin *builtin_address_internal PARAMS((char *, int)); +extern sh_builtin_func_t *find_shell_builtin PARAMS((char *)); +extern sh_builtin_func_t *builtin_address PARAMS((char *)); +extern sh_builtin_func_t *find_special_builtin PARAMS((char *)); +extern void initialize_shell_builtins PARAMS((void)); + +#if defined (ARRAY_VARS) +extern int set_expand_once PARAMS((int, int)); +#endif + +/* Functions from exit.def */ +extern void bash_logout PARAMS((void)); + +/* Functions from getopts.def */ +extern void getopts_reset PARAMS((int)); + +/* Functions from help.def */ +extern void builtin_help PARAMS((void)); + +/* Functions from read.def */ +extern void read_tty_cleanup PARAMS((void)); +extern int read_tty_modified PARAMS((void)); + +extern int read_builtin_timeout PARAMS((int)); +extern void check_read_timeout PARAMS((void)); + +/* Functions from set.def */ +extern int minus_o_option_value PARAMS((char *)); +extern void list_minus_o_opts PARAMS((int, int)); +extern char **get_minus_o_opts PARAMS((void)); +extern int set_minus_o_option PARAMS((int, char *)); + +extern void set_shellopts PARAMS((void)); +extern void parse_shellopts PARAMS((char *)); +extern void initialize_shell_options PARAMS((int)); + +extern void reset_shell_options PARAMS((void)); + +extern char *get_current_options PARAMS((void)); +extern void set_current_options PARAMS((const char *)); + +/* Functions from shopt.def */ +extern void reset_shopt_options PARAMS((void)); +extern char **get_shopt_options PARAMS((void)); + +extern int shopt_setopt PARAMS((char *, int)); +extern int shopt_listopt PARAMS((char *, int)); + +extern int set_login_shell PARAMS((char *, int)); + +extern void set_bashopts PARAMS((void)); +extern void parse_bashopts PARAMS((char *)); +extern void initialize_bashopts PARAMS((int)); + +extern void set_compatibility_opts PARAMS((void)); + +/* Functions from type.def */ +extern int describe_command PARAMS((char *, int)); + +/* Functions from setattr.def */ +extern int set_or_show_attributes PARAMS((WORD_LIST *, int, int)); +extern int show_all_var_attributes PARAMS((int, int)); +extern int show_local_var_attributes PARAMS((int, int)); +extern int show_var_attributes PARAMS((SHELL_VAR *, int, int)); +extern int show_name_attributes PARAMS((char *, int)); +extern int show_localname_attributes PARAMS((char *, int)); +extern int show_func_attributes PARAMS((char *, int)); +extern void set_var_attribute PARAMS((char *, int, int)); +extern int var_attribute_string PARAMS((SHELL_VAR *, int, char *)); + +/* Functions from pushd.def */ +extern char *get_dirstack_from_string PARAMS((char *)); +extern char *get_dirstack_element PARAMS((intmax_t, int)); +extern void set_dirstack_element PARAMS((intmax_t, int, char *)); +extern WORD_LIST *get_directory_stack PARAMS((int)); + +/* Functions from evalstring.c */ +extern int parse_and_execute PARAMS((char *, const char *, int)); +extern int evalstring PARAMS((char *, const char *, int)); +extern void parse_and_execute_cleanup PARAMS((int)); +extern int parse_string PARAMS((char *, const char *, int, COMMAND **, char **)); +extern int should_suppress_fork PARAMS((COMMAND *)); +extern int can_optimize_connection PARAMS((COMMAND *)); +extern int can_optimize_cat_file PARAMS((COMMAND *)); +extern void optimize_connection_fork PARAMS((COMMAND *)); +extern void optimize_subshell_command PARAMS((COMMAND *)); +extern void optimize_shell_function PARAMS((COMMAND *)); + +/* Functions from evalfile.c */ +extern int maybe_execute_file PARAMS((const char *, int)); +extern int force_execute_file PARAMS((const char *, int)); +extern int source_file PARAMS((const char *, int)); +extern int fc_execute_file PARAMS((const char *)); + +/* variables from common.c */ +extern sh_builtin_func_t *this_shell_builtin; +extern sh_builtin_func_t *last_shell_builtin; + +extern SHELL_VAR *builtin_bind_variable PARAMS((char *, char *, int)); +extern SHELL_VAR *builtin_bind_var_to_int PARAMS((char *, intmax_t, int)); +extern int builtin_unbind_variable PARAMS((const char *)); + +extern SHELL_VAR *builtin_find_indexed_array PARAMS((char *, int)); +extern int builtin_arrayref_flags PARAMS((WORD_DESC *, int)); + +/* variables from evalfile.c */ +extern int sourcelevel; + +/* variables from evalstring.c */ +extern int parse_and_execute_level; + +/* variables from break.def/continue.def */ +extern int breaking; +extern int continuing; +extern int loop_level; + +/* variables from shift.def */ +extern int print_shift_error; + +/* variables from shopt.def */ +#if defined (ARRAY_VARS) +extern int expand_once_flag; +#endif + +/* variables from source.def */ +extern int source_searches_cwd; +extern int source_uses_path; + +/* variables from wait.def */ +extern int wait_intr_flag; + +/* common code to set flags for valid_array_reference and builtin_bind_variable */ +#if defined (ARRAY_VARS) +#define SET_VFLAGS(wordflags, vflags, bindflags) \ + do { \ + vflags = assoc_expand_once ? VA_NOEXPAND : 0; \ + bindflags = assoc_expand_once ? ASS_NOEXPAND : 0; \ + if (assoc_expand_once && (wordflags & W_ARRAYREF)) \ + vflags |= VA_ONEWORD|VA_NOEXPAND; \ + if (vflags & VA_NOEXPAND) \ + bindflags |= ASS_NOEXPAND; \ + if (vflags & VA_ONEWORD) \ + bindflags |= ASS_ONEWORD; \ + } while (0) +#endif + +#endif /* !__COMMON_H */ diff --git a/third_party/bash/config-bot.h b/third_party/bash/config-bot.h new file mode 100644 index 000000000..a687e4029 --- /dev/null +++ b/third_party/bash/config-bot.h @@ -0,0 +1,207 @@ +/* config-bot.h */ +/* modify settings or make new ones based on what autoconf tells us. */ + +/* Copyright (C) 1989-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/*********************************************************/ +/* Modify or set defines based on the configure results. */ +/*********************************************************/ + +#if !defined (HAVE_VPRINTF) && defined (HAVE_DOPRNT) +# define USE_VFPRINTF_EMULATION +# define HAVE_VPRINTF +#endif + +#if defined (HAVE_SYS_RESOURCE_H) && defined (HAVE_GETRLIMIT) +# define HAVE_RESOURCE +#endif + +#if !defined (GETPGRP_VOID) +# define HAVE_BSD_PGRP +#endif + +/* Try this without testing __STDC__ for the time being. */ +#if defined (HAVE_STDARG_H) +# define PREFER_STDARG +# define USE_VARARGS +#else +# if defined (HAVE_VARARGS_H) +# define PREFER_VARARGS +# define USE_VARARGS +# endif +#endif + +#if defined (HAVE_SYS_SOCKET_H) && defined (HAVE_GETPEERNAME) && defined (HAVE_NETINET_IN_H) +# define HAVE_NETWORK +#endif + +#if defined (HAVE_REGEX_H) && defined (HAVE_REGCOMP) && defined (HAVE_REGEXEC) +# define HAVE_POSIX_REGEXP +#endif + +/* backwards compatibility between different autoconf versions */ +#if HAVE_DECL_SYS_SIGLIST && !defined (SYS_SIGLIST_DECLARED) +# define SYS_SIGLIST_DECLARED +#endif + +/***********************************************************************/ +/* Unset defines based on what configure reports as missing or broken. */ +/***********************************************************************/ + +/* Ultrix botches type-ahead when switching from canonical to + non-canonical mode, at least through version 4.3 */ +#if !defined (HAVE_TERMIOS_H) || !defined (HAVE_TCGETATTR) || defined (ultrix) +# define TERMIOS_MISSING +#endif + +/* If we have a getcwd(3), but one that does not dynamically allocate memory, + #undef HAVE_GETCWD so the replacement in getcwd.c will be built. We do + not do this on Solaris, because their implementation of loopback mounts + breaks the traditional file system assumptions that getcwd uses. */ +#if defined (HAVE_GETCWD) && defined (GETCWD_BROKEN) && !defined (SOLARIS) +# undef HAVE_GETCWD +#endif + +#if !defined (HAVE_DEV_FD) && defined (NAMED_PIPES_MISSING) +# undef PROCESS_SUBSTITUTION +#endif + +#if defined (JOB_CONTROL_MISSING) +# undef JOB_CONTROL +#endif + +#if defined (STRCOLL_BROKEN) +# undef HAVE_STRCOLL +#endif + +#if !defined (HAVE_POSIX_REGEXP) +# undef COND_REGEXP +#endif + +#if !HAVE_MKSTEMP +# undef USE_MKSTEMP +#endif + +#if !HAVE_MKDTEMP +# undef USE_MKDTEMP +#endif + +/* If the shell is called by this name, it will become restricted. */ +#if defined (RESTRICTED_SHELL) +# define RESTRICTED_SHELL_NAME "rbash" +#endif + +/***********************************************************/ +/* Make sure feature defines have necessary prerequisites. */ +/***********************************************************/ + +/* BANG_HISTORY requires HISTORY. */ +#if defined (BANG_HISTORY) && !defined (HISTORY) +# define HISTORY +#endif /* BANG_HISTORY && !HISTORY */ + +#if defined (READLINE) && !defined (HISTORY) +# define HISTORY +#endif + +#if defined (PROGRAMMABLE_COMPLETION) && !defined (READLINE) +# undef PROGRAMMABLE_COMPLETION +#endif + +#if !defined (V9_ECHO) +# undef DEFAULT_ECHO_TO_XPG +#endif + +#if !defined (PROMPT_STRING_DECODE) +# undef PPROMPT +# define PPROMPT "$ " +#endif + +#if !defined (HAVE_SYSLOG) || !defined (HAVE_SYSLOG_H) +# undef SYSLOG_HISTORY +#endif + +/************************************************/ +/* check multibyte capability for I18N code */ +/************************************************/ + +/* For platforms which support the ISO C amendment 1 functionality we + support user defined character classes. */ +/* Solaris 2.5 has a bug: must be included before . */ +#if defined (HAVE_WCTYPE_H) && defined (HAVE_WCHAR_H) && defined (HAVE_LOCALE_H) +# include +# include +# if defined (HAVE_ISWCTYPE) && \ + defined (HAVE_ISWLOWER) && \ + defined (HAVE_ISWUPPER) && \ + defined (HAVE_MBSRTOWCS) && \ + defined (HAVE_MBRTOWC) && \ + defined (HAVE_MBRLEN) && \ + defined (HAVE_TOWLOWER) && \ + defined (HAVE_TOWUPPER) && \ + defined (HAVE_WCHAR_T) && \ + defined (HAVE_WCTYPE_T) && \ + defined (HAVE_WINT_T) && \ + defined (HAVE_WCWIDTH) && \ + defined (HAVE_WCTYPE) + /* system is supposed to support XPG5 */ +# define HANDLE_MULTIBYTE 1 +# endif +#endif + +/* If we don't want multibyte chars even on a system that supports them, let + the configuring user turn multibyte support off. */ +#if defined (NO_MULTIBYTE_SUPPORT) +# undef HANDLE_MULTIBYTE +#endif + +/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */ +#if HANDLE_MULTIBYTE && !defined (HAVE_MBSTATE_T) +# define wcsrtombs(dest, src, len, ps) (wcsrtombs) (dest, src, len, 0) +# define mbsrtowcs(dest, src, len, ps) (mbsrtowcs) (dest, src, len, 0) +# define wcrtomb(s, wc, ps) (wcrtomb) (s, wc, 0) +# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0) +# define mbrlen(s, n, ps) (mbrlen) (s, n, 0) +# define mbstate_t int +#endif + +/* Make sure MB_LEN_MAX is at least 16 (some systems define + MB_LEN_MAX as 1) */ +#ifdef HANDLE_MULTIBYTE +# include +# if defined(MB_LEN_MAX) && (MB_LEN_MAX < 16) +# undef MB_LEN_MAX +# endif +# if !defined (MB_LEN_MAX) +# define MB_LEN_MAX 16 +# endif +#endif + +/************************************************/ +/* end of multibyte capability checks for I18N */ +/************************************************/ + +/******************************************************************/ +/* Placeholder for builders to #undef any unwanted features from */ +/* config-top.h or created by configure (such as the default mail */ +/* file for mail checking). */ +/******************************************************************/ + +/* If you don't want bash to provide a default mail file to check. */ +/* #undef DEFAULT_MAIL_DIRECTORY */ diff --git a/third_party/bash/config-top.h b/third_party/bash/config-top.h new file mode 100644 index 000000000..db4ab6ee3 --- /dev/null +++ b/third_party/bash/config-top.h @@ -0,0 +1,201 @@ +/* config-top.h - various user-settable options not under the control of autoconf. */ + +/* Copyright (C) 2002-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* Define CONTINUE_AFTER_KILL_ERROR if you want the kill command to + continue processing arguments after one of them fails. This is + what POSIX.2 specifies. */ +#define CONTINUE_AFTER_KILL_ERROR + +/* Define BREAK_COMPLAINS if you want the non-standard, but useful + error messages about `break' and `continue' out of context. */ +#define BREAK_COMPLAINS + +/* Define CD_COMPLAINS if you want the non-standard, but sometimes-desired + error messages about multiple directory arguments to `cd'. */ +#define CD_COMPLAINS + +/* Define BUFFERED_INPUT if you want the shell to do its own input + buffering, rather than using stdio. Do not undefine this; it's + required to preserve semantics required by POSIX. */ +#define BUFFERED_INPUT + +/* Define ONESHOT if you want sh -c 'command' to avoid forking to execute + `command' whenever possible. This is a big efficiency improvement. */ +#define ONESHOT + +/* Define V9_ECHO if you want to give the echo builtin backslash-escape + interpretation using the -e option, in the style of the Bell Labs 9th + Edition version of echo. You cannot emulate the System V echo behavior + without this option. */ +#define V9_ECHO + +/* Define DONT_REPORT_SIGPIPE if you don't want to see `Broken pipe' messages + when a job like `cat jobs.c | exit 1' terminates due to a SIGPIPE. */ +#define DONT_REPORT_SIGPIPE + +/* Define DONT_REPORT_SIGTERM if you don't want to see `Terminates' message + when a job exits due to SIGTERM, since that's the default signal sent + by the kill builtin. */ +#define DONT_REPORT_SIGTERM + +/* Define DONT_REPORT_BROKEN_PIPE_WRITE_ERRORS if you don't want builtins + like `echo' and `printf' to report errors when output does not succeed + due to EPIPE. */ +/* #define DONT_REPORT_BROKEN_PIPE_WRITE_ERRORS */ + +/* The default value of the PATH variable. */ +#ifndef DEFAULT_PATH_VALUE +#define DEFAULT_PATH_VALUE \ + "/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:." +#endif + +/* If you want to unconditionally set a value for PATH in every restricted + shell, set this. */ +/* #define RBASH_STATIC_PATH_VALUE "/rbin:/usr/rbin" */ + +/* The value for PATH when invoking `command -p'. This is only used when + the Posix.2 confstr () function, or CS_PATH define are not present. */ +#ifndef STANDARD_UTILS_PATH +#define STANDARD_UTILS_PATH \ + "/bin:/usr/bin:/sbin:/usr/sbin:/etc:/usr/etc" +#endif + +/* The default path for enable -f */ +#ifndef DEFAULT_LOADABLE_BUILTINS_PATH +#define DEFAULT_LOADABLE_BUILTINS_PATH \ + "/usr/local/lib/bash:/usr/lib/bash:/opt/local/lib/bash:/usr/pkg/lib/bash:/opt/pkg/lib/bash:." +#endif + +/* Default primary and secondary prompt strings. */ +#define PPROMPT "\\s-\\v\\$ " +#define SPROMPT "> " + +/* Undefine this if you don't want the ksh-compatible behavior of reprinting + the select menu after a valid choice is made only if REPLY is set to NULL + in the body of the select command. The menu is always reprinted if the + reply to the select query is an empty line. */ +#define KSH_COMPATIBLE_SELECT + +/* Default interactive shell startup file. */ +#define DEFAULT_BASHRC "~/.bashrc" + +/* System-wide .bashrc file for interactive shells. */ +/* #define SYS_BASHRC "/etc/bash.bashrc" */ + +/* System-wide .bash_logout for login shells. */ +/* #define SYS_BASH_LOGOUT "/etc/bash.bash_logout" */ + +/* Define this to make non-interactive shells begun with argv[0][0] == '-' + run the startup files when not in posix mode. */ +/* #define NON_INTERACTIVE_LOGIN_SHELLS */ + +/* Define this if you want bash to try to check whether it's being run by + sshd and source the .bashrc if so (like the rshd behavior). This checks + for the presence of SSH_CLIENT or SSH2_CLIENT in the initial environment, + which can be fooled under certain not-uncommon circumstances. */ +/* #define SSH_SOURCE_BASHRC */ + +/* Define if you want the case-toggling operators (~[~]) and the + `capcase' variable attribute (declare -c). */ +/* TAG: bash-5.2 disable? */ +#define CASEMOD_TOGGLECASE +#define CASEMOD_CAPCASE + +/* This is used as the name of a shell function to call when a command + name is not found. If you want to name it something other than the + default ("command_not_found_handle"), change it here. */ +/* #define NOTFOUND_HOOK "command_not_found_handle" */ + +/* Define if you want each line saved to the history list in bashhist.c: + bash_add_history() to be sent to syslog(). */ +/* #define SYSLOG_HISTORY */ +#if defined (SYSLOG_HISTORY) +# define SYSLOG_FACILITY LOG_USER +# define SYSLOG_LEVEL LOG_INFO +# define OPENLOG_OPTS LOG_PID +#endif + +/* Define if you want syslogging history to be controllable at runtime via a + shell option; if defined, the value is the default for the syslog_history + shopt option */ +#if defined (SYSLOG_HISTORY) +/* #define SYSLOG_SHOPT 1 */ +#endif + +/* Define if you want to include code in shell.c to support wordexp(3) */ +/* #define WORDEXP_OPTION */ + +/* Define as 1 if you want to enable code that implements multiple coprocs + executing simultaneously */ +#ifndef MULTIPLE_COPROCS +# define MULTIPLE_COPROCS 0 +#endif + +/* Define to 0 if you want the checkwinsize option off by default, 1 if you + want it on. */ +#define CHECKWINSIZE_DEFAULT 1 + +/* Define to 1 if you want to optimize for sequential array assignment when + using indexed arrays, 0 if you want bash-4.2 behavior, which favors + random access but is O(N) for each array assignment. */ +#define OPTIMIZE_SEQUENTIAL_ARRAY_ASSIGNMENT 1 + +/* Define to 1 if you want to be able to export indexed arrays to processes + using the foo=([0]=one [1]=two) and so on */ +/* #define ARRAY_EXPORT 1 */ + +/* Define to 1 if you want the shell to exit if it is running setuid and its + attempt to drop privilege using setuid(getuid()) fails with errno == EAGAIN */ +/* #define EXIT_ON_SETUID_FAILURE 1 */ + +/* Define to 1 if you want the shell to re-check $PATH if a hashed filename + no longer exists. This behavior is the default in Posix mode. */ +#define CHECKHASH_DEFAULT 0 + +/* Define to the maximum level of recursion you want for the eval builtin + and trap handlers (since traps are run as if run by eval). + 0 means the limit is not active. */ +#define EVALNEST_MAX 0 + +/* Define to the maximum level of recursion you want for the source/. builtin. + 0 means the limit is not active. */ +#define SOURCENEST_MAX 0 + +/* Define to use libc mktemp/mkstemp instead of replacements in lib/sh/tmpfile.c */ +#define USE_MKTEMP +#define USE_MKSTEMP +#define USE_MKDTEMP + +/* Define to force the value of OLDPWD inherited from the environment to be a + directory */ +#define OLDPWD_CHECK_DIRECTORY 1 + +/* Define to set the initial size of the history list ($HISTSIZE). This must + be a string. */ +/*#define HISTSIZE_DEFAULT "500"*/ + +/* Define to 0 if you want history expansion to be disabled by default in + interactive shells; define to 1 for the historical behavior of enabling + when the shell is interactive. */ +#define HISTEXPAND_DEFAULT 1 + +/* Undefine or define to 0 if you don't want to allow associative array + assignment using a compound list of key-value pairs. */ +#define ASSOC_KVPAIR_ASSIGNMENT 1 diff --git a/third_party/bash/config.h b/third_party/bash/config.h new file mode 100644 index 000000000..ac1cacfaa --- /dev/null +++ b/third_party/bash/config.h @@ -0,0 +1,1235 @@ +/* config.h. Generated from config.h.in by configure. */ +/* config.h -- Configuration file for bash. */ + +/* Copyright (C) 1987-2009,2011-2012,2013-2019 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#ifdef _COSMO_SOURCE +#undef _COSMO_SOURCE +#endif + +/* Template settings for autoconf */ + +/* Configuration feature settings controllable by autoconf. */ + +/* Define JOB_CONTROL if your operating system supports + BSD-like job control. */ +#define JOB_CONTROL 1 + +/* Define ALIAS if you want the alias features. */ +#define ALIAS 1 + +/* Define PUSHD_AND_POPD if you want those commands to be compiled in. + (Also the `dirs' commands.) */ +#define PUSHD_AND_POPD 1 + +/* Define BRACE_EXPANSION if you want curly brace expansion a la Csh: + foo{a,b} -> fooa foob. Even if this is compiled in (the default) you + can turn it off at shell startup with `-nobraceexpansion', or during + shell execution with `set +o braceexpand'. */ +#define BRACE_EXPANSION 1 + +/* Define READLINE to get the nifty/glitzy editing features. + This is on by default. You can turn it off interactively + with the -nolineediting flag. */ +#define READLINE 1 + +/* Define BANG_HISTORY if you want to have Csh style "!" history expansion. + This is unrelated to READLINE. */ +#define BANG_HISTORY 1 + +/* Define HISTORY if you want to have access to previously typed commands. + + If both HISTORY and READLINE are defined, you can get at the commands + with line editing commands, and you can directly manipulate the history + from the command line. + + If only HISTORY is defined, the `fc' and `history' builtins are + available. */ +#define HISTORY 1 + +/* Define this if you want completion that puts all alternatives into + a brace expansion shell expression. */ +#if defined (BRACE_EXPANSION) && defined (READLINE) +# define BRACE_COMPLETION +#endif /* BRACE_EXPANSION */ + +/* Define DEFAULT_ECHO_TO_XPG if you want the echo builtin to interpret + the backslash-escape characters by default, like the XPG Single Unix + Specification V2 for echo. + This requires that V9_ECHO be defined. */ +/* #undef DEFAULT_ECHO_TO_XPG */ + +/* Define HELP_BUILTIN if you want the `help' shell builtin and the long + documentation strings compiled into the shell. */ +#define HELP_BUILTIN 1 + +/* Define RESTRICTED_SHELL if you want the generated shell to have the + ability to be a restricted one. The shell thus generated can become + restricted by being run with the name "rbash", or by setting the -r + flag. */ +#define RESTRICTED_SHELL 1 + +/* Define DISABLED_BUILTINS if you want "builtin foo" to always run the + shell builtin "foo", even if it has been disabled with "enable -n foo". */ +/* #undef DISABLED_BUILTINS */ + +/* Define PROCESS_SUBSTITUTION if you want the K*rn shell-like process + substitution features "<(file)". */ +/* Right now, you cannot do this on machines without fully operational + FIFO support. This currently include NeXT and Alliant. */ +#define PROCESS_SUBSTITUTION 1 + +/* Define PROMPT_STRING_DECODE if you want the backslash-escaped special + characters in PS1 and PS2 expanded. Variable expansion will still be + performed. */ +#define PROMPT_STRING_DECODE 1 + +/* Define SELECT_COMMAND if you want the Korn-shell style `select' command: + select word in word_list; do command_list; done */ +#define SELECT_COMMAND 1 + +/* Define COMMAND_TIMING of you want the ksh-style `time' reserved word and + the ability to time pipelines, functions, and builtins. */ +#define COMMAND_TIMING 1 + +/* Define ARRAY_VARS if you want ksh-style one-dimensional array variables. */ +#define ARRAY_VARS 1 + +/* Define DPAREN_ARITHMETIC if you want the ksh-style ((...)) arithmetic + evaluation command. */ +#define DPAREN_ARITHMETIC 1 + +/* Define EXTENDED_GLOB if you want the ksh-style [*+@?!](patlist) extended + pattern matching. */ +#define EXTENDED_GLOB 1 + +/* Define EXTGLOB_DEFAULT to the value you'd like the extglob shell option + to have by default */ +#define EXTGLOB_DEFAULT 0 + +/* Define COND_COMMAND if you want the ksh-style [[...]] conditional + command. */ +#define COND_COMMAND 1 + +/* Define COND_REGEXP if you want extended regular expression matching and the + =~ binary operator in the [[...]] conditional command. */ +#define COND_REGEXP 1 + +/* Define COPROCESS_SUPPORT if you want support for ksh-like coprocesses and + the `coproc' reserved word */ +#define COPROCESS_SUPPORT 1 + +/* Define ARITH_FOR_COMMAND if you want the ksh93-style + for (( init; test; step )) do list; done + arithmetic for command. */ +#define ARITH_FOR_COMMAND 1 + +/* Define NETWORK_REDIRECTIONS if you want /dev/(tcp|udp)/host/port to open + socket connections when used in redirections */ +#define NETWORK_REDIRECTIONS 1 + +/* Define PROGRAMMABLE_COMPLETION for the programmable completion features + and the complete builtin. */ +#define PROGRAMMABLE_COMPLETION 1 + +/* Define NO_MULTIBYTE_SUPPORT to not compile in support for multibyte + characters, even if the OS supports them. */ +/* #undef NO_MULTIBYTE_SUPPORT */ + +/* Define DEBUGGER if you want to compile in some features used only by the + bash debugger. */ +#define DEBUGGER 1 + +/* Define STRICT_POSIX if you want bash to be strictly posix.2 conformant by + default (except for echo; that is controlled separately). */ +/* #undef STRICT_POSIX */ + +/* Define MEMSCRAMBLE if you want the bash malloc and free to scramble + memory contents on malloc() and free(). */ +#define MEMSCRAMBLE 1 + +/* Define for case-modifying variable attributes; variables modified on + assignment */ +#define CASEMOD_ATTRS 1 + +/* Define for case-modifying word expansions */ +#define CASEMOD_EXPANSIONS 1 + +/* Define to make the `direxpand' shopt option enabled by default. */ +/* #undef DIRCOMPLETE_EXPAND_DEFAULT */ + +/* Define to make the `globasciiranges' shopt option enabled by default. */ +#define GLOBASCII_DEFAULT 1 + +/* Define to allow functions to be imported from the environment. */ +#define FUNCTION_IMPORT 1 + +/* Define AFS if you are using Transarc's AFS. */ +/* #undef AFS */ + +/* #undef ENABLE_NLS */ + +/* End of configuration settings controllable by autoconf. */ +/* Other settable options appear in config-top.h. */ + +#include "config-top.h" + +/* Beginning of autoconf additions. */ + +/* Characteristics of the C compiler */ +/* #undef const */ + +/* #undef inline */ + +#define restrict __restrict__ + +/* #undef volatile */ + +/* Define if cpp supports the ANSI-C stringizing `#' operator */ +#define HAVE_STRINGIZE 1 + +/* Define if the compiler supports `long double' variables. */ +#define HAVE_LONG_DOUBLE 1 + +#define PROTOTYPES 1 +#define __PROTOTYPES 1 + +/* #undef __CHAR_UNSIGNED__ */ + +/* Define if the compiler supports `long long int' variables. */ +#define HAVE_LONG_LONG_INT 1 + +#define HAVE_UNSIGNED_LONG_LONG_INT 1 + +/* The number of bytes in a int. */ +#define SIZEOF_INT 4 + +/* The number of bytes in a long. */ +#define SIZEOF_LONG 8 + +/* The number of bytes in a pointer to char. */ +#define SIZEOF_CHAR_P 8 + +/* The number of bytes in a size_t. */ +#define SIZEOF_SIZE_T 8 + +/* The number of bytes in a double (hopefully 8). */ +#define SIZEOF_DOUBLE 8 + +/* The number of bytes in an `intmax_t'. */ +#define SIZEOF_INTMAX_T 8 + +/* The number of bytes in a `long long', if we have one. */ +#define SIZEOF_LONG_LONG 8 + +/* The number of bytes in a `wchar_t', if supported */ +#define SIZEOF_WCHAR_T 4 + +/* System paths */ + +#define DEFAULT_MAIL_DIRECTORY "/var/mail" + +/* Characteristics of the system's header files and libraries that affect + the compilation environment. */ + +/* These are set by AC_USE_SYSTEM_EXTENSIONS */ + +/* Define if the system does not provide POSIX.1 features except + with this defined. */ +/* #undef _POSIX_1_SOURCE */ + +/* Define if you need to in order for stat and other things to work. */ +/* #undef _POSIX_SOURCE */ + +/* Define to use GNU libc extensions. */ +#define _GNU_SOURCE 1 + +/* Define to enable general system extensions on Solaris. */ +#define __EXTENSIONS__ 1 + +/* General system extensions on AIX */ +#define _ALL_SOURCE 1 + +#define _POSIX_PTHREAD_SEMANTICS 1 +#define _TANDEM_SOURCE 1 +/* #undef _MINIX */ + +/* Memory management functions. */ + +/* Define if using the bash version of malloc in lib/malloc/malloc.c */ +/* #undef USING_BASH_MALLOC */ + +/* #undef DISABLE_MALLOC_WRAPPERS */ + +/* Define if using alloca.c. */ +/* #undef C_ALLOCA */ + +/* Define to one of _getb67, GETB67, getb67 for Cray-2 and Cray-YMP systems. + This function is required for alloca.c support on those systems. */ +/* #undef CRAY_STACKSEG_END */ + +/* Define if you have alloca, as a function or macro. */ +#define HAVE_ALLOCA 1 + +/* Define if you have and it should be used (not on Ultrix). */ +#define HAVE_ALLOCA_H 1 + +/* Define if major/minor/makedev is defined in */ +/* #undef MAJOR_IN_MAKEDEV */ + +/* Define if major/minor/makedev is defined in */ +#define MAJOR_IN_SYSMACROS 1 + +/* SYSTEM TYPES */ + +/* Define to `long' if doesn't define. */ +/* #undef off_t */ + +/* Define to `int' if doesn't define. */ +/* #undef mode_t */ + +/* Define to `int' if doesn't define. */ +/* #undef sigset_t */ + +/* Define to `int' if doesn't define. */ +/* #undef pid_t */ + +/* Define to `short' if doesn't define. */ +#define bits16_t short + +/* Define to `unsigned short' if doesn't define. */ +#define u_bits16_t unsigned short + +/* Define to `int' if doesn't define. */ +#define bits32_t int + +/* Define to `unsigned int' if doesn't define. */ +#define u_bits32_t unsigned int + +/* Define to `double' if doesn't define. */ +#define bits64_t char * + +/* Define to `unsigned int' if doesn't define. */ +/* #undef u_int */ + +/* Define to `unsigned long' if doesn't define. */ +/* #undef u_long */ + +/* Define to `int' if doesn't define. */ +/* #undef ptrdiff_t */ + +/* Define to `unsigned' if doesn't define. */ +/* #undef size_t */ + +/* Define to `int' if doesn't define. */ +/* #undef ssize_t */ + +/* Define to `long' if doesn't define. */ +/* #undef intmax_t */ + +/* Define to `unsigned long' if doesn't define. */ +/* #undef uintmax_t */ + +/* Define to integer type wide enough to hold a pointer if doesn't define. */ +/* #undef uintptr_t */ + +/* Define to `int' if doesn't define. */ +/* #undef uid_t */ + +/* Define to `long' if doesn't define. */ +/* #undef clock_t */ + +/* Define to `long' if doesn't define. */ +/* #undef time_t */ + +/* Define to `int' if doesn't define. */ +/* #undef gid_t */ + +/* Define to `unsigned int' if doesn't define. */ +/* #undef socklen_t */ + +/* Define to `int' if doesn't define. */ +/* #undef sig_atomic_t */ + +#define HAVE_MBSTATE_T 1 + +/* Define if you have quad_t in . */ +#define HAVE_QUAD_T 1 + +/* Define if you have wchar_t in . */ +#define HAVE_WCHAR_T 1 + +/* Define if you have wctype_t in . */ +#define HAVE_WCTYPE_T 1 + +/* Define if you have wint_t in . */ +#define HAVE_WINT_T 1 + +#define RLIMTYPE rlim_t + +/* Define to the type of elements in the array set by `getgroups'. + Usually this is either `int' or `gid_t'. */ +#define GETGROUPS_T gid_t + +/* Characteristics of the machine archictecture. */ + +/* If using the C implementation of alloca, define if you know the + direction of stack growth for your system; otherwise it will be + automatically deduced at run-time. + STACK_DIRECTION > 0 => grows toward higher addresses + STACK_DIRECTION < 0 => grows toward lower addresses + STACK_DIRECTION = 0 => direction of growth unknown + */ +/* #undef STACK_DIRECTION */ + +/* Define if the machine architecture is big-endian. */ +/* #undef WORDS_BIGENDIAN */ + +/* Check for the presence of certain non-function symbols in the system + libraries. */ + +/* Define if `sys_siglist' is declared by or . */ +#define HAVE_DECL_SYS_SIGLIST 0 +/* #undef SYS_SIGLIST_DECLARED */ + +/* Define if `_sys_siglist' is declared by or . */ +/* #undef UNDER_SYS_SIGLIST_DECLARED */ + +/* #undef HAVE_SYS_SIGLIST */ + +/* #undef HAVE_UNDER_SYS_SIGLIST */ + +#define HAVE_SYS_ERRLIST 1 + +/* #undef HAVE_TZNAME */ +/* #undef HAVE_DECL_TZNAME */ + +/* Characteristics of some of the system structures. */ + +#define HAVE_STRUCT_DIRENT_D_INO 1 + +/* #undef HAVE_STRUCT_DIRENT_D_FILENO */ + +/* #undef HAVE_STRUCT_DIRENT_D_NAMLEN */ + +/* #undef TIOCSTAT_IN_SYS_IOCTL */ + +#define FIONREAD_IN_SYS_IOCTL 1 + +/* #undef GWINSZ_IN_SYS_IOCTL */ + +#define STRUCT_WINSIZE_IN_SYS_IOCTL 1 + +/* #undef TM_IN_SYS_TIME */ + +/* #undef STRUCT_WINSIZE_IN_TERMIOS */ + +#define SPEED_T_IN_SYS_TYPES 1 + +/* #undef TERMIOS_LDISC */ + +/* #undef TERMIO_LDISC */ + +#define HAVE_STRUCT_STAT_ST_BLOCKS 1 + +#define HAVE_STRUCT_TM_TM_ZONE 1 +#define HAVE_TM_ZONE 1 + +#define HAVE_TIMEVAL 1 + +#define HAVE_STRUCT_TIMEZONE 1 + +#define WEXITSTATUS_OFFSET 8 + +#define HAVE_STRUCT_TIMESPEC 1 +#define TIME_H_DEFINES_STRUCT_TIMESPEC 1 +/* #undef SYS_TIME_H_DEFINES_STRUCT_TIMESPEC */ +/* #undef PTHREAD_H_DEFINES_STRUCT_TIMESPEC */ + +#define HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC 1 +#define TYPEOF_STRUCT_STAT_ST_ATIM_IS_STRUCT_TIMESPEC 1 +/* #undef HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC */ +/* #undef HAVE_STRUCT_STAT_ST_ATIMENSEC */ +/* #undef HAVE_STRUCT_STAT_ST_ATIM_ST__TIM_TV_NSEC */ + +/* Characteristics of definitions in the system header files. */ + +#define HAVE_GETPW_DECLS 1 + +/* #undef HAVE_RESOURCE */ + +/* #undef HAVE_LIBC_FNM_EXTMATCH */ + +/* Define if you have and it defines AUDIT_USER_TTY */ +#define HAVE_DECL_AUDIT_USER_TTY 0 + +#define HAVE_DECL_CONFSTR 1 + +#define HAVE_DECL_PRINTF 1 + +#define HAVE_DECL_SBRK 0 + +#define HAVE_DECL_STRCPY 1 + +#define HAVE_DECL_STRSIGNAL 1 + +#define HAVE_DECL_STRTOLD 1 + +/* #undef PRI_MACROS_BROKEN */ + +/* #undef STRTOLD_BROKEN */ + +/* Define if WCONTINUED is defined in system headers, but rejected by waitpid */ +/* #undef WCONTINUED_BROKEN */ + +/* These are checked with BASH_CHECK_DECL */ + +#define HAVE_DECL_STRTOIMAX 1 +#define HAVE_DECL_STRTOL 1 +#define HAVE_DECL_STRTOLL 1 +#define HAVE_DECL_STRTOUL 1 +#define HAVE_DECL_STRTOULL 1 +#define HAVE_DECL_STRTOUMAX 1 + +/* Characteristics of system calls and C library functions. */ + +/* Define if the `getpgrp' function takes no argument. */ +#define GETPGRP_VOID 1 + +#define NAMED_PIPES_MISSING 1 + +/* #undef OPENDIR_NOT_ROBUST */ + +#define PGRP_PIPE 1 + +/* #undef STAT_MACROS_BROKEN */ + +/* #undef ULIMIT_MAXFDS */ + +#define CAN_REDEFINE_GETENV 1 + +#define HAVE_STD_PUTENV 1 + +#define HAVE_STD_UNSETENV 1 + +#define HAVE_PRINTF_A_FORMAT 1 + +/* Define if you have and nl_langinfo(CODESET). */ +#define HAVE_LANGINFO_CODESET 1 + +/* Characteristics of properties exported by the kernel. */ + +/* Define if the kernel can exec files beginning with #! */ +#define HAVE_HASH_BANG_EXEC 1 + +/* Define if you have the /dev/fd devices to map open files into the file system. */ +/* #define HAVE_DEV_FD 1 */ + +/* Defined to /dev/fd or /proc/self/fd (linux). */ +#define DEV_FD_PREFIX "/dev/fd/" + +/* Define if you have the /dev/stdin device. */ +#define HAVE_DEV_STDIN 1 + +/* The type of iconv's `inbuf' argument */ +#define ICONV_CONST + +/* Type and behavior of signal handling functions. */ + +/* #undef MUST_REINSTALL_SIGHANDLERS */ + +/* #undef HAVE_BSD_SIGNALS */ + +#define HAVE_POSIX_SIGNALS 1 + +/* #undef HAVE_USG_SIGHOLD */ + +/* #undef UNUSABLE_RT_SIGNALS */ + +/* Presence of system and C library functions. */ + +/* Define if you have the arc4random function. */ +/* #undef HAVE_ARC4RANDOM */ + +/* Define if you have the asprintf function. */ +#define HAVE_ASPRINTF 1 + +/* Define if you have the bcopy function. */ +#define HAVE_BCOPY 1 + +/* Define if you have the bzero function. */ +#define HAVE_BZERO 1 + +/* Define if you have the chown function. */ +#define HAVE_CHOWN 1 + +/* Define if you have the confstr function. */ +#define HAVE_CONFSTR 1 + +/* Define if you have the dlclose function. */ +/* #undef HAVE_DLCLOSE */ + +/* Define if you have the dlopen function. */ +/* #undef HAVE_DLOPEN */ + +/* Define if you have the dlsym function. */ +/* #undef HAVE_DLSYM */ + +/* Define if you don't have vprintf but do have _doprnt. */ +/* #undef HAVE_DOPRNT */ + +/* Define if you have the dprintf function. */ +#define HAVE_DPRINTF 1 + +/* Define if you have the dup2 function. */ +#define HAVE_DUP2 1 + +/* Define if you have the eaccess function. */ +#define HAVE_EACCESS 1 + +/* Define if you have the faccessat function. */ +#define HAVE_FACCESSAT 1 + +/* Define if you have the fcntl function. */ +#define HAVE_FCNTL 1 + +/* Define if you have the fnmatch function. */ +#define HAVE_FNMATCH 1 + +/* Can fnmatch be used as a fallback to match [=equiv=] with collation weights? */ +#define FNMATCH_EQUIV_FALLBACK 0 + +/* Define if you have the fpurge/__fpurge function. */ +#define HAVE_FPURGE 1 +#define HAVE___FPURGE 1 +#define HAVE_DECL_FPURGE 1 + +/* Define if you have the getaddrinfo function. */ +#define HAVE_GETADDRINFO 1 + +/* Define if you have the getcwd function. */ +#define HAVE_GETCWD 1 + +/* Define if you have the getentropy function. */ +#define HAVE_GETENTROPY 1 + +/* Define if you have the getdtablesize function. */ +#define HAVE_GETDTABLESIZE 1 + +/* Define if you have the getgroups function. */ +#define HAVE_GETGROUPS 1 + +/* Define if you have the gethostbyname function. */ +#define HAVE_GETHOSTBYNAME 1 + +/* Define if you have the gethostname function. */ +#define HAVE_GETHOSTNAME 1 + +/* Define if you have the getpagesize function. */ +#define HAVE_GETPAGESIZE 1 + +/* Define if you have the getpeername function. */ +#define HAVE_GETPEERNAME 1 + +/* Define if you have the getpwent function. */ +#define HAVE_GETPWENT 1 + +/* Define if you have the getpwnam function. */ +#define HAVE_GETPWNAM 1 + +/* Define if you have the getpwuid function. */ +#define HAVE_GETPWUID 1 + +/* Define if you have the getrandom function. */ +#define HAVE_GETRANDOM 1 + +/* Define if you have the getrlimit function. */ +#define HAVE_GETRLIMIT 1 + +/* Define if you have the getrusage function. */ +#define HAVE_GETRUSAGE 1 + +/* Define if you have the getservbyname function. */ +#define HAVE_GETSERVBYNAME 1 + +/* Define if you have the getservent function. */ +#define HAVE_GETSERVENT 1 + +/* Define if you have the gettimeofday function. */ +#define HAVE_GETTIMEOFDAY 1 + +/* Define if you have the getwd function. */ +/* #undef HAVE_GETWD */ + +/* Define if you have the iconv function. */ +#define HAVE_ICONV 1 + +/* Define if you have the imaxdiv function. */ +#define HAVE_IMAXDIV 1 + +/* Define if you have the inet_aton function. */ +#define HAVE_INET_ATON 1 + +/* Define if you have the isascii function. */ +#define HAVE_ISASCII 1 + +/* Define if you have the isblank function. */ +#define HAVE_ISBLANK 1 + +/* Define if you have the isgraph function. */ +#define HAVE_ISGRAPH 1 + +/* Define if you have the isprint function. */ +#define HAVE_ISPRINT 1 + +/* Define if you have the isspace function. */ +#define HAVE_ISSPACE 1 + +/* Define if you have the iswctype function. */ +#define HAVE_ISWCTYPE 1 + +/* Define if you have the iswlower function. */ +#define HAVE_ISWLOWER 1 + +/* Define if you have the iswupper function. */ +#define HAVE_ISWUPPER 1 + +/* Define if you have the isxdigit function. */ +#define HAVE_ISXDIGIT 1 + +/* Define if you have the kill function. */ +#define HAVE_KILL 1 + +/* Define if you have the killpg function. */ +#define HAVE_KILLPG 1 + +/* Define if you have the lstat function. */ +#define HAVE_LSTAT 1 + +/* Define if you have the locale_charset function. */ +/* #undef HAVE_LOCALE_CHARSET */ + +/* Define if you have the mbrlen function. */ +#define HAVE_MBRLEN 1 + +/* Define if you have the mbrtowc function. */ +#define HAVE_MBRTOWC 1 + +/* Define if you have the mbscasecmp function. */ +/* #undef HAVE_MBSCASECMP */ + +/* Define if you have the mbschr function. */ +/* #undef HAVE_MBSCHR */ + +/* Define if you have the mbscmp function. */ +/* #undef HAVE_MBSCMP */ + +/* Define if you have the mbsnrtowcs function. */ +#define HAVE_MBSNRTOWCS 1 + +/* Define if you have the mbsrtowcs function. */ +#define HAVE_MBSRTOWCS 1 + +/* Define if you have the memmove function. */ +#define HAVE_MEMMOVE 1 + +/* Define if you have the memset function. */ +#define HAVE_MEMSET 1 + +/* Define if you have the mkdtemp function. */ +#define HAVE_MKDTEMP 1 + +/* Define if you have the mkfifo function. */ +/* #undef HAVE_MKFIFO */ + +/* Define if you have the mkstemp function. */ +#define HAVE_MKSTEMP 1 + +/* Define if you have the pathconf function. */ +#define HAVE_PATHCONF 1 + +/* Define if you have the pselect function. */ +#define HAVE_PSELECT 1 + +/* Define if you have the putenv function. */ +#define HAVE_PUTENV 1 + +/* Define if you have the raise function. */ +#define HAVE_RAISE 1 + +/* Define if you have the random function. */ +#define HAVE_RANDOM 1 + +/* Define if you have the readlink function. */ +#define HAVE_READLINK 1 + +/* Define if you have the regcomp function. */ +#define HAVE_REGCOMP 1 + +/* Define if you have the regexec function. */ +#define HAVE_REGEXEC 1 + +/* Define if you have the rename function. */ +#define HAVE_RENAME 1 + +/* Define if you have the sbrk function. */ +/* #undef HAVE_SBRK */ + +/* Define if you have the select function. */ +#define HAVE_SELECT 1 + +/* Define if you have the setdtablesize function. */ +/* #undef HAVE_SETDTABLESIZE */ + +/* Define if you have the setenv function. */ +#define HAVE_SETENV 1 + +/* Define if you have the setitimer function. */ +#define HAVE_SETITIMER 1 + +/* Define if you have the setlinebuf function. */ +#define HAVE_SETLINEBUF 1 + +/* Define if you have the setlocale function. */ +#define HAVE_SETLOCALE 1 + +/* Define if you have the setostype function. */ +/* #undef HAVE_SETOSTYPE */ + +/* Define if you have the setregid function. */ +/* #undef HAVE_SETREGID */ +#define HAVE_DECL_SETREGID 1 + +/* Define if you have the setregid function. */ +#define HAVE_SETRESGID 1 +/* #undef HAVE_DECL_SETRESGID */ + +/* Define if you have the setresuid function. */ +#define HAVE_SETRESUID 1 +/* #undef HAVE_DECL_SETRESUID */ + +/* Define if you have the setvbuf function. */ +#define HAVE_SETVBUF 1 + +/* Define if you have the siginterrupt function. */ +#define HAVE_SIGINTERRUPT 1 + +/* Define if you have the POSIX.1-style sigsetjmp function. */ +#define HAVE_POSIX_SIGSETJMP 1 + +/* Define if you have the snprintf function. */ +#define HAVE_SNPRINTF 1 + +/* Define if you have the strcasecmp function. */ +#define HAVE_STRCASECMP 1 + +/* Define if you have the strcasestr function. */ +#define HAVE_STRCASESTR 1 + +/* Define if you have the strchr function. */ +#define HAVE_STRCHR 1 + +/* Define if you have the strchrnul function. */ +#define HAVE_STRCHRNUL 1 + +/* Define if you have the strcoll function. */ +#define HAVE_STRCOLL 1 + +/* Define if you have the strerror function. */ +#define HAVE_STRERROR 1 + +/* Define if you have the strftime function. */ +#define HAVE_STRFTIME 1 + +/* Define if you have the strnlen function. */ +#define HAVE_STRNLEN 1 + +/* Define if you have the strpbrk function. */ +#define HAVE_STRPBRK 1 + +/* Define if you have the strstr function. */ +#define HAVE_STRSTR 1 + +/* Define if you have the strtod function. */ +#define HAVE_STRTOD 1 + +/* Define if you have the strtoimax function. */ +#define HAVE_STRTOIMAX 1 + +/* Define if you have the strtol function. */ +#define HAVE_STRTOL 1 + +/* Define if you have the strtoll function. */ +#define HAVE_STRTOLL 1 + +/* Define if you have the strtoul function. */ +#define HAVE_STRTOUL 1 + +/* Define if you have the strtoull function. */ +#define HAVE_STRTOULL 1 + +/* Define if you have the strtoumax function. */ +#define HAVE_STRTOUMAX 1 + +/* Define if you have the strsignal function or macro. */ +#define HAVE_STRSIGNAL 1 + +/* Define if you have the sysconf function. */ +#define HAVE_SYSCONF 1 + +/* Define if you have the syslog function. */ +#define HAVE_SYSLOG 1 + +/* Define if you have the tcgetattr function. */ +#define HAVE_TCGETATTR 1 + +/* Define if you have the tcgetpgrp function. */ +#define HAVE_TCGETPGRP 1 + +/* Define if you have the times function. */ +#define HAVE_TIMES 1 + +/* Define if you have the towlower function. */ +#define HAVE_TOWLOWER 1 + +/* Define if you have the towupper function. */ +#define HAVE_TOWUPPER 1 + +/* Define if you have the ttyname function. */ +#define HAVE_TTYNAME 1 + +/* Define if you have the tzset function. */ +#define HAVE_TZSET 1 + +/* Define if you have the ulimit function. */ +/* #undef HAVE_ULIMIT */ + +/* Define if you have the uname function. */ +#define HAVE_UNAME 1 + +/* Define if you have the unsetenv function. */ +#define HAVE_UNSETENV 1 + +/* Define if you have the vasprintf function. */ +#define HAVE_VASPRINTF 1 + +/* Define if you have the vprintf function. */ +#define HAVE_VPRINTF 1 + +/* Define if you have the vsnprintf function. */ +#define HAVE_VSNPRINTF 1 + +/* Define if you have the waitpid function. */ +#define HAVE_WAITPID 1 + +/* Define if you have the wait3 function. */ +#define HAVE_WAIT3 1 + +/* Define if you have the wcrtomb function. */ +#define HAVE_WCRTOMB 1 + +/* Define if you have the wcscoll function. */ +#define HAVE_WCSCOLL 1 + +/* Define if you have the wcsdup function. */ +#define HAVE_WCSDUP 1 + +/* Define if you have the wctype function. */ +#define HAVE_WCTYPE 1 + +/* Define if you have the wcswidth function. */ +#define HAVE_WCSWIDTH 1 + +/* Define if you have the wcwidth function. */ +#define HAVE_WCWIDTH 1 + +/* and if it works */ +/* #undef WCWIDTH_BROKEN */ + +/* Presence of certain system include files. */ + +/* Define if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* Define if you have the header file. */ +#define HAVE_DIRENT_H 1 + +/* Define if you have the header file. */ +#define HAVE_DLFCN_H 1 + +/* Define if you have the header file. */ +#define HAVE_GRP_H 1 + +/* Define if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define if you have the header file. */ +#define HAVE_LANGINFO_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_LIBAUDIT_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_LIBINTL_H */ + +/* Define if you have the header file. */ +#define HAVE_LIMITS_H 1 + +/* Define if you have the header file. */ +#define HAVE_LOCALE_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_MBSTR_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_NDIR_H */ + +/* Define if you have the header file. */ +#define HAVE_NETDB_H 1 + +/* Define if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define if you have the header file. */ +#define HAVE_PWD_H 1 + +/* Define if you have the header file. */ +#define HAVE_REGEX_H 1 + +/* Define if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define if you have the header file. */ +#define HAVE_STDARG_H 1 + +/* Define if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define if you have the header file. */ +#define HAVE_STDBOOL_H 1 + +/* Define if you have the header file. */ +#define HAVE_STDDEF_H 1 + +/* Define if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYSLOG_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define if you have the header file. */ +#define HAVE_SYS_FILE_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_MMAN_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_PTE_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_PTEM_H */ + +/* Define if you have the header file. */ +#define HAVE_SYS_RANDOM_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_RESOURCE_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_SYS_STREAM_H */ + +/* Define if you have */ +#define HAVE_SYS_TIME_H 1 + +/* Define if you have */ +#define HAVE_SYS_TIMES_H 1 + +/* Define if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define if you have that is POSIX.1 compatible. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_TERMCAP_H */ + +/* Define if you have the header file. */ +/* #undef HAVE_TERMIO_H */ + +/* Define if you have the header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_ULIMIT_H */ + +/* Define if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define if you have the header file. */ +/* #undef HAVE_VARARGS_H */ + +/* Define if you have the header file. */ +#define HAVE_WCHAR_H 1 + +/* Define if you have the header file. */ +#define HAVE_WCTYPE_H 1 + +/* Presence of certain system libraries. */ + +/* #undef HAVE_LIBDL */ + +/* #undef HAVE_LIBSUN */ + +/* #undef HAVE_LIBSOCKET */ + +/* Are we running the GNU C library, version 2.1 or later? */ +/* #undef GLIBC21 */ + +/* Are we running SVR5 (UnixWare 7)? */ +/* #undef SVR5 */ + +/* Are we running SVR4.2? */ +/* #undef SVR4_2 */ + +/* Are we running some version of SVR4? */ +/* #undef SVR4 */ + +/* Define if job control is unusable or unsupported. */ +/* #undef JOB_CONTROL_MISSING */ + +/* Do we need to define _KERNEL to get the RLIMIT_* defines from + ? */ +/* #undef RLIMIT_NEEDS_KERNEL */ + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define for large files on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +/* Do strcoll(3) and strcmp(3) give different results in the default locale? */ +/* #undef STRCOLL_BROKEN */ + +/* #undef DUP2_BROKEN */ + +/* #undef GETCWD_BROKEN */ + +/* #undef DEV_FD_STAT_BROKEN */ + +/* An array implementation that prioritizes speed (O(1) access) over space, + in array2.c */ +/* #undef ALT_ARRAY_IMPLEMENTATION */ + +/* Support for $"..." translatable strings. */ +#define TRANSLATABLE_STRINGS 1 + +/* Additional defines for configuring lib/intl, maintained by autoscan/autoheader */ + +/* Define if you have the header file. */ +/* #undef HAVE_ARGZ_H */ + +/* Define if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define if you have the header file. */ +#define HAVE_MALLOC_H 1 + +/* Define if you have the header file. */ +#define HAVE_STDIO_EXT_H 1 + +/* Define if you have the `dcgettext' function. */ +/* #undef HAVE_DCGETTEXT */ + +/* Define if you have the `localeconv' function. */ +#define HAVE_LOCALECONV 1 + +/* Define if your system has a working `malloc' function. */ +/* #undef HAVE_MALLOC */ + +/* Define if you have the `mempcpy' function. */ +#define HAVE_MEMPCPY 1 + +/* Define if you have a working `mmap' system call. */ +/* #undef HAVE_MMAP */ + +/* Define if you have the `mremap' function. */ +/* #undef HAVE_MREMAP */ + +/* Define if you have the `munmap' function. */ +#define HAVE_MUNMAP 1 + +/* Define if you have the `nl_langinfo' function. */ +/* #undef HAVE_NL_LANGINFO */ + +/* Define if you have the `stpcpy' function. */ +#define HAVE_STPCPY 1 + +/* Define if you have the `strcspn' function. */ +#define HAVE_STRCSPN 1 + +/* Define if you have the `strdup' function. */ +#define HAVE_STRDUP 1 + +/* Define if you have the `__argz_count' function. */ +/* #undef HAVE___ARGZ_COUNT */ + +/* Define if you have the `__argz_next' function. */ +/* #undef HAVE___ARGZ_NEXT */ + +/* Define if you have the `__argz_stringify' function. */ +/* #undef HAVE___ARGZ_STRINGIFY */ + +/* End additions for lib/intl */ + +#include "config-bot.h" + +#endif /* _CONFIG_H_ */ diff --git a/third_party/bash/conftypes.h b/third_party/bash/conftypes.h new file mode 100644 index 000000000..1c8c5480a --- /dev/null +++ b/third_party/bash/conftypes.h @@ -0,0 +1,58 @@ +/* conftypes.h -- defines for build and host system. */ + +/* Copyright (C) 2001, 2005, 2008,2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_CONFTYPES_H_) +#define _CONFTYPES_H_ + +/* Placeholder for future modifications if cross-compiling or building a + `fat' binary, e.g. on Apple Rhapsody. These values are used in multiple + files, so they appear here. */ +#if !defined (RHAPSODY) && !defined (MACOSX) +# define HOSTTYPE CONF_HOSTTYPE +# define OSTYPE CONF_OSTYPE +# define MACHTYPE CONF_MACHTYPE +#else /* RHAPSODY */ +# if defined(__powerpc__) || defined(__ppc__) +# define HOSTTYPE "powerpc" +# elif defined(__i386__) +# define HOSTTYPE "i386" +# else +# define HOSTTYPE CONF_HOSTTYPE +# endif + +# define OSTYPE CONF_OSTYPE +# define VENDOR CONF_VENDOR + +# define MACHTYPE HOSTTYPE "-" VENDOR "-" OSTYPE +#endif /* RHAPSODY */ + +#ifndef HOSTTYPE +# define HOSTTYPE "unknown" +#endif + +#ifndef OSTYPE +# define OSTYPE "unknown" +#endif + +#ifndef MACHTYPE +# define MACHTYPE "unknown" +#endif + +#endif /* _CONFTYPES_H_ */ diff --git a/third_party/bash/copy_cmd.c b/third_party/bash/copy_cmd.c new file mode 100644 index 000000000..758ff238a --- /dev/null +++ b/third_party/bash/copy_cmd.c @@ -0,0 +1,459 @@ +/* copy_command.c -- copy a COMMAND structure. This is needed + primarily for making function definitions, but I'm not sure + that anyone else will need it. */ + +/* Copyright (C) 1987-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#include "shell.h" + +static PATTERN_LIST *copy_case_clause PARAMS((PATTERN_LIST *)); +static PATTERN_LIST *copy_case_clauses PARAMS((PATTERN_LIST *)); +static FOR_COM *copy_for_command PARAMS((FOR_COM *)); +#if defined (ARITH_FOR_COMMAND) +static ARITH_FOR_COM *copy_arith_for_command PARAMS((ARITH_FOR_COM *)); +#endif +static GROUP_COM *copy_group_command PARAMS((GROUP_COM *)); +static SUBSHELL_COM *copy_subshell_command PARAMS((SUBSHELL_COM *)); +static COPROC_COM *copy_coproc_command PARAMS((COPROC_COM *)); +static CASE_COM *copy_case_command PARAMS((CASE_COM *)); +static WHILE_COM *copy_while_command PARAMS((WHILE_COM *)); +static IF_COM *copy_if_command PARAMS((IF_COM *)); +#if defined (DPAREN_ARITHMETIC) +static ARITH_COM *copy_arith_command PARAMS((ARITH_COM *)); +#endif +#if defined (COND_COMMAND) +static COND_COM *copy_cond_command PARAMS((COND_COM *)); +#endif +static SIMPLE_COM *copy_simple_command PARAMS((SIMPLE_COM *)); + +WORD_DESC * +copy_word (w) + WORD_DESC *w; +{ + WORD_DESC *new_word; + + new_word = make_bare_word (w->word); + new_word->flags = w->flags; + return (new_word); +} + +/* Copy the chain of words in LIST. Return a pointer to + the new chain. */ +WORD_LIST * +copy_word_list (list) + WORD_LIST *list; +{ + WORD_LIST *new_list, *tl; + + for (new_list = tl = (WORD_LIST *)NULL; list; list = list->next) + { + if (new_list == 0) + new_list = tl = make_word_list (copy_word (list->word), new_list); + else + { + tl->next = make_word_list (copy_word (list->word), (WORD_LIST *)NULL); + tl = tl->next; + } + } + + return (new_list); +} + +static PATTERN_LIST * +copy_case_clause (clause) + PATTERN_LIST *clause; +{ + PATTERN_LIST *new_clause; + + new_clause = (PATTERN_LIST *)xmalloc (sizeof (PATTERN_LIST)); + new_clause->patterns = copy_word_list (clause->patterns); + new_clause->action = copy_command (clause->action); + new_clause->flags = clause->flags; + return (new_clause); +} + +static PATTERN_LIST * +copy_case_clauses (clauses) + PATTERN_LIST *clauses; +{ + PATTERN_LIST *new_list, *new_clause; + + for (new_list = (PATTERN_LIST *)NULL; clauses; clauses = clauses->next) + { + new_clause = copy_case_clause (clauses); + new_clause->next = new_list; + new_list = new_clause; + } + return (REVERSE_LIST (new_list, PATTERN_LIST *)); +} + +/* Copy a single redirect. */ +REDIRECT * +copy_redirect (redirect) + REDIRECT *redirect; +{ + REDIRECT *new_redirect; + + new_redirect = (REDIRECT *)xmalloc (sizeof (REDIRECT)); +#if 0 + FASTCOPY ((char *)redirect, (char *)new_redirect, (sizeof (REDIRECT))); +#else + *new_redirect = *redirect; /* let the compiler do the fast structure copy */ +#endif + + if (redirect->rflags & REDIR_VARASSIGN) + new_redirect->redirector.filename = copy_word (redirect->redirector.filename); + + switch (redirect->instruction) + { + case r_reading_until: + case r_deblank_reading_until: + new_redirect->here_doc_eof = redirect->here_doc_eof ? savestring (redirect->here_doc_eof) : 0; + /*FALLTHROUGH*/ + case r_reading_string: + case r_appending_to: + case r_output_direction: + case r_input_direction: + case r_inputa_direction: + case r_err_and_out: + case r_append_err_and_out: + case r_input_output: + case r_output_force: + case r_duplicating_input_word: + case r_duplicating_output_word: + case r_move_input_word: + case r_move_output_word: + new_redirect->redirectee.filename = copy_word (redirect->redirectee.filename); + break; + case r_duplicating_input: + case r_duplicating_output: + case r_move_input: + case r_move_output: + case r_close_this: + break; + } + return (new_redirect); +} + +REDIRECT * +copy_redirects (list) + REDIRECT *list; +{ + REDIRECT *new_list, *temp; + + for (new_list = (REDIRECT *)NULL; list; list = list->next) + { + temp = copy_redirect (list); + temp->next = new_list; + new_list = temp; + } + return (REVERSE_LIST (new_list, REDIRECT *)); +} + +static FOR_COM * +copy_for_command (com) + FOR_COM *com; +{ + FOR_COM *new_for; + + new_for = (FOR_COM *)xmalloc (sizeof (FOR_COM)); + new_for->flags = com->flags; + new_for->line = com->line; + new_for->name = copy_word (com->name); + new_for->map_list = copy_word_list (com->map_list); + new_for->action = copy_command (com->action); + return (new_for); +} + +#if defined (ARITH_FOR_COMMAND) +static ARITH_FOR_COM * +copy_arith_for_command (com) + ARITH_FOR_COM *com; +{ + ARITH_FOR_COM *new_arith_for; + + new_arith_for = (ARITH_FOR_COM *)xmalloc (sizeof (ARITH_FOR_COM)); + new_arith_for->flags = com->flags; + new_arith_for->line = com->line; + new_arith_for->init = copy_word_list (com->init); + new_arith_for->test = copy_word_list (com->test); + new_arith_for->step = copy_word_list (com->step); + new_arith_for->action = copy_command (com->action); + return (new_arith_for); +} +#endif /* ARITH_FOR_COMMAND */ + +static GROUP_COM * +copy_group_command (com) + GROUP_COM *com; +{ + GROUP_COM *new_group; + + new_group = (GROUP_COM *)xmalloc (sizeof (GROUP_COM)); + new_group->command = copy_command (com->command); + return (new_group); +} + +static SUBSHELL_COM * +copy_subshell_command (com) + SUBSHELL_COM *com; +{ + SUBSHELL_COM *new_subshell; + + new_subshell = (SUBSHELL_COM *)xmalloc (sizeof (SUBSHELL_COM)); + new_subshell->command = copy_command (com->command); + new_subshell->flags = com->flags; + new_subshell->line = com->line; + return (new_subshell); +} + +static COPROC_COM * +copy_coproc_command (com) + COPROC_COM *com; +{ + COPROC_COM *new_coproc; + + new_coproc = (COPROC_COM *)xmalloc (sizeof (COPROC_COM)); + new_coproc->name = savestring (com->name); + new_coproc->command = copy_command (com->command); + new_coproc->flags = com->flags; + return (new_coproc); +} + +static CASE_COM * +copy_case_command (com) + CASE_COM *com; +{ + CASE_COM *new_case; + + new_case = (CASE_COM *)xmalloc (sizeof (CASE_COM)); + new_case->flags = com->flags; + new_case->line = com->line; + new_case->word = copy_word (com->word); + new_case->clauses = copy_case_clauses (com->clauses); + return (new_case); +} + +static WHILE_COM * +copy_while_command (com) + WHILE_COM *com; +{ + WHILE_COM *new_while; + + new_while = (WHILE_COM *)xmalloc (sizeof (WHILE_COM)); + new_while->flags = com->flags; + new_while->test = copy_command (com->test); + new_while->action = copy_command (com->action); + return (new_while); +} + +static IF_COM * +copy_if_command (com) + IF_COM *com; +{ + IF_COM *new_if; + + new_if = (IF_COM *)xmalloc (sizeof (IF_COM)); + new_if->flags = com->flags; + new_if->test = copy_command (com->test); + new_if->true_case = copy_command (com->true_case); + new_if->false_case = com->false_case ? copy_command (com->false_case) : com->false_case; + return (new_if); +} + +#if defined (DPAREN_ARITHMETIC) +static ARITH_COM * +copy_arith_command (com) + ARITH_COM *com; +{ + ARITH_COM *new_arith; + + new_arith = (ARITH_COM *)xmalloc (sizeof (ARITH_COM)); + new_arith->flags = com->flags; + new_arith->exp = copy_word_list (com->exp); + new_arith->line = com->line; + + return (new_arith); +} +#endif + +#if defined (COND_COMMAND) +static COND_COM * +copy_cond_command (com) + COND_COM *com; +{ + COND_COM *new_cond; + + new_cond = (COND_COM *)xmalloc (sizeof (COND_COM)); + new_cond->flags = com->flags; + new_cond->line = com->line; + new_cond->type = com->type; + new_cond->op = com->op ? copy_word (com->op) : com->op; + new_cond->left = com->left ? copy_cond_command (com->left) : (COND_COM *)NULL; + new_cond->right = com->right ? copy_cond_command (com->right) : (COND_COM *)NULL; + + return (new_cond); +} +#endif + +static SIMPLE_COM * +copy_simple_command (com) + SIMPLE_COM *com; +{ + SIMPLE_COM *new_simple; + + new_simple = (SIMPLE_COM *)xmalloc (sizeof (SIMPLE_COM)); + new_simple->flags = com->flags; + new_simple->words = copy_word_list (com->words); + new_simple->redirects = com->redirects ? copy_redirects (com->redirects) : (REDIRECT *)NULL; + new_simple->line = com->line; + return (new_simple); +} + +FUNCTION_DEF * +copy_function_def_contents (old, new_def) + FUNCTION_DEF *old, *new_def; +{ + new_def->name = copy_word (old->name); + new_def->command = old->command ? copy_command (old->command) : old->command; + new_def->flags = old->flags; + new_def->line = old->line; + new_def->source_file = old->source_file ? savestring (old->source_file) : old->source_file; + return (new_def); +} + +FUNCTION_DEF * +copy_function_def (com) + FUNCTION_DEF *com; +{ + FUNCTION_DEF *new_def; + + new_def = (FUNCTION_DEF *)xmalloc (sizeof (FUNCTION_DEF)); + new_def = copy_function_def_contents (com, new_def); + return (new_def); +} + +/* Copy the command structure in COMMAND. Return a pointer to the + copy. Don't you forget to dispose_command () on this pointer + later! */ +COMMAND * +copy_command (command) + COMMAND *command; +{ + COMMAND *new_command; + + if (command == NULL) + return (command); + + new_command = (COMMAND *)xmalloc (sizeof (COMMAND)); + FASTCOPY ((char *)command, (char *)new_command, sizeof (COMMAND)); + new_command->flags = command->flags; + new_command->line = command->line; + + if (command->redirects) + new_command->redirects = copy_redirects (command->redirects); + + switch (command->type) + { + case cm_for: + new_command->value.For = copy_for_command (command->value.For); + break; + +#if defined (ARITH_FOR_COMMAND) + case cm_arith_for: + new_command->value.ArithFor = copy_arith_for_command (command->value.ArithFor); + break; +#endif + +#if defined (SELECT_COMMAND) + case cm_select: + new_command->value.Select = + (SELECT_COM *)copy_for_command ((FOR_COM *)command->value.Select); + break; +#endif + + case cm_group: + new_command->value.Group = copy_group_command (command->value.Group); + break; + + case cm_subshell: + new_command->value.Subshell = copy_subshell_command (command->value.Subshell); + break; + + case cm_coproc: + new_command->value.Coproc = copy_coproc_command (command->value.Coproc); + break; + + case cm_case: + new_command->value.Case = copy_case_command (command->value.Case); + break; + + case cm_until: + case cm_while: + new_command->value.While = copy_while_command (command->value.While); + break; + + case cm_if: + new_command->value.If = copy_if_command (command->value.If); + break; + +#if defined (DPAREN_ARITHMETIC) + case cm_arith: + new_command->value.Arith = copy_arith_command (command->value.Arith); + break; +#endif + +#if defined (COND_COMMAND) + case cm_cond: + new_command->value.Cond = copy_cond_command (command->value.Cond); + break; +#endif + + case cm_simple: + new_command->value.Simple = copy_simple_command (command->value.Simple); + break; + + case cm_connection: + { + CONNECTION *new_connection; + + new_connection = (CONNECTION *)xmalloc (sizeof (CONNECTION)); + new_connection->connector = command->value.Connection->connector; + new_connection->first = copy_command (command->value.Connection->first); + new_connection->second = copy_command (command->value.Connection->second); + new_command->value.Connection = new_connection; + break; + } + + case cm_function_def: + new_command->value.Function_def = copy_function_def (command->value.Function_def); + break; + } + return (new_command); +} diff --git a/third_party/bash/dispose_cmd.c b/third_party/bash/dispose_cmd.c new file mode 100644 index 000000000..c624605b5 --- /dev/null +++ b/third_party/bash/dispose_cmd.c @@ -0,0 +1,342 @@ +/* dispose_command.c -- dispose of a COMMAND structure. */ + +/* Copyright (C) 1987-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "shell.h" + +extern sh_obj_cache_t wdcache, wlcache; + +/* Dispose of the command structure passed. */ +void +dispose_command (command) + COMMAND *command; +{ + if (command == 0) + return; + + if (command->redirects) + dispose_redirects (command->redirects); + + switch (command->type) + { + case cm_for: +#if defined (SELECT_COMMAND) + case cm_select: +#endif + { + register FOR_COM *c; +#if defined (SELECT_COMMAND) + if (command->type == cm_select) + c = (FOR_COM *)command->value.Select; + else +#endif + c = command->value.For; + dispose_word (c->name); + dispose_words (c->map_list); + dispose_command (c->action); + free (c); + break; + } + +#if defined (ARITH_FOR_COMMAND) + case cm_arith_for: + { + register ARITH_FOR_COM *c; + + c = command->value.ArithFor; + dispose_words (c->init); + dispose_words (c->test); + dispose_words (c->step); + dispose_command (c->action); + free (c); + break; + } +#endif /* ARITH_FOR_COMMAND */ + + case cm_group: + { + dispose_command (command->value.Group->command); + free (command->value.Group); + break; + } + + case cm_subshell: + { + dispose_command (command->value.Subshell->command); + free (command->value.Subshell); + break; + } + + case cm_coproc: + { + free (command->value.Coproc->name); + dispose_command (command->value.Coproc->command); + free (command->value.Coproc); + break; + } + + case cm_case: + { + register CASE_COM *c; + PATTERN_LIST *t, *p; + + c = command->value.Case; + dispose_word (c->word); + + for (p = c->clauses; p; ) + { + dispose_words (p->patterns); + dispose_command (p->action); + t = p; + p = p->next; + free (t); + } + free (c); + break; + } + + case cm_until: + case cm_while: + { + register WHILE_COM *c; + + c = command->value.While; + dispose_command (c->test); + dispose_command (c->action); + free (c); + break; + } + + case cm_if: + { + register IF_COM *c; + + c = command->value.If; + dispose_command (c->test); + dispose_command (c->true_case); + dispose_command (c->false_case); + free (c); + break; + } + + case cm_simple: + { + register SIMPLE_COM *c; + + c = command->value.Simple; + dispose_words (c->words); + dispose_redirects (c->redirects); + free (c); + break; + } + + case cm_connection: + { + register CONNECTION *c; + + c = command->value.Connection; + dispose_command (c->first); + dispose_command (c->second); + free (c); + break; + } + +#if defined (DPAREN_ARITHMETIC) + case cm_arith: + { + register ARITH_COM *c; + + c = command->value.Arith; + dispose_words (c->exp); + free (c); + break; + } +#endif /* DPAREN_ARITHMETIC */ + +#if defined (COND_COMMAND) + case cm_cond: + { + register COND_COM *c; + + c = command->value.Cond; + dispose_cond_node (c); + break; + } +#endif /* COND_COMMAND */ + + case cm_function_def: + { + register FUNCTION_DEF *c; + + c = command->value.Function_def; + dispose_function_def (c); + break; + } + + default: + command_error ("dispose_command", CMDERR_BADTYPE, command->type, 0); + break; + } + free (command); +} + +#if defined (COND_COMMAND) +/* How to free a node in a conditional command. */ +void +dispose_cond_node (cond) + COND_COM *cond; +{ + if (cond) + { + if (cond->left) + dispose_cond_node (cond->left); + if (cond->right) + dispose_cond_node (cond->right); + if (cond->op) + dispose_word (cond->op); + free (cond); + } +} +#endif /* COND_COMMAND */ + +void +dispose_function_def_contents (c) + FUNCTION_DEF *c; +{ + dispose_word (c->name); + dispose_command (c->command); + FREE (c->source_file); +} + +void +dispose_function_def (c) + FUNCTION_DEF *c; +{ + dispose_function_def_contents (c); + free (c); +} + +/* How to free a WORD_DESC. */ +void +dispose_word (w) + WORD_DESC *w; +{ + FREE (w->word); + ocache_free (wdcache, WORD_DESC, w); +} + +/* Free a WORD_DESC, but not the word contained within. */ +void +dispose_word_desc (w) + WORD_DESC *w; +{ + w->word = 0; + ocache_free (wdcache, WORD_DESC, w); +} + +/* How to get rid of a linked list of words. A WORD_LIST. */ +void +dispose_words (list) + WORD_LIST *list; +{ + WORD_LIST *t; + + while (list) + { + t = list; + list = list->next; + dispose_word (t->word); +#if 0 + free (t); +#else + ocache_free (wlcache, WORD_LIST, t); +#endif + } +} + +#ifdef INCLUDE_UNUSED +/* How to dispose of an array of pointers to char. This is identical to + free_array in stringlib.c. */ +void +dispose_word_array (array) + char **array; +{ + register int count; + + if (array == 0) + return; + + for (count = 0; array[count]; count++) + free (array[count]); + + free (array); +} +#endif + +/* How to dispose of an list of redirections. A REDIRECT. */ +void +dispose_redirects (list) + REDIRECT *list; +{ + register REDIRECT *t; + + while (list) + { + t = list; + list = list->next; + + if (t->rflags & REDIR_VARASSIGN) + dispose_word (t->redirector.filename); + + switch (t->instruction) + { + case r_reading_until: + case r_deblank_reading_until: + free (t->here_doc_eof); + /*FALLTHROUGH*/ + case r_reading_string: + case r_output_direction: + case r_input_direction: + case r_inputa_direction: + case r_appending_to: + case r_err_and_out: + case r_append_err_and_out: + case r_input_output: + case r_output_force: + case r_duplicating_input_word: + case r_duplicating_output_word: + case r_move_input_word: + case r_move_output_word: + dispose_word (t->redirectee.filename); + /* FALLTHROUGH */ + default: + break; + } + free (t); + } +} diff --git a/third_party/bash/dispose_cmd.h b/third_party/bash/dispose_cmd.h new file mode 100644 index 000000000..6095d44a4 --- /dev/null +++ b/third_party/bash/dispose_cmd.h @@ -0,0 +1,40 @@ +/* dispose_cmd.h -- Functions appearing in dispose_cmd.c. */ + +/* Copyright (C) 1993-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_DISPOSE_CMD_H_) +#define _DISPOSE_CMD_H_ + +#include "stdc.h" + +extern void dispose_command PARAMS((COMMAND *)); +extern void dispose_word_desc PARAMS((WORD_DESC *)); +extern void dispose_word PARAMS((WORD_DESC *)); +extern void dispose_words PARAMS((WORD_LIST *)); +extern void dispose_word_array PARAMS((char **)); +extern void dispose_redirects PARAMS((REDIRECT *)); + +#if defined (COND_COMMAND) +extern void dispose_cond_node PARAMS((COND_COM *)); +#endif + +extern void dispose_function_def_contents PARAMS((FUNCTION_DEF *)); +extern void dispose_function_def PARAMS((FUNCTION_DEF *)); + +#endif /* !_DISPOSE_CMD_H_ */ diff --git a/third_party/bash/eaccess.c b/third_party/bash/eaccess.c new file mode 100644 index 000000000..106062018 --- /dev/null +++ b/third_party/bash/eaccess.c @@ -0,0 +1,244 @@ +/* eaccess.c - eaccess replacement for the shell, plus other access functions. */ + +/* Copyright (C) 2006-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if defined (HAVE_CONFIG_H) +# include "config.h" +#endif + +#include + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" + +#include +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#if !defined (_POSIX_VERSION) && defined (HAVE_SYS_FILE_H) +# include +#endif /* !_POSIX_VERSION */ +#include "posixstat.h" +#include "filecntl.h" + +#include "shell.h" + +#if !defined (R_OK) +#define R_OK 4 +#define W_OK 2 +#define X_OK 1 +#define F_OK 0 +#endif /* R_OK */ + +static int path_is_devfd PARAMS((const char *)); +static int sh_stataccess PARAMS((const char *, int)); +#if HAVE_DECL_SETREGID +static int sh_euidaccess PARAMS((const char *, int)); +#endif + +static int +path_is_devfd (path) + const char *path; +{ + if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0) + return 1; + else if (STREQN (path, "/dev/std", 8)) + { + if (STREQ (path+8, "in") || STREQ (path+8, "out") || STREQ (path+8, "err")) + return 1; + else + return 0; + } + else + return 0; +} + +/* A wrapper for stat () which disallows pathnames that are empty strings + and handles /dev/fd emulation on systems that don't have it. */ +int +sh_stat (path, finfo) + const char *path; + struct stat *finfo; +{ + static char *pbuf = 0; + + if (*path == '\0') + { + errno = ENOENT; + return (-1); + } + if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0) + { + /* If stating /dev/fd/n doesn't produce the same results as fstat of + FD N, then define DEV_FD_STAT_BROKEN */ +#if !defined (HAVE_DEV_FD) || defined (DEV_FD_STAT_BROKEN) + intmax_t fd; + int r; + + if (legal_number (path + 8, &fd) && fd == (int)fd) + { + r = fstat ((int)fd, finfo); + if (r == 0 || errno != EBADF) + return (r); + } + errno = ENOENT; + return (-1); +#else + /* If HAVE_DEV_FD is defined, DEV_FD_PREFIX is defined also, and has a + trailing slash. Make sure /dev/fd/xx really uses DEV_FD_PREFIX/xx. + On most systems, with the notable exception of linux, this is + effectively a no-op. */ + pbuf = xrealloc (pbuf, sizeof (DEV_FD_PREFIX) + strlen (path + 8)); + strcpy (pbuf, DEV_FD_PREFIX); + strcat (pbuf, path + 8); + return (stat (pbuf, finfo)); +#endif /* !HAVE_DEV_FD */ + } +#if !defined (HAVE_DEV_STDIN) + else if (STREQN (path, "/dev/std", 8)) + { + if (STREQ (path+8, "in")) + return (fstat (0, finfo)); + else if (STREQ (path+8, "out")) + return (fstat (1, finfo)); + else if (STREQ (path+8, "err")) + return (fstat (2, finfo)); + else + return (stat (path, finfo)); + } +#endif /* !HAVE_DEV_STDIN */ + return (stat (path, finfo)); +} + +/* Do the same thing access(2) does, but use the effective uid and gid, + and don't make the mistake of telling root that any file is + executable. This version uses stat(2). */ +static int +sh_stataccess (path, mode) + const char *path; + int mode; +{ + struct stat st; + + if (sh_stat (path, &st) < 0) + return (-1); + + if (current_user.euid == 0) + { + /* Root can read or write any file. */ + if ((mode & X_OK) == 0) + return (0); + + /* Root can execute any file that has any one of the execute + bits set. */ + if (st.st_mode & S_IXUGO) + return (0); + } + + if (st.st_uid == current_user.euid) /* owner */ + mode <<= 6; + else if (group_member (st.st_gid)) + mode <<= 3; + + if (st.st_mode & mode) + return (0); + + errno = EACCES; + return (-1); +} + +#if HAVE_DECL_SETREGID +/* Version to call when uid != euid or gid != egid. We temporarily swap + the effective and real uid and gid as appropriate. */ +static int +sh_euidaccess (path, mode) + const char *path; + int mode; +{ + int r, e; + + if (current_user.uid != current_user.euid) + setreuid (current_user.euid, current_user.uid); + if (current_user.gid != current_user.egid) + setregid (current_user.egid, current_user.gid); + + r = access (path, mode); + e = errno; + + if (current_user.uid != current_user.euid) + setreuid (current_user.uid, current_user.euid); + if (current_user.gid != current_user.egid) + setregid (current_user.gid, current_user.egid); + + errno = e; + return r; +} +#endif + +int +sh_eaccess (path, mode) + const char *path; + int mode; +{ + int ret; + + if (path_is_devfd (path)) + return (sh_stataccess (path, mode)); + +#if (defined (HAVE_FACCESSAT) && defined (AT_EACCESS)) || defined (HAVE_EACCESS) +# if defined (HAVE_FACCESSAT) && defined (AT_EACCESS) + ret = faccessat (AT_FDCWD, path, mode, AT_EACCESS); +# else /* HAVE_EACCESS */ /* FreeBSD */ + ret = eaccess (path, mode); /* XXX -- not always correct for X_OK */ +# endif /* HAVE_EACCESS */ +# if defined (__FreeBSD__) || defined (SOLARIS) || defined (_AIX) + if (ret == 0 && current_user.euid == 0 && mode == X_OK) + return (sh_stataccess (path, mode)); +# endif /* __FreeBSD__ || SOLARIS || _AIX */ + return ret; +#elif defined (EFF_ONLY_OK) /* SVR4(?), SVR4.2 */ + return access (path, mode|EFF_ONLY_OK); +#else + if (mode == F_OK) + return (sh_stataccess (path, mode)); + +# if HAVE_DECL_SETREGID + if (current_user.uid != current_user.euid || current_user.gid != current_user.egid) + return (sh_euidaccess (path, mode)); +# endif + + if (current_user.uid == current_user.euid && current_user.gid == current_user.egid) + { + ret = access (path, mode); +#if defined (__FreeBSD__) || defined (SOLARIS) + if (ret == 0 && current_user.euid == 0 && mode == X_OK) + return (sh_stataccess (path, mode)); +#endif + return ret; + } + + return (sh_stataccess (path, mode)); +#endif +} diff --git a/third_party/bash/error.c b/third_party/bash/error.c new file mode 100644 index 000000000..3e7a2d617 --- /dev/null +++ b/third_party/bash/error.c @@ -0,0 +1,537 @@ +/* error.c -- Functions for handling errors. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (PREFER_STDARG) +# include +#else +# include +#endif + +#include + +#include +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "flags.h" +#include "input.h" + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +extern int executing_line_number PARAMS((void)); + +#if defined (JOB_CONTROL) +extern pid_t shell_pgrp; +extern int give_terminal_to PARAMS((pid_t, int)); +#endif /* JOB_CONTROL */ + +#if defined (ARRAY_VARS) +extern const char * const bash_badsub_errmsg; +#endif + +static void error_prolog PARAMS((int)); + +/* The current maintainer of the shell. You change this in the + Makefile. */ +#if !defined (MAINTAINER) +#define MAINTAINER "bash-maintainers@gnu.org" +#endif + +const char * const the_current_maintainer = MAINTAINER; + +int gnu_error_format = 0; + +static void +error_prolog (print_lineno) + int print_lineno; +{ + char *ename; + int line; + + ename = get_name_for_error (); + line = (print_lineno && interactive_shell == 0) ? executing_line_number () : -1; + + if (line > 0) + fprintf (stderr, "%s:%s%d: ", ename, gnu_error_format ? "" : _(" line "), line); + else + fprintf (stderr, "%s: ", ename); +} + +/* Return the name of the shell or the shell script for error reporting. */ +char * +get_name_for_error () +{ + char *name; +#if defined (ARRAY_VARS) + SHELL_VAR *bash_source_v; + ARRAY *bash_source_a; +#endif + + name = (char *)NULL; + if (interactive_shell == 0) + { +#if defined (ARRAY_VARS) + bash_source_v = find_variable ("BASH_SOURCE"); + if (bash_source_v && array_p (bash_source_v) && + (bash_source_a = array_cell (bash_source_v))) + name = array_reference (bash_source_a, 0); + if (name == 0 || *name == '\0') /* XXX - was just name == 0 */ +#endif + name = dollar_vars[0]; + } + if (name == 0 && shell_name && *shell_name) + name = base_pathname (shell_name); + if (name == 0) +#if defined (PROGRAM) + name = PROGRAM; +#else + name = "bash"; +#endif + + return (name); +} + +/* Report an error having to do with FILENAME. This does not use + sys_error so the filename is not interpreted as a printf-style + format string. */ +void +file_error (filename) + const char *filename; +{ + report_error ("%s: %s", filename, strerror (errno)); +} + +void +#if defined (PREFER_STDARG) +programming_error (const char *format, ...) +#else +programming_error (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + char *h; + +#if defined (JOB_CONTROL) + give_terminal_to (shell_pgrp, 0); +#endif /* JOB_CONTROL */ + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + va_end (args); + +#if defined (HISTORY) + if (remember_on_history) + { + h = last_history_line (); + fprintf (stderr, _("last command: %s\n"), h ? h : "(null)"); + } +#endif + +#if 0 + fprintf (stderr, "Report this to %s\n", the_current_maintainer); +#endif + + fprintf (stderr, _("Aborting...")); + fflush (stderr); + + abort (); +} + +/* Print an error message and, if `set -e' has been executed, exit the + shell. Used in this file by file_error and programming_error. Used + outside this file mostly to report substitution and expansion errors, + and for bad invocation options. */ +void +#if defined (PREFER_STDARG) +report_error (const char *format, ...) +#else +report_error (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + error_prolog (1); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + + va_end (args); + if (exit_immediately_on_error) + { + if (last_command_exit_value == 0) + last_command_exit_value = EXECUTION_FAILURE; + exit_shell (last_command_exit_value); + } +} + +void +#if defined (PREFER_STDARG) +fatal_error (const char *format, ...) +#else +fatal_error (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + error_prolog (0); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + + va_end (args); + sh_exit (2); +} + +void +#if defined (PREFER_STDARG) +internal_error (const char *format, ...) +#else +internal_error (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + error_prolog (1); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + + va_end (args); +} + +void +#if defined (PREFER_STDARG) +internal_warning (const char *format, ...) +#else +internal_warning (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + error_prolog (1); + fprintf (stderr, _("warning: ")); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + + va_end (args); +} + +void +#if defined (PREFER_STDARG) +internal_inform (const char *format, ...) +#else +internal_inform (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + error_prolog (1); + /* TRANSLATORS: this is a prefix for informational messages. */ + fprintf (stderr, _("INFORM: ")); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + + va_end (args); +} + +void +#if defined (PREFER_STDARG) +internal_debug (const char *format, ...) +#else +internal_debug (format, va_alist) + const char *format; + va_dcl +#endif +{ +#ifdef DEBUG + va_list args; + + error_prolog (1); + fprintf (stderr, _("DEBUG warning: ")); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + + va_end (args); +#else + return; +#endif +} + +void +#if defined (PREFER_STDARG) +sys_error (const char *format, ...) +#else +sys_error (format, va_alist) + const char *format; + va_dcl +#endif +{ + int e; + va_list args; + + e = errno; + error_prolog (0); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, ": %s\n", strerror (e)); + + va_end (args); +} + +/* An error from the parser takes the general form + + shell_name: input file name: line number: message + + The input file name and line number are omitted if the shell is + currently interactive. If the shell is not currently interactive, + the input file name is inserted only if it is different from the + shell name. */ +void +#if defined (PREFER_STDARG) +parser_error (int lineno, const char *format, ...) +#else +parser_error (lineno, format, va_alist) + int lineno; + const char *format; + va_dcl +#endif +{ + va_list args; + char *ename, *iname; + + ename = get_name_for_error (); + iname = yy_input_name (); + + if (interactive) + fprintf (stderr, "%s: ", ename); + else if (interactive_shell) + fprintf (stderr, "%s: %s:%s%d: ", ename, iname, gnu_error_format ? "" : _(" line "), lineno); + else if (STREQ (ename, iname)) + fprintf (stderr, "%s:%s%d: ", ename, gnu_error_format ? "" : _(" line "), lineno); + else + fprintf (stderr, "%s: %s:%s%d: ", ename, iname, gnu_error_format ? "" : _(" line "), lineno); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + + va_end (args); + + if (exit_immediately_on_error) + exit_shell (last_command_exit_value = 2); +} + +#ifdef DEBUG +/* This assumes ASCII and is suitable only for debugging */ +char * +strescape (str) + const char *str; +{ + char *r, *result; + unsigned char *s; + + r = result = (char *)xmalloc (strlen (str) * 2 + 1); + + for (s = (unsigned char *)str; s && *s; s++) + { + if (*s < ' ') + { + *r++ = '^'; + *r++ = *s+64; + } + else if (*s == 127) + { + *r++ = '^'; + *r++ = '?'; + } + else + *r++ = *s; + } + + *r = '\0'; + return result; +} + +void +#if defined (PREFER_STDARG) +itrace (const char *format, ...) +#else +itrace (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + fprintf(stderr, "TRACE: pid %ld: ", (long)getpid()); + + SH_VA_START (args, format); + + vfprintf (stderr, format, args); + fprintf (stderr, "\n"); + + va_end (args); + + fflush(stderr); +} + +/* A trace function for silent debugging -- doesn't require a control + terminal. */ +void +#if defined (PREFER_STDARG) +trace (const char *format, ...) +#else +trace (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + static FILE *tracefp = (FILE *)NULL; + + if (tracefp == NULL) + tracefp = fopen("/tmp/bash-trace.log", "a+"); + + if (tracefp == NULL) + tracefp = stderr; + else + fcntl (fileno (tracefp), F_SETFD, 1); /* close-on-exec */ + + fprintf(tracefp, "TRACE: pid %ld: ", (long)getpid()); + + SH_VA_START (args, format); + + vfprintf (tracefp, format, args); + fprintf (tracefp, "\n"); + + va_end (args); + + fflush(tracefp); +} + +#endif /* DEBUG */ + +/* **************************************************************** */ +/* */ +/* Common error reporting */ +/* */ +/* **************************************************************** */ + + +static const char * const cmd_error_table[] = { + N_("unknown command error"), /* CMDERR_DEFAULT */ + N_("bad command type"), /* CMDERR_BADTYPE */ + N_("bad connector"), /* CMDERR_BADCONN */ + N_("bad jump"), /* CMDERR_BADJUMP */ + 0 +}; + +void +command_error (func, code, e, flags) + const char *func; + int code, e, flags; /* flags currently unused */ +{ + if (code > CMDERR_LAST) + code = CMDERR_DEFAULT; + + programming_error ("%s: %s: %d", func, _(cmd_error_table[code]), e); +} + +char * +command_errstr (code) + int code; +{ + if (code > CMDERR_LAST) + code = CMDERR_DEFAULT; + + return (_(cmd_error_table[code])); +} + +#ifdef ARRAY_VARS +void +err_badarraysub (s) + const char *s; +{ + report_error ("%s: %s", s, _(bash_badsub_errmsg)); +} +#endif + +void +err_unboundvar (s) + const char *s; +{ + report_error (_("%s: unbound variable"), s); +} + +void +err_readonly (s) + const char *s; +{ + report_error (_("%s: readonly variable"), s); +} diff --git a/third_party/bash/error.h b/third_party/bash/error.h new file mode 100644 index 000000000..785c1deb5 --- /dev/null +++ b/third_party/bash/error.h @@ -0,0 +1,82 @@ +/* error.h -- External declarations of functions appearing in error.c. */ + +/* Copyright (C) 1993-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_ERROR_H_) +#define _ERROR_H_ + +#include "stdc.h" + +/* Get the name of the shell or shell script for an error message. */ +extern char *get_name_for_error PARAMS((void)); + +/* Report an error having to do with FILENAME. */ +extern void file_error PARAMS((const char *)); + +/* Report a programmer's error, and abort. Pass REASON, and ARG1 ... ARG5. */ +extern void programming_error PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); + +/* General error reporting. Pass FORMAT and ARG1 ... ARG5. */ +extern void report_error PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); + +/* Error messages for parts of the parser that don't call report_syntax_error */ +extern void parser_error PARAMS((int, const char *, ...)) __attribute__((__format__ (printf, 2, 3))); + +/* Report an unrecoverable error and exit. Pass FORMAT and ARG1 ... ARG5. */ +extern void fatal_error PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); + +/* Report a system error, like BSD warn(3). */ +extern void sys_error PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); + +/* Report an internal error. */ +extern void internal_error PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); + +/* Report an internal warning. */ +extern void internal_warning PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); + +/* Report an internal warning for debugging purposes. */ +extern void internal_debug PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); + +/* Report an internal informational notice. */ +extern void internal_inform PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); + +/* Debugging functions, not enabled in released version. */ +extern char *strescape PARAMS((const char *)); +extern void itrace PARAMS((const char *, ...)) __attribute__ ((__format__ (printf, 1, 2))); +extern void trace PARAMS((const char *, ...)) __attribute__ ((__format__ (printf, 1, 2))); + +/* Report an error having to do with command parsing or execution. */ +extern void command_error PARAMS((const char *, int, int, int)); + +extern char *command_errstr PARAMS((int)); + +/* Specific error message functions that eventually call report_error or + internal_error. */ + +extern void err_badarraysub PARAMS((const char *)); +extern void err_unboundvar PARAMS((const char *)); +extern void err_readonly PARAMS((const char *)); + +#ifdef DEBUG +# define INTERNAL_DEBUG(x) internal_debug x +#else +# define INTERNAL_DEBUG(x) +#endif + +#endif /* !_ERROR_H_ */ diff --git a/third_party/bash/eval.c b/third_party/bash/eval.c new file mode 100644 index 000000000..24c5f04eb --- /dev/null +++ b/third_party/bash/eval.c @@ -0,0 +1,401 @@ +/* eval.c -- reading and evaluating commands. */ + +/* Copyright (C) 1996-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" +#include + +#include + +#include "bashintl.h" + +#include "shell.h" +#include "parser.h" +#include "flags.h" +#include "trap.h" + +#include "common.h" + +#include "input.h" +#include "execute_cmd.h" + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +static void send_pwd_to_eterm PARAMS((void)); +static sighandler alrm_catcher PARAMS((int)); + +/* Read and execute commands until EOF is reached. This assumes that + the input source has already been initialized. */ +int +reader_loop () +{ + int our_indirection_level; + COMMAND * volatile current_command; + + USE_VAR(current_command); + + current_command = (COMMAND *)NULL; + + our_indirection_level = ++indirection_level; + + if (just_one_command) + reset_readahead_token (); + + while (EOF_Reached == 0) + { + int code; + + code = setjmp_nosigs (top_level); + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + /* XXX - why do we set this every time through the loop? And why do + it if SIGINT is trapped in an interactive shell? */ + if (interactive_shell && signal_is_ignored (SIGINT) == 0 && signal_is_trapped (SIGINT) == 0) + set_signal_handler (SIGINT, sigint_sighandler); + + if (code != NOT_JUMPED) + { + indirection_level = our_indirection_level; + + switch (code) + { + /* Some kind of throw to top_level has occurred. */ + case ERREXIT: + if (exit_immediately_on_error) + reset_local_contexts (); /* not in a function */ + case FORCE_EOF: + case EXITPROG: + case EXITBLTIN: + current_command = (COMMAND *)NULL; + EOF_Reached = EOF; + goto exec_done; + + case DISCARD: + /* Make sure the exit status is reset to a non-zero value, but + leave existing non-zero values (e.g., > 128 on signal) + alone. */ + if (last_command_exit_value == 0) + set_exit_status (EXECUTION_FAILURE); + if (subshell_environment) + { + current_command = (COMMAND *)NULL; + EOF_Reached = EOF; + goto exec_done; + } + /* Obstack free command elements, etc. */ + if (current_command) + { + dispose_command (current_command); + current_command = (COMMAND *)NULL; + } + + restore_sigmask (); + break; + + default: + command_error ("reader_loop", CMDERR_BADJUMP, code, 0); + } + } + + executing = 0; + if (temporary_env) + dispose_used_env_vars (); + +#if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA) + /* Attempt to reclaim memory allocated with alloca (). */ + (void) alloca (0); +#endif + + if (read_command () == 0) + { + if (interactive_shell == 0 && read_but_dont_execute) + { + set_exit_status (last_command_exit_value); + dispose_command (global_command); + global_command = (COMMAND *)NULL; + } + else if (current_command = global_command) + { + global_command = (COMMAND *)NULL; + + /* If the shell is interactive, expand and display $PS0 after reading a + command (possibly a list or pipeline) and before executing it. */ + if (interactive && ps0_prompt) + { + char *ps0_string; + + ps0_string = decode_prompt_string (ps0_prompt); + if (ps0_string && *ps0_string) + { + fprintf (stderr, "%s", ps0_string); + fflush (stderr); + } + free (ps0_string); + } + + current_command_number++; + + executing = 1; + stdin_redir = 0; + + execute_command (current_command); + + exec_done: + QUIT; + + if (current_command) + { + dispose_command (current_command); + current_command = (COMMAND *)NULL; + } + } + } + else + { + /* Parse error, maybe discard rest of stream if not interactive. */ + if (interactive == 0) + EOF_Reached = EOF; + } + if (just_one_command) + EOF_Reached = EOF; + } + indirection_level--; + return (last_command_exit_value); +} + +/* Pretty print shell scripts */ +int +pretty_print_loop () +{ + COMMAND *current_command; + char *command_to_print; + int code; + int global_posix_mode, last_was_newline; + + global_posix_mode = posixly_correct; + last_was_newline = 0; + while (EOF_Reached == 0) + { + code = setjmp_nosigs (top_level); + if (code) + return (EXECUTION_FAILURE); + if (read_command() == 0) + { + current_command = global_command; + global_command = 0; + posixly_correct = 1; /* print posix-conformant */ + if (current_command && (command_to_print = make_command_string (current_command))) + { + printf ("%s\n", command_to_print); /* for now */ + last_was_newline = 0; + } + else if (last_was_newline == 0) + { + printf ("\n"); + last_was_newline = 1; + } + posixly_correct = global_posix_mode; + dispose_command (current_command); + } + else + return (EXECUTION_FAILURE); + } + + return (EXECUTION_SUCCESS); +} + +static sighandler +alrm_catcher(i) + int i; +{ + char *msg; + + msg = _("\007timed out waiting for input: auto-logout\n"); + write (1, msg, strlen (msg)); + + bash_logout (); /* run ~/.bash_logout if this is a login shell */ + jump_to_top_level (EXITPROG); + SIGRETURN (0); +} + +/* Send an escape sequence to emacs term mode to tell it the + current working directory. */ +static void +send_pwd_to_eterm () +{ + char *pwd, *f; + + f = 0; + pwd = get_string_value ("PWD"); + if (pwd == 0) + f = pwd = get_working_directory ("eterm"); + fprintf (stderr, "\032/%s\n", pwd); + free (f); +} + +#if defined (ARRAY_VARS) +/* Caller ensures that A has a non-zero number of elements */ +int +execute_array_command (a, v) + ARRAY *a; + void *v; +{ + char *tag; + char **argv; + int argc, i; + + tag = (char *)v; + argc = 0; + argv = array_to_argv (a, &argc); + for (i = 0; i < argc; i++) + { + if (argv[i] && argv[i][0]) + execute_variable_command (argv[i], tag); + } + strvec_dispose (argv); + return 0; +} +#endif + +static void +execute_prompt_command () +{ + char *command_to_execute; + SHELL_VAR *pcv; +#if defined (ARRAY_VARS) + ARRAY *pcmds; +#endif + + pcv = find_variable ("PROMPT_COMMAND"); + if (pcv == 0 || var_isset (pcv) == 0 || invisible_p (pcv)) + return; +#if defined (ARRAY_VARS) + if (array_p (pcv)) + { + if ((pcmds = array_cell (pcv)) && array_num_elements (pcmds) > 0) + execute_array_command (pcmds, "PROMPT_COMMAND"); + return; + } + else if (assoc_p (pcv)) + return; /* currently don't allow associative arrays here */ +#endif + + command_to_execute = value_cell (pcv); + if (command_to_execute && *command_to_execute) + execute_variable_command (command_to_execute, "PROMPT_COMMAND"); +} + +/* Call the YACC-generated parser and return the status of the parse. + Input is read from the current input stream (bash_input). yyparse + leaves the parsed command in the global variable GLOBAL_COMMAND. + This is where PROMPT_COMMAND is executed. */ +int +parse_command () +{ + int r; + + need_here_doc = 0; + run_pending_traps (); + + /* Allow the execution of a random command just before the printing + of each primary prompt. If the shell variable PROMPT_COMMAND + is set then its value (array or string) is the command(s) to execute. */ + /* The tests are a combination of SHOULD_PROMPT() and prompt_again() + from parse.y, which are the conditions under which the prompt is + actually printed. */ + if (interactive && bash_input.type != st_string && parser_expanding_alias() == 0) + { +#if defined (READLINE) + if (no_line_editing || (bash_input.type == st_stdin && parser_will_prompt ())) +#endif + execute_prompt_command (); + + if (running_under_emacs == 2) + send_pwd_to_eterm (); /* Yuck */ + } + + current_command_line_count = 0; + r = yyparse (); + + if (need_here_doc) + gather_here_documents (); + + return (r); +} + +/* Read and parse a command, returning the status of the parse. The command + is left in the globval variable GLOBAL_COMMAND for use by reader_loop. + This is where the shell timeout code is executed. */ +int +read_command () +{ + SHELL_VAR *tmout_var; + int tmout_len, result; + SigHandler *old_alrm; + + set_current_prompt_level (1); + global_command = (COMMAND *)NULL; + + /* Only do timeouts if interactive. */ + tmout_var = (SHELL_VAR *)NULL; + tmout_len = 0; + old_alrm = (SigHandler *)NULL; + + if (interactive) + { + tmout_var = find_variable ("TMOUT"); + + if (tmout_var && var_isset (tmout_var)) + { + tmout_len = atoi (value_cell (tmout_var)); + if (tmout_len > 0) + { + old_alrm = set_signal_handler (SIGALRM, alrm_catcher); + alarm (tmout_len); + } + } + } + + QUIT; + + current_command_line_count = 0; + result = parse_command (); + + if (interactive && tmout_var && (tmout_len > 0)) + { + alarm(0); + set_signal_handler (SIGALRM, old_alrm); + } + + return (result); +} diff --git a/third_party/bash/evalfile.c b/third_party/bash/evalfile.c new file mode 100644 index 000000000..0479a0e62 --- /dev/null +++ b/third_party/bash/evalfile.c @@ -0,0 +1,384 @@ +/* evalfile.c - read and evaluate commands from a file or file descriptor */ + +/* Copyright (C) 1996-2017 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashtypes.h" +#include "posixstat.h" +#include "filecntl.h" + +#include +#include +#include + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "parser.h" +#include "jobs.h" +#include "builtins.h" +#include "flags.h" +#include "input.h" +#include "execute_cmd.h" +#include "trap.h" + +#include "y.tab.h" + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +#include "typemax.h" + +#include "common.h" + +#if !defined (errno) +extern int errno; +#endif + +/* Flags for _evalfile() */ +#define FEVAL_ENOENTOK 0x001 +#define FEVAL_BUILTIN 0x002 +#define FEVAL_UNWINDPROT 0x004 +#define FEVAL_NONINT 0x008 +#define FEVAL_LONGJMP 0x010 +#define FEVAL_HISTORY 0x020 +#define FEVAL_CHECKBINARY 0x040 +#define FEVAL_REGFILE 0x080 +#define FEVAL_NOPUSHARGS 0x100 + +/* How many `levels' of sourced files we have. */ +int sourcelevel = 0; + +static int +_evalfile (filename, flags) + const char *filename; + int flags; +{ + volatile int old_interactive; + procenv_t old_return_catch; + int return_val, fd, result, pflags, i, nnull; + ssize_t nr; /* return value from read(2) */ + char *string; + struct stat finfo; + size_t file_size; + sh_vmsg_func_t *errfunc; +#if defined (ARRAY_VARS) + SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v; + ARRAY *funcname_a, *bash_source_a, *bash_lineno_a; + struct func_array_state *fa; +# if defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; +# endif + char *t, tt[2]; +#endif + + USE_VAR(pflags); + +#if defined (ARRAY_VARS) + GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a); + GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a); + GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a); +# if defined (DEBUGGER) + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); +# endif +#endif + + fd = open (filename, O_RDONLY); + + if (fd < 0 || (fstat (fd, &finfo) == -1)) + { + i = errno; + if (fd >= 0) + close (fd); + errno = i; + +file_error_and_exit: + if (((flags & FEVAL_ENOENTOK) == 0) || errno != ENOENT) + file_error (filename); + + if (flags & FEVAL_LONGJMP) + { + last_command_exit_value = EXECUTION_FAILURE; + jump_to_top_level (EXITPROG); + } + + return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE + : ((errno == ENOENT && (flags & FEVAL_ENOENTOK) != 0) ? 0 : -1)); + } + + errfunc = ((flags & FEVAL_BUILTIN) ? builtin_error : internal_error); + + if (S_ISDIR (finfo.st_mode)) + { + (*errfunc) (_("%s: is a directory"), filename); + close (fd); + return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1); + } + else if ((flags & FEVAL_REGFILE) && S_ISREG (finfo.st_mode) == 0) + { + (*errfunc) (_("%s: not a regular file"), filename); + close (fd); + return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1); + } + + file_size = (size_t)finfo.st_size; + /* Check for overflow with large files. */ + if (file_size != finfo.st_size || file_size + 1 < file_size) + { + (*errfunc) (_("%s: file is too large"), filename); + close (fd); + return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1); + } + + if (S_ISREG (finfo.st_mode) && file_size <= SSIZE_MAX) + { + string = (char *)xmalloc (1 + file_size); + nr = read (fd, string, file_size); + if (nr >= 0) + string[nr] = '\0'; + } + else + nr = zmapfd (fd, &string, 0); + + return_val = errno; + close (fd); + errno = return_val; + + if (nr < 0) /* XXX was != file_size, not < 0 */ + { + free (string); + goto file_error_and_exit; + } + + if (nr == 0) + { + free (string); + return ((flags & FEVAL_BUILTIN) ? EXECUTION_SUCCESS : 1); + } + + if ((flags & FEVAL_CHECKBINARY) && + check_binary_file (string, (nr > 80) ? 80 : nr)) + { + free (string); + (*errfunc) (_("%s: cannot execute binary file"), filename); + return ((flags & FEVAL_BUILTIN) ? EX_BINARY_FILE : -1); + } + + i = strlen (string); + if (i < nr) + { + for (nnull = i = 0; i < nr; i++) + if (string[i] == '\0') + { + memmove (string+i, string+i+1, nr - i); + nr--; + /* Even if the `check binary' flag is not set, we want to avoid + sourcing files with more than 256 null characters -- that + probably indicates a binary file. */ + if ((flags & FEVAL_BUILTIN) && ++nnull > 256) + { + free (string); + (*errfunc) (_("%s: cannot execute binary file"), filename); + return ((flags & FEVAL_BUILTIN) ? EX_BINARY_FILE : -1); + } + } + } + + if (flags & FEVAL_UNWINDPROT) + { + begin_unwind_frame ("_evalfile"); + + unwind_protect_int (return_catch_flag); + unwind_protect_jmp_buf (return_catch); + if (flags & FEVAL_NONINT) + unwind_protect_int (interactive); + unwind_protect_int (sourcelevel); + } + else + { + COPY_PROCENV (return_catch, old_return_catch); + if (flags & FEVAL_NONINT) + old_interactive = interactive; + } + + if (flags & FEVAL_NONINT) + interactive = 0; + + return_catch_flag++; + sourcelevel++; + +#if defined (ARRAY_VARS) + array_push (bash_source_a, (char *)filename); + t = itos (executing_line_number ()); + array_push (bash_lineno_a, t); + free (t); + array_push (funcname_a, "source"); /* not exactly right */ + + fa = (struct func_array_state *)xmalloc (sizeof (struct func_array_state)); + fa->source_a = bash_source_a; + fa->source_v = bash_source_v; + fa->lineno_a = bash_lineno_a; + fa->lineno_v = bash_lineno_v; + fa->funcname_a = funcname_a; + fa->funcname_v = funcname_v; + if (flags & FEVAL_UNWINDPROT) + add_unwind_protect (restore_funcarray_state, fa); + +# if defined (DEBUGGER) + /* Have to figure out a better way to do this when `source' is supplied + arguments */ + if ((flags & FEVAL_NOPUSHARGS) == 0) + { + if (shell_compatibility_level <= 44) + init_bash_argv (); + array_push (bash_argv_a, (char *)filename); /* XXX - unconditionally? */ + tt[0] = '1'; tt[1] = '\0'; + array_push (bash_argc_a, tt); + if (flags & FEVAL_UNWINDPROT) + add_unwind_protect (pop_args, 0); + } +# endif +#endif + + /* set the flags to be passed to parse_and_execute */ + pflags = SEVAL_RESETLINE; + pflags |= (flags & FEVAL_HISTORY) ? 0 : SEVAL_NOHIST; + + if (flags & FEVAL_BUILTIN) + result = EXECUTION_SUCCESS; + + return_val = setjmp_nosigs (return_catch); + + /* If `return' was seen outside of a function, but in the script, then + force parse_and_execute () to clean up. */ + if (return_val) + { + parse_and_execute_cleanup (-1); + result = return_catch_value; + } + else + result = parse_and_execute (string, filename, pflags); + + if (flags & FEVAL_UNWINDPROT) + run_unwind_frame ("_evalfile"); + else + { + if (flags & FEVAL_NONINT) + interactive = old_interactive; +#if defined (ARRAY_VARS) + restore_funcarray_state (fa); +# if defined (DEBUGGER) + if ((flags & FEVAL_NOPUSHARGS) == 0) + { + /* Don't need to call pop_args here until we do something better + when source is passed arguments (see above). */ + array_pop (bash_argc_a); + array_pop (bash_argv_a); + } +# endif +#endif + return_catch_flag--; + sourcelevel--; + COPY_PROCENV (old_return_catch, return_catch); + } + + /* If we end up with EOF after sourcing a file, which can happen when the file + doesn't end with a newline, pretend that it did. */ + if (current_token == yacc_EOF) + push_token ('\n'); /* XXX */ + + return ((flags & FEVAL_BUILTIN) ? result : 1); +} + +int +maybe_execute_file (fname, force_noninteractive) + const char *fname; + int force_noninteractive; +{ + char *filename; + int result, flags; + + filename = bash_tilde_expand (fname, 0); + flags = FEVAL_ENOENTOK; + if (force_noninteractive) + flags |= FEVAL_NONINT; + result = _evalfile (filename, flags); + free (filename); + return result; +} + +int +force_execute_file (fname, force_noninteractive) + const char *fname; + int force_noninteractive; +{ + char *filename; + int result, flags; + + filename = bash_tilde_expand (fname, 0); + flags = 0; + if (force_noninteractive) + flags |= FEVAL_NONINT; + result = _evalfile (filename, flags); + free (filename); + return result; +} + +#if defined (HISTORY) +int +fc_execute_file (filename) + const char *filename; +{ + int flags; + + /* We want these commands to show up in the history list if + remember_on_history is set. We use FEVAL_BUILTIN to return + the result of parse_and_execute. */ + flags = FEVAL_ENOENTOK|FEVAL_HISTORY|FEVAL_REGFILE|FEVAL_BUILTIN; + return (_evalfile (filename, flags)); +} +#endif /* HISTORY */ + +int +source_file (filename, sflags) + const char *filename; + int sflags; +{ + int flags, rval; + + flags = FEVAL_BUILTIN|FEVAL_UNWINDPROT|FEVAL_NONINT; + if (sflags) + flags |= FEVAL_NOPUSHARGS; + /* POSIX shells exit if non-interactive and file error. */ + if (posixly_correct && interactive_shell == 0 && executing_command_builtin == 0) + flags |= FEVAL_LONGJMP; + rval = _evalfile (filename, flags); + + run_return_trap (); + return rval; +} diff --git a/third_party/bash/evalstring.c b/third_party/bash/evalstring.c new file mode 100644 index 000000000..046be3c7c --- /dev/null +++ b/third_party/bash/evalstring.c @@ -0,0 +1,818 @@ +/* evalstring.c - evaluate a string as one or more shell commands. */ + +/* Copyright (C) 1996-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include + +#include + +#include "filecntl.h" +#include "bashansi.h" + +#include "shell.h" +#include "jobs.h" +#include "builtins.h" +#include "flags.h" +#include "parser.h" +#include "input.h" +#include "execute_cmd.h" +#include "redir.h" +#include "trap.h" +#include "bashintl.h" + +#include "y.tab.h" + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +#include "common.h" +#include "builtext.h" + +#if !defined (errno) +extern int errno; +#endif + +#define IS_BUILTIN(s) (builtin_address_internal(s, 0) != (struct builtin *)NULL) + +int parse_and_execute_level = 0; + +static int cat_file PARAMS((REDIRECT *)); + +#define PE_TAG "parse_and_execute top" +#define PS_TAG "parse_string top" + +#if defined (HISTORY) +static void +set_history_remembering () +{ + remember_on_history = enable_history_list; +} +#endif + +static void +restore_lastcom (x) + char *x; +{ + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = x; +} + +int +should_optimize_fork (command, subshell) + COMMAND *command; + int subshell; +{ + return (running_trap == 0 && + command->type == cm_simple && + signal_is_trapped (EXIT_TRAP) == 0 && + signal_is_trapped (ERROR_TRAP) == 0 && + any_signals_trapped () < 0 && + (subshell || (command->redirects == 0 && command->value.Simple->redirects == 0)) && + ((command->flags & CMD_TIME_PIPELINE) == 0) && + ((command->flags & CMD_INVERT_RETURN) == 0)); +} + +/* This has extra tests to account for STARTUP_STATE == 2, which is for + -c command but has been extended to command and process substitution + (basically any time you call parse_and_execute in a subshell). */ +int +should_suppress_fork (command) + COMMAND *command; +{ + int subshell; + + subshell = subshell_environment & SUBSHELL_PROCSUB; /* salt to taste */ + return (startup_state == 2 && parse_and_execute_level == 1 && + *bash_input.location.string == '\0' && + parser_expanding_alias () == 0 && + should_optimize_fork (command, subshell)); +} + +int +can_optimize_connection (command) + COMMAND *command; +{ + return (*bash_input.location.string == '\0' && + parser_expanding_alias () == 0 && + (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') && + command->value.Connection->second->type == cm_simple); +} + +void +optimize_connection_fork (command) + COMMAND *command; +{ + if (command->type == cm_connection && + (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') && + (command->value.Connection->second->flags & CMD_TRY_OPTIMIZING) && + ((startup_state == 2 && should_suppress_fork (command->value.Connection->second)) || + ((subshell_environment & SUBSHELL_PAREN) && should_optimize_fork (command->value.Connection->second, 0)))) + { + command->value.Connection->second->flags |= CMD_NO_FORK; + command->value.Connection->second->value.Simple->flags |= CMD_NO_FORK; + } +} + +void +optimize_subshell_command (command) + COMMAND *command; +{ + if (should_optimize_fork (command, 0)) + { + command->flags |= CMD_NO_FORK; + command->value.Simple->flags |= CMD_NO_FORK; + } + else if (command->type == cm_connection && + (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') && + command->value.Connection->second->type == cm_simple && + parser_expanding_alias () == 0) + { + command->value.Connection->second->flags |= CMD_TRY_OPTIMIZING; + command->value.Connection->second->value.Simple->flags |= CMD_TRY_OPTIMIZING; + } +} + +void +optimize_shell_function (command) + COMMAND *command; +{ + COMMAND *fc; + + fc = (command->type == cm_group) ? command->value.Group->command : command; + + if (fc->type == cm_simple && should_suppress_fork (fc)) + { + fc->flags |= CMD_NO_FORK; + fc->value.Simple->flags |= CMD_NO_FORK; + } + else if (fc->type == cm_connection && can_optimize_connection (fc) && should_suppress_fork (fc->value.Connection->second)) + { + fc->value.Connection->second->flags |= CMD_NO_FORK; + fc->value.Connection->second->value.Simple->flags |= CMD_NO_FORK; + } +} + +int +can_optimize_cat_file (command) + COMMAND *command; +{ + return (command->type == cm_simple && !command->redirects && + (command->flags & CMD_TIME_PIPELINE) == 0 && + command->value.Simple->words == 0 && + command->value.Simple->redirects && + command->value.Simple->redirects->next == 0 && + command->value.Simple->redirects->instruction == r_input_direction && + command->value.Simple->redirects->redirector.dest == 0); +} + +/* How to force parse_and_execute () to clean up after itself. */ +void +parse_and_execute_cleanup (old_running_trap) + int old_running_trap; +{ + if (running_trap > 0) + { + /* We assume if we have a different value for running_trap than when + we started (the only caller that cares is evalstring()), the + original caller will perform the cleanup, and we should not step + on them. */ + if (running_trap != old_running_trap) + run_trap_cleanup (running_trap - 1); + unfreeze_jobs_list (); + } + + if (have_unwind_protects ()) + run_unwind_frame (PE_TAG); + else + parse_and_execute_level = 0; /* XXX */ +} + +static void +parse_prologue (string, flags, tag) + char *string; + int flags; + char *tag; +{ + char *orig_string, *lastcom; + int x; + + orig_string = string; + /* Unwind protect this invocation of parse_and_execute (). */ + begin_unwind_frame (tag); + unwind_protect_int (parse_and_execute_level); + unwind_protect_jmp_buf (top_level); + unwind_protect_int (indirection_level); + unwind_protect_int (line_number); + unwind_protect_int (line_number_for_err_trap); + unwind_protect_int (loop_level); + unwind_protect_int (executing_list); + unwind_protect_int (comsub_ignore_return); + if (flags & (SEVAL_NONINT|SEVAL_INTERACT)) + unwind_protect_int (interactive); + +#if defined (HISTORY) + if (parse_and_execute_level == 0) + add_unwind_protect (set_history_remembering, (char *)NULL); + else + unwind_protect_int (remember_on_history); /* can be used in scripts */ +# if defined (BANG_HISTORY) + unwind_protect_int (history_expansion_inhibited); +# endif /* BANG_HISTORY */ +#endif /* HISTORY */ + + if (interactive_shell) + { + x = get_current_prompt_level (); + add_unwind_protect (set_current_prompt_level, x); + } + + if (the_printed_command_except_trap) + { + lastcom = savestring (the_printed_command_except_trap); + add_unwind_protect (restore_lastcom, lastcom); + } + + add_unwind_protect (pop_stream, (char *)NULL); + if (parser_expanding_alias ()) + add_unwind_protect (parser_restore_alias, (char *)NULL); + + if (orig_string && ((flags & SEVAL_NOFREE) == 0)) + add_unwind_protect (xfree, orig_string); + end_unwind_frame (); + + if (flags & (SEVAL_NONINT|SEVAL_INTERACT)) + interactive = (flags & SEVAL_NONINT) ? 0 : 1; + +#if defined (HISTORY) + if (flags & SEVAL_NOHIST) + bash_history_disable (); +# if defined (BANG_HISTORY) + if (flags & SEVAL_NOHISTEXP) + history_expansion_inhibited = 1; +# endif /* BANG_HISTORY */ +#endif /* HISTORY */ +} + +/* Parse and execute the commands in STRING. Returns whatever + execute_command () returns. This frees STRING. FLAGS is a + flags word; look in common.h for the possible values. Actions + are: + (flags & SEVAL_NONINT) -> interactive = 0; + (flags & SEVAL_INTERACT) -> interactive = 1; + (flags & SEVAL_NOHIST) -> call bash_history_disable () + (flags & SEVAL_NOFREE) -> don't free STRING when finished + (flags & SEVAL_RESETLINE) -> reset line_number to 1 + (flags & SEVAL_NOHISTEXP) -> history_expansion_inhibited -> 1 +*/ + +int +parse_and_execute (string, from_file, flags) + char *string; + const char *from_file; + int flags; +{ + int code, lreset; + volatile int should_jump_to_top_level, last_result; + COMMAND *volatile command; + volatile sigset_t pe_sigmask; + + parse_prologue (string, flags, PE_TAG); + + parse_and_execute_level++; + + lreset = flags & SEVAL_RESETLINE; + +#if defined (HAVE_POSIX_SIGNALS) + /* If we longjmp and are going to go on, use this to restore signal mask */ + sigemptyset ((sigset_t *)&pe_sigmask); + sigprocmask (SIG_BLOCK, (sigset_t *)NULL, (sigset_t *)&pe_sigmask); +#endif + + /* Reset the line number if the caller wants us to. If we don't reset the + line number, we have to subtract one, because we will add one just + before executing the next command (resetting the line number sets it to + 0; the first line number is 1). */ + push_stream (lreset); + if (parser_expanding_alias ()) + /* push current shell_input_line */ + parser_save_alias (); + + if (lreset == 0) + line_number--; + + indirection_level++; + + code = should_jump_to_top_level = 0; + last_result = EXECUTION_SUCCESS; + + /* We need to reset enough of the token state so we can start fresh. */ + if (current_token == yacc_EOF) + current_token = '\n'; /* reset_parser() ? */ + + with_input_from_string (string, from_file); + clear_shell_input_line (); + while (*(bash_input.location.string) || parser_expanding_alias ()) + { + command = (COMMAND *)NULL; + + if (interrupt_state) + { + last_result = EXECUTION_FAILURE; + break; + } + + /* Provide a location for functions which `longjmp (top_level)' to + jump to. This prevents errors in substitution from restarting + the reader loop directly, for example. */ + code = setjmp_nosigs (top_level); + + if (code) + { + should_jump_to_top_level = 0; + switch (code) + { + case ERREXIT: + /* variable_context -> 0 is what eval.c:reader_loop() does in + these circumstances. Don't bother with cleanup here because + we don't want to run the function execution cleanup stuff + that will cause pop_context and other functions to run. + We call reset_local_contexts() instead, which just frees + context memory. + XXX - change that if we want the function context to be + unwound. */ + if (exit_immediately_on_error && variable_context) + { + discard_unwind_frame ("pe_dispose"); + reset_local_contexts (); /* not in a function */ + } + should_jump_to_top_level = 1; + goto out; + case FORCE_EOF: + case EXITPROG: + if (command) + run_unwind_frame ("pe_dispose"); + /* Remember to call longjmp (top_level) after the old + value for it is restored. */ + should_jump_to_top_level = 1; + goto out; + + case EXITBLTIN: + if (command) + { + if (variable_context && signal_is_trapped (0)) + { + /* Let's make sure we run the exit trap in the function + context, as we do when not running parse_and_execute. + The pe_dispose unwind frame comes before any unwind- + protects installed by the string we're evaluating, so + it will undo the current function scope. */ + dispose_command (command); + discard_unwind_frame ("pe_dispose"); + } + else + run_unwind_frame ("pe_dispose"); + } + should_jump_to_top_level = 1; + goto out; + + case DISCARD: + if (command) + run_unwind_frame ("pe_dispose"); + last_result = last_command_exit_value = EXECUTION_FAILURE; /* XXX */ + set_pipestatus_from_exit (last_command_exit_value); + if (subshell_environment) + { + should_jump_to_top_level = 1; + goto out; + } + else + { +#if 0 + dispose_command (command); /* pe_dispose does this */ +#endif +#if defined (HAVE_POSIX_SIGNALS) + sigprocmask (SIG_SETMASK, (sigset_t *)&pe_sigmask, (sigset_t *)NULL); +#endif + continue; + } + + default: + command_error ("parse_and_execute", CMDERR_BADJUMP, code, 0); + break; + } + } + + if (parse_command () == 0) + { + if ((flags & SEVAL_PARSEONLY) || (interactive_shell == 0 && read_but_dont_execute)) + { + last_result = EXECUTION_SUCCESS; + dispose_command (global_command); + global_command = (COMMAND *)NULL; + } + else if (command = global_command) + { + struct fd_bitmap *bitmap; + + if (flags & SEVAL_FUNCDEF) + { + char *x; + + /* If the command parses to something other than a straight + function definition, or if we have not consumed the entire + string, or if the parser has transformed the function + name (as parsing will if it begins or ends with shell + whitespace, for example), reject the attempt */ + if (command->type != cm_function_def || + ((x = parser_remaining_input ()) && *x) || + (STREQ (from_file, command->value.Function_def->name->word) == 0)) + { + internal_warning (_("%s: ignoring function definition attempt"), from_file); + should_jump_to_top_level = 0; + last_result = last_command_exit_value = EX_BADUSAGE; + set_pipestatus_from_exit (last_command_exit_value); + reset_parser (); + break; + } + } + + bitmap = new_fd_bitmap (FD_BITMAP_SIZE); + begin_unwind_frame ("pe_dispose"); + add_unwind_protect (dispose_fd_bitmap, bitmap); + add_unwind_protect (dispose_command, command); /* XXX */ + + global_command = (COMMAND *)NULL; + + if ((subshell_environment & SUBSHELL_COMSUB) && comsub_ignore_return) + command->flags |= CMD_IGNORE_RETURN; + +#if defined (ONESHOT) + /* + * IF + * we were invoked as `bash -c' (startup_state == 2) AND + * parse_and_execute has not been called recursively AND + * we're not running a trap AND + * we have parsed the full command (string == '\0') AND + * we're not going to run the exit trap AND + * we have a simple command without redirections AND + * the command is not being timed AND + * the command's return status is not being inverted AND + * there aren't any traps in effect + * THEN + * tell the execution code that we don't need to fork + */ + if (should_suppress_fork (command)) + { + command->flags |= CMD_NO_FORK; + command->value.Simple->flags |= CMD_NO_FORK; + } + + /* Can't optimize forks out here execept for simple commands. + This knows that the parser sets up commands as left-side heavy + (&& and || are left-associative) and after the single parse, + if we are at the end of the command string, the last in a + series of connection commands is + command->value.Connection->second. */ + else if (command->type == cm_connection && can_optimize_connection (command)) + { + command->value.Connection->second->flags |= CMD_TRY_OPTIMIZING; + command->value.Connection->second->value.Simple->flags |= CMD_TRY_OPTIMIZING; + } +#endif /* ONESHOT */ + + /* See if this is a candidate for $( value.Simple->redirects); + last_result = (r < 0) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; + } + else + last_result = execute_command_internal + (command, 0, NO_PIPE, NO_PIPE, bitmap); + dispose_command (command); + dispose_fd_bitmap (bitmap); + discard_unwind_frame ("pe_dispose"); + + if (flags & SEVAL_ONECMD) + { + reset_parser (); + break; + } + } + } + else + { + last_result = EX_BADUSAGE; /* was EXECUTION_FAILURE */ + + if (interactive_shell == 0 && this_shell_builtin && + (this_shell_builtin == source_builtin || this_shell_builtin == eval_builtin) && + last_command_exit_value == EX_BADSYNTAX && posixly_correct && executing_command_builtin == 0) + { + should_jump_to_top_level = 1; + code = ERREXIT; + last_command_exit_value = EX_BADUSAGE; + } + + /* Since we are shell compatible, syntax errors in a script + abort the execution of the script. Right? */ + break; + } + } + + out: + + run_unwind_frame (PE_TAG); + + if (interrupt_state && parse_and_execute_level == 0) + { + /* An interrupt during non-interactive execution in an + interactive shell (e.g. via $PROMPT_COMMAND) should + not cause the shell to exit. */ + interactive = interactive_shell; + throw_to_top_level (); + } + + CHECK_TERMSIG; + + if (should_jump_to_top_level) + jump_to_top_level (code); + + return (last_result); +} + +/* Parse a command contained in STRING according to FLAGS and return the + number of characters consumed from the string. If non-NULL, set *ENDP + to the position in the string where the parse ended. Used to validate + command substitutions during parsing to obey Posix rules about finding + the end of the command and balancing parens. */ +int +parse_string (string, from_file, flags, cmdp, endp) + char *string; + const char *from_file; + int flags; + COMMAND **cmdp; + char **endp; +{ + int code, nc; + volatile int should_jump_to_top_level; + COMMAND *volatile command, *oglobal; + char *ostring; + volatile sigset_t ps_sigmask; + + parse_prologue (string, flags, PS_TAG); + +#if defined (HAVE_POSIX_SIGNALS) + /* If we longjmp and are going to go on, use this to restore signal mask */ + sigemptyset ((sigset_t *)&ps_sigmask); + sigprocmask (SIG_BLOCK, (sigset_t *)NULL, (sigset_t *)&ps_sigmask); +#endif + + /* Reset the line number if the caller wants us to. If we don't reset the + line number, we have to subtract one, because we will add one just + before executing the next command (resetting the line number sets it to + 0; the first line number is 1). */ + push_stream (0); + if (parser_expanding_alias ()) + /* push current shell_input_line */ + parser_save_alias (); + + code = should_jump_to_top_level = 0; + oglobal = global_command; + + with_input_from_string (string, from_file); + ostring = bash_input.location.string; + while (*(bash_input.location.string)) /* XXX - parser_expanding_alias () ? */ + { + command = (COMMAND *)NULL; + +#if 0 + if (interrupt_state) + break; +#endif + + /* Provide a location for functions which `longjmp (top_level)' to + jump to. */ + code = setjmp_nosigs (top_level); + + if (code) + { + INTERNAL_DEBUG(("parse_string: longjmp executed: code = %d", code)); + + should_jump_to_top_level = 0; + switch (code) + { + case FORCE_EOF: + case ERREXIT: + case EXITPROG: + case EXITBLTIN: + case DISCARD: /* XXX */ + if (command) + dispose_command (command); + /* Remember to call longjmp (top_level) after the old + value for it is restored. */ + should_jump_to_top_level = 1; + goto out; + + default: +#if defined (HAVE_POSIX_SIGNALS) + sigprocmask (SIG_SETMASK, (sigset_t *)&ps_sigmask, (sigset_t *)NULL); +#endif + command_error ("parse_string", CMDERR_BADJUMP, code, 0); + break; + } + } + + if (parse_command () == 0) + { + if (cmdp) + *cmdp = global_command; + else + dispose_command (global_command); + global_command = (COMMAND *)NULL; + } + else + { + if ((flags & SEVAL_NOLONGJMP) == 0) + { + should_jump_to_top_level = 1; + code = DISCARD; + } + else + reset_parser (); /* XXX - sets token_to_read */ + break; + } + + if (current_token == yacc_EOF || current_token == shell_eof_token) + { + if (current_token == shell_eof_token) + rewind_input_string (); + break; + } + } + +out: + + global_command = oglobal; + nc = bash_input.location.string - ostring; + if (endp) + *endp = bash_input.location.string; + + run_unwind_frame (PS_TAG); + + /* If we return < 0, the caller (xparse_dolparen) will jump_to_top_level for + us, after doing cleanup */ + if (should_jump_to_top_level) + { + if (parse_and_execute_level == 0) + top_level_cleanup (); + if (code == DISCARD) + return -DISCARD; + jump_to_top_level (code); + } + + return (nc); +} + +int +open_redir_file (r, fnp) + REDIRECT *r; + char **fnp; +{ + char *fn; + int fd, rval; + + if (r->instruction != r_input_direction) + return -1; + + /* Get the filename. */ + if (posixly_correct && !interactive_shell) + disallow_filename_globbing++; + fn = redirection_expand (r->redirectee.filename); + if (posixly_correct && !interactive_shell) + disallow_filename_globbing--; + + if (fn == 0) + { + redirection_error (r, AMBIGUOUS_REDIRECT, fn); + return -1; + } + + fd = open(fn, O_RDONLY); + if (fd < 0) + { + file_error (fn); + free (fn); + if (fnp) + *fnp = 0; + return -1; + } + + if (fnp) + *fnp = fn; + return fd; +} + +/* Handle a $( < file ) command substitution. This expands the filename, + returning errors as appropriate, then just cats the file to the standard + output. */ +static int +cat_file (r) + REDIRECT *r; +{ + char *fn; + int fd, rval; + + fd = open_redir_file (r, &fn); + if (fd < 0) + return -1; + + rval = zcatfd (fd, 1, fn); + + free (fn); + close (fd); + + return (rval); +} + +int +evalstring (string, from_file, flags) + char *string; + const char *from_file; + int flags; +{ + volatile int r, rflag, rcatch; + volatile int was_trap; + + /* Are we running a trap when we execute this function? */ + was_trap = running_trap; + + rcatch = 0; + rflag = return_catch_flag; + /* If we are in a place where `return' is valid, we have to catch + `eval "... return"' and make sure parse_and_execute cleans up. Then + we can trampoline to the previous saved return_catch location. */ + if (rflag) + { + begin_unwind_frame ("evalstring"); + + unwind_protect_int (return_catch_flag); + unwind_protect_jmp_buf (return_catch); + + return_catch_flag++; /* increment so we have a counter */ + rcatch = setjmp_nosigs (return_catch); + } + + if (rcatch) + { + /* We care about whether or not we are running the same trap we were + when we entered this function. */ + parse_and_execute_cleanup (was_trap); + r = return_catch_value; + } + else + /* Note that parse_and_execute () frees the string it is passed. */ + r = parse_and_execute (string, from_file, flags); + + if (rflag) + { + run_unwind_frame ("evalstring"); + if (rcatch && return_catch_flag) + { + return_catch_value = r; + sh_longjmp (return_catch, 1); + } + } + + return (r); +} diff --git a/third_party/bash/execute_cmd.c b/third_party/bash/execute_cmd.c new file mode 100644 index 000000000..665099968 --- /dev/null +++ b/third_party/bash/execute_cmd.c @@ -0,0 +1,6229 @@ +/* execute_cmd.c -- Execute a COMMAND structure. */ + +/* Copyright (C) 1987-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) + #pragma alloca +#endif /* _AIX && RISC6000 && !__GNUC__ */ + +#include +#include "chartypes.h" +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "filecntl.h" +#include "posixstat.h" +#include +#if defined (HAVE_SYS_PARAM_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "posixtime.h" + +#if defined (HAVE_SYS_RESOURCE_H) && !defined (RLIMTYPE) +# include +#endif + +#if defined (HAVE_SYS_TIMES_H) && defined (HAVE_TIMES) +# include +#endif + +#include + +#if !defined (errno) +extern int errno; +#endif + +#define NEED_FPURGE_DECL +#define NEED_SH_SETLINEBUF_DECL + +#include "bashansi.h" +#include "bashintl.h" + +#include "memalloc.h" +#include "shell.h" +#include "y.tab.h" /* use <...> so we pick it up from the build directory */ +#include "parser.h" +#include "flags.h" +#include "builtins.h" +#include "hashlib.h" +#include "jobs.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "redir.h" +#include "trap.h" +#include "pathexp.h" +#include "hashcmd.h" + +#if defined (COND_COMMAND) +# include "test.h" +#endif + +#include "common.h" +#include "builtext.h" /* list of builtins */ + +#include "getopt.h" + +#include "strmatch.h" +#include "tilde.h" + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +#if defined (ALIAS) +# include "alias.h" +#endif + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include /* mbschr */ +#endif + +extern int command_string_index; +extern char *the_printed_command; +extern time_t shell_start_time; +#if defined (HAVE_GETTIMEOFDAY) +extern struct timeval shellstart; +#endif +#if 0 +extern char *glob_argv_flags; +#endif + +extern int close PARAMS((int)); + +/* Static functions defined and used in this file. */ +static void close_pipes PARAMS((int, int)); +static void do_piping PARAMS((int, int)); +static void bind_lastarg PARAMS((char *)); +static int shell_control_structure PARAMS((enum command_type)); +static void cleanup_redirects PARAMS((REDIRECT *)); + +#if defined (JOB_CONTROL) +static int restore_signal_mask PARAMS((sigset_t *)); +#endif + +static int builtin_status PARAMS((int)); + +static int execute_for_command PARAMS((FOR_COM *)); +#if defined (SELECT_COMMAND) +static int displen PARAMS((const char *)); +static int print_index_and_element PARAMS((int, int, WORD_LIST *)); +static void indent PARAMS((int, int)); +static void print_select_list PARAMS((WORD_LIST *, int, int, int)); +static char *select_query PARAMS((WORD_LIST *, int, char *, int)); +static int execute_select_command PARAMS((SELECT_COM *)); +#endif +#if defined (DPAREN_ARITHMETIC) +static int execute_arith_command PARAMS((ARITH_COM *)); +#endif +#if defined (COND_COMMAND) +static int execute_cond_node PARAMS((COND_COM *)); +static int execute_cond_command PARAMS((COND_COM *)); +#endif +#if defined (COMMAND_TIMING) +static int mkfmt PARAMS((char *, int, int, time_t, int)); +static void print_formatted_time PARAMS((FILE *, char *, + time_t, int, time_t, int, + time_t, int, int)); +static int time_command PARAMS((COMMAND *, int, int, int, struct fd_bitmap *)); +#endif +#if defined (ARITH_FOR_COMMAND) +static intmax_t eval_arith_for_expr PARAMS((WORD_LIST *, int *)); +static int execute_arith_for_command PARAMS((ARITH_FOR_COM *)); +#endif +static int execute_case_command PARAMS((CASE_COM *)); +static int execute_while_command PARAMS((WHILE_COM *)); +static int execute_until_command PARAMS((WHILE_COM *)); +static int execute_while_or_until PARAMS((WHILE_COM *, int)); +static int execute_if_command PARAMS((IF_COM *)); +static int execute_null_command PARAMS((REDIRECT *, int, int, int)); +static void fix_assignment_words PARAMS((WORD_LIST *)); +static void fix_arrayref_words PARAMS((WORD_LIST *)); +static int execute_simple_command PARAMS((SIMPLE_COM *, int, int, int, struct fd_bitmap *)); +static int execute_builtin PARAMS((sh_builtin_func_t *, WORD_LIST *, int, int)); +static int execute_function PARAMS((SHELL_VAR *, WORD_LIST *, int, struct fd_bitmap *, int, int)); +static int execute_builtin_or_function PARAMS((WORD_LIST *, sh_builtin_func_t *, + SHELL_VAR *, + REDIRECT *, struct fd_bitmap *, int)); +static void execute_subshell_builtin_or_function PARAMS((WORD_LIST *, REDIRECT *, + sh_builtin_func_t *, + SHELL_VAR *, + int, int, int, + struct fd_bitmap *, + int)); +static int execute_disk_command PARAMS((WORD_LIST *, REDIRECT *, char *, + int, int, int, struct fd_bitmap *, int)); + +static char *getinterp PARAMS((char *, int, int *)); +static void initialize_subshell PARAMS((void)); +static int execute_in_subshell PARAMS((COMMAND *, int, int, int, struct fd_bitmap *)); +#if defined (COPROCESS_SUPPORT) +static void coproc_setstatus PARAMS((struct coproc *, int)); +static int execute_coproc PARAMS((COMMAND *, int, int, struct fd_bitmap *)); +#endif + +static int execute_pipeline PARAMS((COMMAND *, int, int, int, struct fd_bitmap *)); + +static int execute_connection PARAMS((COMMAND *, int, int, int, struct fd_bitmap *)); + +static int execute_intern_function PARAMS((WORD_DESC *, FUNCTION_DEF *)); + +/* Set to 1 if fd 0 was the subject of redirection to a subshell. Global + so that reader_loop can set it to zero before executing a command. */ +int stdin_redir; + +/* The name of the command that is currently being executed. + `test' needs this, for example. */ +char *this_command_name; + +/* The printed representation of the currently-executing command (same as + the_printed_command), except when a trap is being executed. Useful for + a debugger to know where exactly the program is currently executing. */ +char *the_printed_command_except_trap; + +/* For catching RETURN in a function. */ +int return_catch_flag; +int return_catch_value; +procenv_t return_catch; + +/* The value returned by the last synchronous command. */ +volatile int last_command_exit_value; + +/* Whether or not the last command (corresponding to last_command_exit_value) + was terminated by a signal, and, if so, which one. */ +int last_command_exit_signal; + +/* Are we currently ignoring the -e option for the duration of a builtin's + execution? */ +int builtin_ignoring_errexit = 0; + +/* The list of redirections to perform which will undo the redirections + that I made in the shell. */ +REDIRECT *redirection_undo_list = (REDIRECT *)NULL; + +/* The list of redirections to perform which will undo the internal + redirections performed by the `exec' builtin. These are redirections + that must be undone even when exec discards redirection_undo_list. */ +REDIRECT *exec_redirection_undo_list = (REDIRECT *)NULL; + +/* When greater than zero, value is the `level' of builtins we are + currently executing (e.g. `eval echo a' would have it set to 2). */ +int executing_builtin = 0; + +/* Non-zero if we are executing a command list (a;b;c, etc.) */ +int executing_list = 0; + +/* Non-zero if failing commands in a command substitution should not exit the + shell even if -e is set. Used to pass the CMD_IGNORE_RETURN flag down to + commands run in command substitutions by parse_and_execute. */ +int comsub_ignore_return = 0; + +/* Non-zero if we have just forked and are currently running in a subshell + environment. */ +int subshell_environment; + +/* Count of nested subshells, like SHLVL. Available via $BASH_SUBSHELL */ +int subshell_level = 0; + +/* Currently-executing shell function. */ +SHELL_VAR *this_shell_function; + +/* If non-zero, matches in case and [[ ... ]] are case-insensitive */ +int match_ignore_case = 0; + +int executing_command_builtin = 0; + +struct stat SB; /* used for debugging */ + +static int special_builtin_failed; + +static COMMAND *currently_executing_command; + +/* The line number that the currently executing function starts on. */ +static int function_line_number; + +/* XXX - set to 1 if we're running the DEBUG trap and we want to show the line + number containing the function name. Used by executing_line_number to + report the correct line number. Kind of a hack. */ +static int showing_function_line; + +static int connection_count; + +/* $LINENO ($BASH_LINENO) for use by an ERR trap. Global so parse_and_execute + can save and restore it. */ +int line_number_for_err_trap; + +/* A convenience macro to avoid resetting line_number_for_err_trap while + running the ERR trap. */ +#define SET_LINE_NUMBER(v) \ +do { \ + line_number = v; \ + if (signal_in_progress (ERROR_TRAP) == 0 && running_trap != (ERROR_TRAP + 1)) \ + line_number_for_err_trap = line_number; \ +} while (0) + +/* This can't be in executing_line_number() because that's used for LINENO + and we want LINENO to reflect the line number of commands run during + the ERR trap. Right now this is only used to push to BASH_LINENO. */ +#define GET_LINE_NUMBER() \ + (signal_in_progress (ERROR_TRAP) && running_trap == ERROR_TRAP+1) \ + ? line_number_for_err_trap \ + : executing_line_number () + +/* A sort of function nesting level counter */ +int funcnest = 0; +int funcnest_max = 0; + +int evalnest = 0; +int evalnest_max = EVALNEST_MAX; + +int sourcenest = 0; +int sourcenest_max = SOURCENEST_MAX; + +volatile int from_return_trap = 0; + +int lastpipe_opt = 0; + +struct fd_bitmap *current_fds_to_close = (struct fd_bitmap *)NULL; + +#define FD_BITMAP_DEFAULT_SIZE 32 + +/* Functions to allocate and deallocate the structures used to pass + information from the shell to its children about file descriptors + to close. */ +struct fd_bitmap * +new_fd_bitmap (size) + int size; +{ + struct fd_bitmap *ret; + + ret = (struct fd_bitmap *)xmalloc (sizeof (struct fd_bitmap)); + + ret->size = size; + + if (size) + { + ret->bitmap = (char *)xmalloc (size); + memset (ret->bitmap, '\0', size); + } + else + ret->bitmap = (char *)NULL; + return (ret); +} + +void +dispose_fd_bitmap (fdbp) + struct fd_bitmap *fdbp; +{ + FREE (fdbp->bitmap); + free (fdbp); +} + +void +close_fd_bitmap (fdbp) + struct fd_bitmap *fdbp; +{ + register int i; + + if (fdbp) + { + for (i = 0; i < fdbp->size; i++) + if (fdbp->bitmap[i]) + { + close (i); + fdbp->bitmap[i] = 0; + } + } +} + +/* Return the line number of the currently executing command. */ +int +executing_line_number () +{ + if (executing && showing_function_line == 0 && + (variable_context == 0 || interactive_shell == 0) && + currently_executing_command) + { +#if defined (COND_COMMAND) + if (currently_executing_command->type == cm_cond) + return currently_executing_command->value.Cond->line; +#endif +#if defined (DPAREN_ARITHMETIC) + if (currently_executing_command->type == cm_arith) + return currently_executing_command->value.Arith->line; +#endif +#if defined (ARITH_FOR_COMMAND) + if (currently_executing_command->type == cm_arith_for) + return currently_executing_command->value.ArithFor->line; +#endif + + return line_number; + } + else + return line_number; +} + +/* Execute the command passed in COMMAND. COMMAND is exactly what + read_command () places into GLOBAL_COMMAND. See "command.h" for the + details of the command structure. + + EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible + return values. Executing a command with nothing in it returns + EXECUTION_SUCCESS. */ +int +execute_command (command) + COMMAND *command; +{ + struct fd_bitmap *bitmap; + int result; + + current_fds_to_close = (struct fd_bitmap *)NULL; + bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE); + begin_unwind_frame ("execute-command"); + add_unwind_protect (dispose_fd_bitmap, (char *)bitmap); + + /* Just do the command, but not asynchronously. */ + result = execute_command_internal (command, 0, NO_PIPE, NO_PIPE, bitmap); + + dispose_fd_bitmap (bitmap); + discard_unwind_frame ("execute-command"); + +#if defined (PROCESS_SUBSTITUTION) + /* don't unlink fifos if we're in a shell function; wait until the function + returns. */ + if (variable_context == 0 && executing_list == 0) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + QUIT; + return (result); +} + +/* Return 1 if TYPE is a shell control structure type. */ +static int +shell_control_structure (type) + enum command_type type; +{ + switch (type) + { +#if defined (ARITH_FOR_COMMAND) + case cm_arith_for: +#endif +#if defined (SELECT_COMMAND) + case cm_select: +#endif +#if defined (DPAREN_ARITHMETIC) + case cm_arith: +#endif +#if defined (COND_COMMAND) + case cm_cond: +#endif + case cm_case: + case cm_while: + case cm_until: + case cm_if: + case cm_for: + case cm_group: + case cm_function_def: + return (1); + + default: + return (0); + } +} + +/* A function to use to unwind_protect the redirection undo list + for loops. */ +static void +cleanup_redirects (list) + REDIRECT *list; +{ + do_redirections (list, RX_ACTIVE); + dispose_redirects (list); +} + +void +undo_partial_redirects () +{ + if (redirection_undo_list) + { + cleanup_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + } +} + +#if 0 +/* Function to unwind_protect the redirections for functions and builtins. */ +static void +cleanup_func_redirects (list) + REDIRECT *list; +{ + do_redirections (list, RX_ACTIVE); +} +#endif + +void +dispose_exec_redirects () +{ + if (exec_redirection_undo_list) + { + dispose_redirects (exec_redirection_undo_list); + exec_redirection_undo_list = (REDIRECT *)NULL; + } +} + +void +dispose_partial_redirects () +{ + if (redirection_undo_list) + { + dispose_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + } +} + +#if defined (JOB_CONTROL) +/* A function to restore the signal mask to its proper value when the shell + is interrupted or errors occur while creating a pipeline. */ +static int +restore_signal_mask (set) + sigset_t *set; +{ + return (sigprocmask (SIG_SETMASK, set, (sigset_t *)NULL)); +} +#endif /* JOB_CONTROL */ + +#ifdef DEBUG +/* A debugging function that can be called from gdb, for instance. */ +void +open_files (void) +{ + register int i; + int f, fd_table_size; + + fd_table_size = getdtablesize (); + + fprintf (stderr, "pid %ld open files:", (long)getpid ()); + for (i = 3; i < fd_table_size; i++) + { + if ((f = fcntl (i, F_GETFD, 0)) != -1) + fprintf (stderr, " %d (%s)", i, f ? "close" : "open"); + } + fprintf (stderr, "\n"); +} +#endif + +void +async_redirect_stdin () +{ + int fd; + + fd = open ("/dev/null", O_RDONLY); + if (fd > 0) + { + dup2 (fd, 0); + close (fd); + } + else if (fd < 0) + internal_error (_("cannot redirect standard input from /dev/null: %s"), strerror (errno)); +} + +#define DESCRIBE_PID(pid) do { if (interactive) describe_pid (pid); } while (0) + +/* Execute the command passed in COMMAND, perhaps doing it asynchronously. + COMMAND is exactly what read_command () places into GLOBAL_COMMAND. + ASYNCHRONOUS, if non-zero, says to do this command in the background. + PIPE_IN and PIPE_OUT are file descriptors saying where input comes + from and where it goes. They can have the value of NO_PIPE, which means + I/O is stdin/stdout. + FDS_TO_CLOSE is a list of file descriptors to close once the child has + been forked. This list often contains the unusable sides of pipes, etc. + + EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible + return values. Executing a command with nothing in it returns + EXECUTION_SUCCESS. */ +int +execute_command_internal (command, asynchronous, pipe_in, pipe_out, + fds_to_close) + COMMAND *command; + int asynchronous; + int pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + int exec_result, user_subshell, invert, ignore_return, was_error_trap, fork_flags; + REDIRECT *my_undo_list, *exec_undo_list; + char *tcmd; + volatile int save_line_number; +#if defined (PROCESS_SUBSTITUTION) + volatile int ofifo, nfifo, osize, saved_fifo; + volatile void *ofifo_list; +#endif + + if (breaking || continuing) + return (last_command_exit_value); + if (read_but_dont_execute) + return (last_command_exit_value); + if (command == 0) + return (EXECUTION_SUCCESS); + + QUIT; + run_pending_traps (); + +#if 0 + if (running_trap == 0) +#endif + currently_executing_command = command; + + invert = (command->flags & CMD_INVERT_RETURN) != 0; + + /* If we're inverting the return value and `set -e' has been executed, + we don't want a failing command to inadvertently cause the shell + to exit. */ + if (exit_immediately_on_error && invert) /* XXX */ + command->flags |= CMD_IGNORE_RETURN; /* XXX */ + + exec_result = EXECUTION_SUCCESS; + + /* If a command was being explicitly run in a subshell, or if it is + a shell control-structure, and it has a pipe, then we do the command + in a subshell. */ + if (command->type == cm_subshell && (command->flags & CMD_NO_FORK)) + return (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)); + +#if defined (COPROCESS_SUPPORT) + if (command->type == cm_coproc) + return (last_command_exit_value = execute_coproc (command, pipe_in, pipe_out, fds_to_close)); +#endif + + user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0); + +#if defined (TIME_BEFORE_SUBSHELL) + if ((command->flags & CMD_TIME_PIPELINE) && user_subshell && asynchronous == 0) + { + command->flags |= CMD_FORCE_SUBSHELL; + exec_result = time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close); + currently_executing_command = (COMMAND *)NULL; + return (exec_result); + } +#endif + + if (command->type == cm_subshell || + (command->flags & (CMD_WANT_SUBSHELL|CMD_FORCE_SUBSHELL)) || + (shell_control_structure (command->type) && + (pipe_out != NO_PIPE || pipe_in != NO_PIPE || asynchronous))) + { + pid_t paren_pid; + int s; + char *p; + + /* Fork a subshell, turn off the subshell bit, turn off job + control and call execute_command () on the command again. */ + save_line_number = line_number; + if (command->type == cm_subshell) + SET_LINE_NUMBER (command->value.Subshell->line); /* XXX - save value? */ + /* Otherwise we defer setting line_number */ + tcmd = make_command_string (command); + fork_flags = asynchronous ? FORK_ASYNC : 0; + paren_pid = make_child (p = savestring (tcmd), fork_flags); + + if (user_subshell && signal_is_trapped (ERROR_TRAP) && + signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0) + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + if (paren_pid == 0) + { +#if defined (JOB_CONTROL) + FREE (p); /* child doesn't use pointer */ +#endif + /* We want to run the exit trap for forced {} subshells, and we + want to note this before execute_in_subshell modifies the + COMMAND struct. Need to keep in mind that execute_in_subshell + runs the exit trap for () subshells itself. */ + /* This handles { command; } & */ + s = user_subshell == 0 && command->type == cm_group && pipe_in == NO_PIPE && pipe_out == NO_PIPE && asynchronous; + /* run exit trap for : | { ...; } and { ...; } | : */ + /* run exit trap for : | ( ...; ) and ( ...; ) | : */ + s += user_subshell == 0 && command->type == cm_group && (pipe_in != NO_PIPE || pipe_out != NO_PIPE) && asynchronous == 0; + + last_command_exit_value = execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close); + if (s) + subshell_exit (last_command_exit_value); + else + sh_exit (last_command_exit_value); + /* NOTREACHED */ + } + else + { + close_pipes (pipe_in, pipe_out); + +#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) + if (variable_context == 0) /* wait until shell function completes */ + unlink_fifo_list (); +#endif + /* If we are part of a pipeline, and not the end of the pipeline, + then we should simply return and let the last command in the + pipe be waited for. If we are not in a pipeline, or are the + last command in the pipeline, then we wait for the subshell + and return its exit status as usual. */ + if (pipe_out != NO_PIPE) + return (EXECUTION_SUCCESS); + + stop_pipeline (asynchronous, (COMMAND *)NULL); + + line_number = save_line_number; + + if (asynchronous == 0) + { + was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0; + invert = (command->flags & CMD_INVERT_RETURN) != 0; + ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; + + exec_result = wait_for (paren_pid, 0); + + /* If we have to, invert the return value. */ + if (invert) + exec_result = ((exec_result == EXECUTION_SUCCESS) + ? EXECUTION_FAILURE + : EXECUTION_SUCCESS); + + last_command_exit_value = exec_result; + if (user_subshell && was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS) + { + save_line_number = line_number; + line_number = line_number_for_err_trap; + run_error_trap (); + line_number = save_line_number; + } + + if (user_subshell && ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS) + { + run_pending_traps (); + jump_to_top_level (ERREXIT); + } + + return (last_command_exit_value); + } + else + { + DESCRIBE_PID (paren_pid); + + run_pending_traps (); + + /* Posix 2013 2.9.3.1: "the exit status of an asynchronous list + shall be zero." */ + last_command_exit_value = 0; + return (EXECUTION_SUCCESS); + } + } + } + +#if defined (COMMAND_TIMING) + if (command->flags & CMD_TIME_PIPELINE) + { + if (asynchronous) + { + command->flags |= CMD_FORCE_SUBSHELL; + exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); + } + else + { + exec_result = time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close); +#if 0 + if (running_trap == 0) +#endif + currently_executing_command = (COMMAND *)NULL; + } + return (exec_result); + } +#endif /* COMMAND_TIMING */ + + if (shell_control_structure (command->type) && command->redirects) + stdin_redir = stdin_redirects (command->redirects); + +#if defined (PROCESS_SUBSTITUTION) +# if !defined (HAVE_DEV_FD) + reap_procsubs (); +# endif + + /* XXX - also if sourcelevel != 0? */ + if (variable_context != 0 || executing_list) + { + ofifo = num_fifos (); + ofifo_list = copy_fifo_list ((int *)&osize); + begin_unwind_frame ("internal_fifos"); + if (ofifo_list) + add_unwind_protect (xfree, ofifo_list); + saved_fifo = 1; + } + else + saved_fifo = 0; +#endif + + /* Handle WHILE FOR CASE etc. with redirections. (Also '&' input + redirection.) */ + was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0; + ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; + + if (do_redirections (command->redirects, RX_ACTIVE|RX_UNDOABLE) != 0) + { + undo_partial_redirects (); + dispose_exec_redirects (); +#if defined (PROCESS_SUBSTITUTION) + if (saved_fifo) + { + free ((void *)ofifo_list); + discard_unwind_frame ("internal_fifos"); + } +#endif + + /* Handle redirection error as command failure if errexit set. */ + last_command_exit_value = EXECUTION_FAILURE; + if (ignore_return == 0 && invert == 0 && pipe_in == NO_PIPE && pipe_out == NO_PIPE) + { + if (was_error_trap) + { + save_line_number = line_number; + line_number = line_number_for_err_trap; + run_error_trap (); + line_number = save_line_number; + } + if (exit_immediately_on_error) + { + run_pending_traps (); + jump_to_top_level (ERREXIT); + } + } + return (last_command_exit_value); + } + + my_undo_list = redirection_undo_list; + redirection_undo_list = (REDIRECT *)NULL; + + exec_undo_list = exec_redirection_undo_list; + exec_redirection_undo_list = (REDIRECT *)NULL; + + if (my_undo_list || exec_undo_list) + begin_unwind_frame ("loop_redirections"); + + if (my_undo_list) + add_unwind_protect ((Function *)cleanup_redirects, my_undo_list); + + if (exec_undo_list) + add_unwind_protect ((Function *)dispose_redirects, exec_undo_list); + + QUIT; + + switch (command->type) + { + case cm_simple: + { + save_line_number = line_number; + /* We can't rely on variables retaining their values across a + call to execute_simple_command if a longjmp occurs as the + result of a `return' builtin. This is true for sure with gcc. */ +#if defined (RECYCLES_PIDS) + last_made_pid = NO_PID; +#endif + was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0; + + if (ignore_return && command->value.Simple) + command->value.Simple->flags |= CMD_IGNORE_RETURN; + if (command->flags & CMD_STDIN_REDIR) + command->value.Simple->flags |= CMD_STDIN_REDIR; + + SET_LINE_NUMBER (command->value.Simple->line); + exec_result = + execute_simple_command (command->value.Simple, pipe_in, pipe_out, + asynchronous, fds_to_close); + line_number = save_line_number; + + /* The temporary environment should be used for only the simple + command immediately following its definition. */ + dispose_used_env_vars (); + +#if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA) + /* Reclaim memory allocated with alloca () on machines which + may be using the alloca emulation code. */ + (void) alloca (0); +#endif /* (ultrix && mips) || C_ALLOCA */ + + /* If we forked to do the command, then we must wait_for () + the child. */ + + /* XXX - this is something to watch out for if there are problems + when the shell is compiled without job control. Don't worry about + whether or not last_made_pid == last_pid; already_making_children + tells us whether or not there are unwaited-for children to wait + for and reap. */ + if (already_making_children && pipe_out == NO_PIPE) + { + stop_pipeline (asynchronous, (COMMAND *)NULL); + + if (asynchronous) + { + DESCRIBE_PID (last_made_pid); + exec_result = EXECUTION_SUCCESS; + invert = 0; /* async commands always succeed */ + } + else +#if !defined (JOB_CONTROL) + /* Do not wait for asynchronous processes started from + startup files. */ + if (last_made_pid != NO_PID && last_made_pid != last_asynchronous_pid) +#else + if (last_made_pid != NO_PID) +#endif + /* When executing a shell function that executes other + commands, this causes the last simple command in + the function to be waited for twice. This also causes + subshells forked to execute builtin commands (e.g., in + pipelines) to be waited for twice. */ + exec_result = wait_for (last_made_pid, 0); + } + } + + /* 2009/02/13 -- pipeline failure is processed elsewhere. This handles + only the failure of a simple command. We don't want to run the error + trap if the command run by the `command' builtin fails; we want to + defer that until the command builtin itself returns failure. */ + /* 2020/07/14 -- this changes with how the command builtin is handled */ + if (was_error_trap && ignore_return == 0 && invert == 0 && + pipe_in == NO_PIPE && pipe_out == NO_PIPE && + (command->value.Simple->flags & CMD_COMMAND_BUILTIN) == 0 && + exec_result != EXECUTION_SUCCESS) + { + last_command_exit_value = exec_result; + line_number = line_number_for_err_trap; + run_error_trap (); + line_number = save_line_number; + } + + if (ignore_return == 0 && invert == 0 && + ((posixly_correct && interactive == 0 && special_builtin_failed) || + (exit_immediately_on_error && pipe_in == NO_PIPE && pipe_out == NO_PIPE && exec_result != EXECUTION_SUCCESS))) + { + last_command_exit_value = exec_result; + run_pending_traps (); + + /* Undo redirections before running exit trap on the way out of + set -e. Report by Mark Farrell 5/19/2014 */ + if (exit_immediately_on_error && signal_is_trapped (0) && + unwind_protect_tag_on_stack ("saved-redirects")) + run_unwind_frame ("saved-redirects"); + + jump_to_top_level (ERREXIT); + } + + break; + + case cm_for: + if (ignore_return) + command->value.For->flags |= CMD_IGNORE_RETURN; + exec_result = execute_for_command (command->value.For); + break; + +#if defined (ARITH_FOR_COMMAND) + case cm_arith_for: + if (ignore_return) + command->value.ArithFor->flags |= CMD_IGNORE_RETURN; + exec_result = execute_arith_for_command (command->value.ArithFor); + break; +#endif + +#if defined (SELECT_COMMAND) + case cm_select: + if (ignore_return) + command->value.Select->flags |= CMD_IGNORE_RETURN; + exec_result = execute_select_command (command->value.Select); + break; +#endif + + case cm_case: + if (ignore_return) + command->value.Case->flags |= CMD_IGNORE_RETURN; + exec_result = execute_case_command (command->value.Case); + break; + + case cm_while: + if (ignore_return) + command->value.While->flags |= CMD_IGNORE_RETURN; + exec_result = execute_while_command (command->value.While); + break; + + case cm_until: + if (ignore_return) + command->value.While->flags |= CMD_IGNORE_RETURN; + exec_result = execute_until_command (command->value.While); + break; + + case cm_if: + if (ignore_return) + command->value.If->flags |= CMD_IGNORE_RETURN; + exec_result = execute_if_command (command->value.If); + break; + + case cm_group: + + /* This code can be executed from either of two paths: an explicit + '{}' command, or via a function call. If we are executed via a + function call, we have already taken care of the function being + executed in the background (down there in execute_simple_command ()), + and this command should *not* be marked as asynchronous. If we + are executing a regular '{}' group command, and asynchronous == 1, + we must want to execute the whole command in the background, so we + need a subshell, and we want the stuff executed in that subshell + (this group command) to be executed in the foreground of that + subshell (i.e. there will not be *another* subshell forked). + + What we do is to force a subshell if asynchronous, and then call + execute_command_internal again with asynchronous still set to 1, + but with the original group command, so the printed command will + look right. + + The code above that handles forking off subshells will note that + both subshell and async are on, and turn off async in the child + after forking the subshell (but leave async set in the parent, so + the normal call to describe_pid is made). This turning off + async is *crucial*; if it is not done, this will fall into an + infinite loop of executions through this spot in subshell after + subshell until the process limit is exhausted. */ + + if (asynchronous) + { + command->flags |= CMD_FORCE_SUBSHELL; + exec_result = + execute_command_internal (command, 1, pipe_in, pipe_out, + fds_to_close); + } + else + { + if (ignore_return && command->value.Group->command) + command->value.Group->command->flags |= CMD_IGNORE_RETURN; + exec_result = + execute_command_internal (command->value.Group->command, + asynchronous, pipe_in, pipe_out, + fds_to_close); + } + break; + + case cm_connection: + exec_result = execute_connection (command, asynchronous, + pipe_in, pipe_out, fds_to_close); + if (asynchronous) + invert = 0; /* XXX */ + + break; + +#if defined (DPAREN_ARITHMETIC) + case cm_arith: +#endif +#if defined (COND_COMMAND) + case cm_cond: +#endif + case cm_function_def: + was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0; +#if defined (DPAREN_ARITHMETIC) + if (ignore_return && command->type == cm_arith) + command->value.Arith->flags |= CMD_IGNORE_RETURN; +#endif +#if defined (COND_COMMAND) + if (ignore_return && command->type == cm_cond) + command->value.Cond->flags |= CMD_IGNORE_RETURN; +#endif + + line_number_for_err_trap = save_line_number = line_number; /* XXX */ +#if defined (DPAREN_ARITHMETIC) + if (command->type == cm_arith) + exec_result = execute_arith_command (command->value.Arith); + else +#endif +#if defined (COND_COMMAND) + if (command->type == cm_cond) + exec_result = execute_cond_command (command->value.Cond); + else +#endif + if (command->type == cm_function_def) + exec_result = execute_intern_function (command->value.Function_def->name, + command->value.Function_def); + line_number = save_line_number; + + if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS) + { + last_command_exit_value = exec_result; + save_line_number = line_number; + line_number = line_number_for_err_trap; + run_error_trap (); + line_number = save_line_number; + } + + if (ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS) + { + last_command_exit_value = exec_result; + run_pending_traps (); + jump_to_top_level (ERREXIT); + } + + break; + + default: + command_error ("execute_command", CMDERR_BADTYPE, command->type, 0); + } + + if (my_undo_list) + cleanup_redirects (my_undo_list); + + if (exec_undo_list) + dispose_redirects (exec_undo_list); + + if (my_undo_list || exec_undo_list) + discard_unwind_frame ("loop_redirections"); + +#if defined (PROCESS_SUBSTITUTION) + if (saved_fifo) + { + nfifo = num_fifos (); + if (nfifo > ofifo) + close_new_fifos ((void *)ofifo_list, osize); + free ((void *)ofifo_list); + discard_unwind_frame ("internal_fifos"); + } +#endif + + /* Invert the return value if we have to */ + if (invert) + exec_result = (exec_result == EXECUTION_SUCCESS) + ? EXECUTION_FAILURE + : EXECUTION_SUCCESS; + +#if defined (DPAREN_ARITHMETIC) || defined (COND_COMMAND) + /* This is where we set PIPESTATUS from the exit status of the appropriate + compound commands (the ones that look enough like simple commands to + cause confusion). We might be able to optimize by not doing this if + subshell_environment != 0. */ + switch (command->type) + { +# if defined (DPAREN_ARITHMETIC) + case cm_arith: +# endif +# if defined (COND_COMMAND) + case cm_cond: +# endif + set_pipestatus_from_exit (exec_result); + break; + default: + break; + } +#endif + + last_command_exit_value = exec_result; + run_pending_traps (); + currently_executing_command = (COMMAND *)NULL; + + return (last_command_exit_value); +} + +#if defined (COMMAND_TIMING) + +#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) +extern struct timeval *difftimeval PARAMS((struct timeval *, struct timeval *, struct timeval *)); +extern struct timeval *addtimeval PARAMS((struct timeval *, struct timeval *, struct timeval *)); +extern int timeval_to_cpu PARAMS((struct timeval *, struct timeval *, struct timeval *)); +#endif + +#define POSIX_TIMEFORMAT "real %2R\nuser %2U\nsys %2S" +#define BASH_TIMEFORMAT "\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS" + +static const int precs[] = { 0, 100, 10, 1 }; + +/* Expand one `%'-prefixed escape sequence from a time format string. */ +static int +mkfmt (buf, prec, lng, sec, sec_fraction) + char *buf; + int prec, lng; + time_t sec; + int sec_fraction; +{ + time_t min; + char abuf[INT_STRLEN_BOUND(time_t) + 1]; + int ind, aind; + + ind = 0; + abuf[sizeof(abuf) - 1] = '\0'; + + /* If LNG is non-zero, we want to decompose SEC into minutes and seconds. */ + if (lng) + { + min = sec / 60; + sec %= 60; + aind = sizeof(abuf) - 2; + do + abuf[aind--] = (min % 10) + '0'; + while (min /= 10); + aind++; + while (abuf[aind]) + buf[ind++] = abuf[aind++]; + buf[ind++] = 'm'; + } + + /* Now add the seconds. */ + aind = sizeof (abuf) - 2; + do + abuf[aind--] = (sec % 10) + '0'; + while (sec /= 10); + aind++; + while (abuf[aind]) + buf[ind++] = abuf[aind++]; + + /* We want to add a decimal point and PREC places after it if PREC is + nonzero. PREC is not greater than 3. SEC_FRACTION is between 0 + and 999. */ + if (prec != 0) + { + buf[ind++] = locale_decpoint (); + for (aind = 1; aind <= prec; aind++) + { + buf[ind++] = (sec_fraction / precs[aind]) + '0'; + sec_fraction %= precs[aind]; + } + } + + if (lng) + buf[ind++] = 's'; + buf[ind] = '\0'; + + return (ind); +} + +/* Interpret the format string FORMAT, interpolating the following escape + sequences: + %[prec][l][RUS] + + where the optional `prec' is a precision, meaning the number of + characters after the decimal point, the optional `l' means to format + using minutes and seconds (MMmNN[.FF]s), like the `times' builtin', + and the last character is one of + + R number of seconds of `real' time + U number of seconds of `user' time + S number of seconds of `system' time + + An occurrence of `%%' in the format string is translated to a `%'. The + result is printed to FP, a pointer to a FILE. The other variables are + the seconds and thousandths of a second of real, user, and system time, + resectively. */ +static void +print_formatted_time (fp, format, rs, rsf, us, usf, ss, ssf, cpu) + FILE *fp; + char *format; + time_t rs; + int rsf; + time_t us; + int usf; + time_t ss; + int ssf, cpu; +{ + int prec, lng, len; + char *str, *s, ts[INT_STRLEN_BOUND (time_t) + sizeof ("mSS.FFFF")]; + time_t sum; + int sum_frac; + int sindex, ssize; + + len = strlen (format); + ssize = (len + 64) - (len % 64); + str = (char *)xmalloc (ssize); + sindex = 0; + + for (s = format; *s; s++) + { + if (*s != '%' || s[1] == '\0') + { + RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64); + str[sindex++] = *s; + } + else if (s[1] == '%') + { + s++; + RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64); + str[sindex++] = *s; + } + else if (s[1] == 'P') + { + s++; +#if 0 + /* clamp CPU usage at 100% */ + if (cpu > 10000) + cpu = 10000; +#endif + sum = cpu / 100; + sum_frac = (cpu % 100) * 10; + len = mkfmt (ts, 2, 0, sum, sum_frac); + RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64); + strcpy (str + sindex, ts); + sindex += len; + } + else + { + prec = 3; /* default is three places past the decimal point. */ + lng = 0; /* default is to not use minutes or append `s' */ + s++; + if (DIGIT (*s)) /* `precision' */ + { + prec = *s++ - '0'; + if (prec > 3) prec = 3; + } + if (*s == 'l') /* `length extender' */ + { + lng = 1; + s++; + } + if (*s == 'R' || *s == 'E') + len = mkfmt (ts, prec, lng, rs, rsf); + else if (*s == 'U') + len = mkfmt (ts, prec, lng, us, usf); + else if (*s == 'S') + len = mkfmt (ts, prec, lng, ss, ssf); + else + { + internal_error (_("TIMEFORMAT: `%c': invalid format character"), *s); + free (str); + return; + } + RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64); + strcpy (str + sindex, ts); + sindex += len; + } + } + + str[sindex] = '\0'; + fprintf (fp, "%s\n", str); + fflush (fp); + + free (str); +} + +static int +time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int asynchronous, pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + int rv, posix_time, old_flags, nullcmd, code; + time_t rs, us, ss; + int rsf, usf, ssf; + int cpu; + char *time_format; + volatile procenv_t save_top_level; + volatile int old_subshell; + +#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) + struct timeval real, user, sys; + struct timeval before, after; +# if defined (HAVE_STRUCT_TIMEZONE) + struct timezone dtz; /* posix doesn't define this */ +# endif + struct rusage selfb, selfa, kidsb, kidsa; /* a = after, b = before */ +#else +# if defined (HAVE_TIMES) + clock_t tbefore, tafter, real, user, sys; + struct tms before, after; +# endif +#endif + +#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) +# if defined (HAVE_STRUCT_TIMEZONE) + gettimeofday (&before, &dtz); +# else + gettimeofday (&before, (void *)NULL); +# endif /* !HAVE_STRUCT_TIMEZONE */ + getrusage (RUSAGE_SELF, &selfb); + getrusage (RUSAGE_CHILDREN, &kidsb); +#else +# if defined (HAVE_TIMES) + tbefore = times (&before); +# endif +#endif + + old_subshell = subshell_environment; + posix_time = command && (command->flags & CMD_TIME_POSIX); + + nullcmd = (command == 0) || (command->type == cm_simple && command->value.Simple->words == 0 && command->value.Simple->redirects == 0); + if (posixly_correct && nullcmd) + { +#if defined (HAVE_GETRUSAGE) + selfb.ru_utime.tv_sec = kidsb.ru_utime.tv_sec = selfb.ru_stime.tv_sec = kidsb.ru_stime.tv_sec = 0; + selfb.ru_utime.tv_usec = kidsb.ru_utime.tv_usec = selfb.ru_stime.tv_usec = kidsb.ru_stime.tv_usec = 0; + before = shellstart; +#else + before.tms_utime = before.tms_stime = before.tms_cutime = before.tms_cstime = 0; + tbefore = shell_start_time; +#endif + } + + old_flags = command->flags; + COPY_PROCENV (top_level, save_top_level); + command->flags &= ~(CMD_TIME_PIPELINE|CMD_TIME_POSIX); + code = setjmp_nosigs (top_level); + if (code == NOT_JUMPED) + rv = execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close); + COPY_PROCENV (save_top_level, top_level); + + command->flags = old_flags; + + /* If we're jumping in a different subshell environment than we started, + don't bother printing timing stats, just keep longjmping back to the + original top level. */ + if (code != NOT_JUMPED && subshell_environment && subshell_environment != old_subshell) + sh_longjmp (top_level, code); + + rs = us = ss = 0; + rsf = usf = ssf = cpu = 0; + +#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) +# if defined (HAVE_STRUCT_TIMEZONE) + gettimeofday (&after, &dtz); +# else + gettimeofday (&after, (void *)NULL); +# endif /* !HAVE_STRUCT_TIMEZONE */ + getrusage (RUSAGE_SELF, &selfa); + getrusage (RUSAGE_CHILDREN, &kidsa); + + difftimeval (&real, &before, &after); + timeval_to_secs (&real, &rs, &rsf); + + addtimeval (&user, difftimeval(&after, &selfb.ru_utime, &selfa.ru_utime), + difftimeval(&before, &kidsb.ru_utime, &kidsa.ru_utime)); + timeval_to_secs (&user, &us, &usf); + + addtimeval (&sys, difftimeval(&after, &selfb.ru_stime, &selfa.ru_stime), + difftimeval(&before, &kidsb.ru_stime, &kidsa.ru_stime)); + timeval_to_secs (&sys, &ss, &ssf); + + cpu = timeval_to_cpu (&real, &user, &sys); +#else +# if defined (HAVE_TIMES) + tafter = times (&after); + + real = tafter - tbefore; + clock_t_to_secs (real, &rs, &rsf); + + user = (after.tms_utime - before.tms_utime) + (after.tms_cutime - before.tms_cutime); + clock_t_to_secs (user, &us, &usf); + + sys = (after.tms_stime - before.tms_stime) + (after.tms_cstime - before.tms_cstime); + clock_t_to_secs (sys, &ss, &ssf); + + cpu = (real == 0) ? 0 : ((user + sys) * 10000) / real; + +# else + rs = us = ss = 0; + rsf = usf = ssf = cpu = 0; +# endif +#endif + + if (posix_time) + time_format = POSIX_TIMEFORMAT; + else if ((time_format = get_string_value ("TIMEFORMAT")) == 0) + { + if (posixly_correct && nullcmd) + time_format = "user\t%2lU\nsys\t%2lS"; + else + time_format = BASH_TIMEFORMAT; + } + + if (time_format && *time_format) + print_formatted_time (stderr, time_format, rs, rsf, us, usf, ss, ssf, cpu); + + if (code) + sh_longjmp (top_level, code); + + return rv; +} +#endif /* COMMAND_TIMING */ + +/* Execute a command that's supposed to be in a subshell. This must be + called after make_child and we must be running in the child process. + The caller will return or exit() immediately with the value this returns. */ +static int +execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int asynchronous; + int pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + volatile int user_subshell, user_coproc, invert; + int return_code, function_value, should_redir_stdin, ois, result; + volatile COMMAND *tcom; + + USE_VAR(user_subshell); + USE_VAR(user_coproc); + USE_VAR(invert); + USE_VAR(tcom); + USE_VAR(asynchronous); + + subshell_level++; + should_redir_stdin = (asynchronous && (command->flags & CMD_STDIN_REDIR) && + pipe_in == NO_PIPE && + stdin_redirects (command->redirects) == 0); + + invert = (command->flags & CMD_INVERT_RETURN) != 0; + user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0); + user_coproc = command->type == cm_coproc; + + command->flags &= ~(CMD_FORCE_SUBSHELL | CMD_WANT_SUBSHELL | CMD_INVERT_RETURN); + + /* If a command is asynchronous in a subshell (like ( foo ) & or + the special case of an asynchronous GROUP command where the + + the subshell bit is turned on down in case cm_group: below), + turn off `asynchronous', so that two subshells aren't spawned. + XXX - asynchronous used to be set to 0 in this block, but that + means that setup_async_signals was never run. Now it's set to + 0 after subshell_environment is set appropriately and setup_async_signals + is run. + + This seems semantically correct to me. For example, + ( foo ) & seems to say ``do the command `foo' in a subshell + environment, but don't wait for that subshell to finish'', + and "{ foo ; bar ; } &" seems to me to be like functions or + builtins in the background, which executed in a subshell + environment. I just don't see the need to fork two subshells. */ + + /* Don't fork again, we are already in a subshell. A `doubly + async' shell is not interactive, however. */ + if (asynchronous) + { +#if defined (JOB_CONTROL) + /* If a construct like ( exec xxx yyy ) & is given while job + control is active, we want to prevent exec from putting the + subshell back into the original process group, carefully + undoing all the work we just did in make_child. */ + original_pgrp = -1; +#endif /* JOB_CONTROL */ + ois = interactive_shell; + interactive_shell = 0; + /* This test is to prevent alias expansion by interactive shells that + run `(command) &' but to allow scripts that have enabled alias + expansion with `shopt -s expand_alias' to continue to expand + aliases. */ + if (ois != interactive_shell) + expand_aliases = 0; + } + + /* Subshells are neither login nor interactive. */ + login_shell = interactive = 0; + + /* And we're no longer in a loop. See Posix interp 842 (we are not in the + "same execution environment"). */ + if (shell_compatibility_level > 44) + loop_level = 0; + + if (user_subshell) + { + subshell_environment = SUBSHELL_PAREN; /* XXX */ + if (asynchronous) + subshell_environment |= SUBSHELL_ASYNC; + } + else + { + subshell_environment = 0; /* XXX */ + if (asynchronous) + subshell_environment |= SUBSHELL_ASYNC; + if (pipe_in != NO_PIPE || pipe_out != NO_PIPE) + subshell_environment |= SUBSHELL_PIPE; + if (user_coproc) + subshell_environment |= SUBSHELL_COPROC; + } + + QUIT; + CHECK_TERMSIG; + + reset_terminating_signals (); /* in sig.c */ + /* Cancel traps, in trap.c. */ + /* Reset the signal handlers in the child, but don't free the + trap strings. Set a flag noting that we have to free the + trap strings if we run trap to change a signal disposition. */ + clear_pending_traps (); + reset_signal_handlers (); + subshell_environment |= SUBSHELL_RESETTRAP; + /* Note that signal handlers have been reset, so we should no longer + reset the handler and resend trapped signals to ourselves. */ + subshell_environment &= ~SUBSHELL_IGNTRAP; + + /* We are in a subshell, so forget that we are running a trap handler or + that the signal handler has changed (we haven't changed it!) */ + /* XXX - maybe do this for `real' signals and not ERR/DEBUG/RETURN/EXIT + traps? */ + if (running_trap > 0) + { + run_trap_cleanup (running_trap - 1); + running_trap = 0; /* XXX - maybe leave this */ + } + + /* Make sure restore_original_signals doesn't undo the work done by + make_child to ensure that asynchronous children are immune to SIGINT + and SIGQUIT. Turn off asynchronous to make sure more subshells are + not spawned. */ + if (asynchronous) + { + setup_async_signals (); + asynchronous = 0; + } + else + set_sigint_handler (); + +#if defined (JOB_CONTROL) + set_sigchld_handler (); +#endif /* JOB_CONTROL */ + + /* Delete all traces that there were any jobs running. This is + only for subshells. */ + without_job_control (); + + if (fds_to_close) + close_fd_bitmap (fds_to_close); + + do_piping (pipe_in, pipe_out); + +#if defined (COPROCESS_SUPPORT) + coproc_closeall (); +#endif + +#if defined (PROCESS_SUBSTITUTION) + clear_fifo_list (); /* XXX- we haven't created any FIFOs */ +#endif + + /* If this is a user subshell, set a flag if stdin was redirected. + This is used later to decide whether to redirect fd 0 to + /dev/null for async commands in the subshell. This adds more + sh compatibility, but I'm not sure it's the right thing to do. + Note that an input pipe to a compound command suffices to inhibit + the implicit /dev/null redirection for asynchronous commands + executed as part of that compound command. */ + if (user_subshell) + { + stdin_redir = stdin_redirects (command->redirects) || pipe_in != NO_PIPE; +#if 0 + restore_default_signal (EXIT_TRAP); /* XXX - reset_signal_handlers above */ +#endif + } + else if (shell_control_structure (command->type) && pipe_in != NO_PIPE) + stdin_redir = 1; + + /* If this is an asynchronous command (command &), we want to + redirect the standard input from /dev/null in the absence of + any specific redirection involving stdin. */ + if (should_redir_stdin && stdin_redir == 0) + async_redirect_stdin (); + +#if defined (BUFFERED_INPUT) + /* In any case, we are not reading our command input from stdin. */ + default_buffered_input = -1; +#endif + + /* We can't optimize away forks if one of the commands executed by the + subshell sets an exit trap, so we set CMD_NO_FORK for simple commands + and set CMD_TRY_OPTIMIZING for simple commands on the right side of an + and-or or `;' list to test for optimizing forks when they are executed. */ + if (user_subshell && command->type == cm_subshell) + optimize_subshell_command (command->value.Subshell->command); + + /* Do redirections, then dispose of them before recursive call. */ + if (command->redirects) + { + if (do_redirections (command->redirects, RX_ACTIVE) != 0) + exit (invert ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + + dispose_redirects (command->redirects); + command->redirects = (REDIRECT *)NULL; + } + + if (command->type == cm_subshell) + tcom = command->value.Subshell->command; + else if (user_coproc) + tcom = command->value.Coproc->command; + else + tcom = command; + + if (command->flags & CMD_TIME_PIPELINE) + tcom->flags |= CMD_TIME_PIPELINE; + if (command->flags & CMD_TIME_POSIX) + tcom->flags |= CMD_TIME_POSIX; + + /* Make sure the subshell inherits any CMD_IGNORE_RETURN flag. */ + if ((command->flags & CMD_IGNORE_RETURN) && tcom != command) + tcom->flags |= CMD_IGNORE_RETURN; + + /* If this is a simple command, tell execute_disk_command that it + might be able to get away without forking and simply exec. + This means things like ( sleep 10 ) will only cause one fork. + If we're timing the command or inverting its return value, however, + we cannot do this optimization. */ + if ((user_subshell || user_coproc) && (tcom->type == cm_simple || tcom->type == cm_subshell) && + ((tcom->flags & CMD_TIME_PIPELINE) == 0) && + ((tcom->flags & CMD_INVERT_RETURN) == 0)) + { + tcom->flags |= CMD_NO_FORK; + if (tcom->type == cm_simple) + tcom->value.Simple->flags |= CMD_NO_FORK; + } + + invert = (tcom->flags & CMD_INVERT_RETURN) != 0; + tcom->flags &= ~CMD_INVERT_RETURN; + + result = setjmp_nosigs (top_level); + + /* If we're inside a function while executing this subshell, we + need to handle a possible `return'. */ + function_value = 0; + if (return_catch_flag) + function_value = setjmp_nosigs (return_catch); + + /* If we're going to exit the shell, we don't want to invert the return + status. */ + if (result == EXITPROG || result == EXITBLTIN) + invert = 0, return_code = last_command_exit_value; + else if (result) + return_code = (last_command_exit_value == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : last_command_exit_value; + else if (function_value) + return_code = return_catch_value; + else + return_code = execute_command_internal ((COMMAND *)tcom, asynchronous, NO_PIPE, NO_PIPE, fds_to_close); + + /* If we are asked to, invert the return value. */ + if (invert) + return_code = (return_code == EXECUTION_SUCCESS) ? EXECUTION_FAILURE + : EXECUTION_SUCCESS; + + + /* If we were explicitly placed in a subshell with (), we need + to do the `shell cleanup' things, such as running traps[0]. */ + if (user_subshell && signal_is_trapped (0)) + { + last_command_exit_value = return_code; + return_code = run_exit_trap (); + } + +#if 0 + subshell_level--; /* don't bother, caller will just exit */ +#endif + return (return_code); + /* NOTREACHED */ +} + +#if defined (COPROCESS_SUPPORT) +#define COPROC_MAX 16 + +typedef struct cpelement + { + struct cpelement *next; + struct coproc *coproc; + } +cpelement_t; + +typedef struct cplist + { + struct cpelement *head; + struct cpelement *tail; + int ncoproc; + int lock; + } +cplist_t; + +static struct cpelement *cpe_alloc PARAMS((struct coproc *)); +static void cpe_dispose PARAMS((struct cpelement *)); +static struct cpelement *cpl_add PARAMS((struct coproc *)); +static struct cpelement *cpl_delete PARAMS((pid_t)); +static void cpl_reap PARAMS((void)); +static void cpl_flush PARAMS((void)); +static void cpl_closeall PARAMS((void)); +static struct cpelement *cpl_search PARAMS((pid_t)); +static struct cpelement *cpl_searchbyname PARAMS((const char *)); +static void cpl_prune PARAMS((void)); + +static void coproc_free PARAMS((struct coproc *)); + +/* Will go away when there is fully-implemented support for multiple coprocs. */ +Coproc sh_coproc = { 0, NO_PID, -1, -1, 0, 0, 0, 0, 0 }; + +cplist_t coproc_list = {0, 0, 0}; + +/* Functions to manage the list of coprocs */ + +static struct cpelement * +cpe_alloc (cp) + Coproc *cp; +{ + struct cpelement *cpe; + + cpe = (struct cpelement *)xmalloc (sizeof (struct cpelement)); + cpe->coproc = cp; + cpe->next = (struct cpelement *)0; + return cpe; +} + +static void +cpe_dispose (cpe) + struct cpelement *cpe; +{ + free (cpe); +} + +static struct cpelement * +cpl_add (cp) + Coproc *cp; +{ + struct cpelement *cpe; + + cpe = cpe_alloc (cp); + + if (coproc_list.head == 0) + { + coproc_list.head = coproc_list.tail = cpe; + coproc_list.ncoproc = 0; /* just to make sure */ + } + else + { + coproc_list.tail->next = cpe; + coproc_list.tail = cpe; + } + coproc_list.ncoproc++; + + return cpe; +} + +static struct cpelement * +cpl_delete (pid) + pid_t pid; +{ + struct cpelement *prev, *p; + + for (prev = p = coproc_list.head; p; prev = p, p = p->next) + if (p->coproc->c_pid == pid) + { + prev->next = p->next; /* remove from list */ + break; + } + + if (p == 0) + return 0; /* not found */ + + INTERNAL_DEBUG (("cpl_delete: deleting %d", pid)); + + /* Housekeeping in the border cases. */ + if (p == coproc_list.head) + coproc_list.head = coproc_list.head->next; + else if (p == coproc_list.tail) + coproc_list.tail = prev; + + coproc_list.ncoproc--; + if (coproc_list.ncoproc == 0) + coproc_list.head = coproc_list.tail = 0; + else if (coproc_list.ncoproc == 1) + coproc_list.tail = coproc_list.head; /* just to make sure */ + + return (p); +} + +static void +cpl_reap () +{ + struct cpelement *p, *next, *nh, *nt; + + /* Build a new list by removing dead coprocs and fix up the coproc_list + pointers when done. */ + nh = nt = next = (struct cpelement *)0; + for (p = coproc_list.head; p; p = next) + { + next = p->next; + if (p->coproc->c_flags & COPROC_DEAD) + { + coproc_list.ncoproc--; /* keep running count, fix up pointers later */ + INTERNAL_DEBUG (("cpl_reap: deleting %d", p->coproc->c_pid)); + coproc_dispose (p->coproc); + cpe_dispose (p); + } + else if (nh == 0) + nh = nt = p; + else + { + nt->next = p; + nt = nt->next; + } + } + + if (coproc_list.ncoproc == 0) + coproc_list.head = coproc_list.tail = 0; + else + { + if (nt) + nt->next = 0; + coproc_list.head = nh; + coproc_list.tail = nt; + if (coproc_list.ncoproc == 1) + coproc_list.tail = coproc_list.head; /* just to make sure */ + } +} + +/* Clear out the list of saved statuses */ +static void +cpl_flush () +{ + struct cpelement *cpe, *p; + + for (cpe = coproc_list.head; cpe; ) + { + p = cpe; + cpe = cpe->next; + + coproc_dispose (p->coproc); + cpe_dispose (p); + } + + coproc_list.head = coproc_list.tail = 0; + coproc_list.ncoproc = 0; +} + +static void +cpl_closeall () +{ + struct cpelement *cpe; + + for (cpe = coproc_list.head; cpe; cpe = cpe->next) + coproc_close (cpe->coproc); +} + +static void +cpl_fdchk (fd) + int fd; +{ + struct cpelement *cpe; + + for (cpe = coproc_list.head; cpe; cpe = cpe->next) + coproc_checkfd (cpe->coproc, fd); +} + +/* Search for PID in the list of coprocs; return the cpelement struct if + found. If not found, return NULL. */ +static struct cpelement * +cpl_search (pid) + pid_t pid; +{ + struct cpelement *cpe; + + for (cpe = coproc_list.head ; cpe; cpe = cpe->next) + if (cpe->coproc->c_pid == pid) + return cpe; + return (struct cpelement *)NULL; +} + +/* Search for the coproc named NAME in the list of coprocs; return the + cpelement struct if found. If not found, return NULL. */ +static struct cpelement * +cpl_searchbyname (name) + const char *name; +{ + struct cpelement *cp; + + for (cp = coproc_list.head ; cp; cp = cp->next) + if (STREQ (cp->coproc->c_name, name)) + return cp; + return (struct cpelement *)NULL; +} + +static pid_t +cpl_firstactive () +{ + struct cpelement *cpe; + + for (cpe = coproc_list.head ; cpe; cpe = cpe->next) + if ((cpe->coproc->c_flags & COPROC_DEAD) == 0) + return cpe->coproc->c_pid; + return (pid_t)NO_PID; +} + +#if 0 +static void +cpl_prune () +{ + struct cpelement *cp; + + while (coproc_list.head && coproc_list.ncoproc > COPROC_MAX) + { + cp = coproc_list.head; + coproc_list.head = coproc_list.head->next; + coproc_dispose (cp->coproc); + cpe_dispose (cp); + coproc_list.ncoproc--; + } +} +#endif + +/* These currently use a single global "shell coproc" but are written in a + way to not preclude additional coprocs later (using the list management + package above). */ + +struct coproc * +getcoprocbypid (pid) + pid_t pid; +{ +#if MULTIPLE_COPROCS + struct cpelement *p; + + p = cpl_search (pid); + return (p ? p->coproc : 0); +#else + return (pid == sh_coproc.c_pid ? &sh_coproc : 0); +#endif +} + +struct coproc * +getcoprocbyname (name) + const char *name; +{ +#if MULTIPLE_COPROCS + struct cpelement *p; + + p = cpl_searchbyname (name); + return (p ? p->coproc : 0); +#else + return ((sh_coproc.c_name && STREQ (sh_coproc.c_name, name)) ? &sh_coproc : 0); +#endif +} + +void +coproc_init (cp) + struct coproc *cp; +{ + cp->c_name = 0; + cp->c_pid = NO_PID; + cp->c_rfd = cp->c_wfd = -1; + cp->c_rsave = cp->c_wsave = -1; + cp->c_flags = cp->c_status = cp->c_lock = 0; +} + +struct coproc * +coproc_alloc (name, pid) + char *name; + pid_t pid; +{ + struct coproc *cp; + +#if MULTIPLE_COPROCS + cp = (struct coproc *)xmalloc (sizeof (struct coproc)); +#else + cp = &sh_coproc; +#endif + coproc_init (cp); + cp->c_lock = 2; + + cp->c_pid = pid; + cp->c_name = savestring (name); +#if MULTIPLE_COPROCS + cpl_add (cp); +#endif + cp->c_lock = 0; + return (cp); +} + +static void +coproc_free (cp) + struct coproc *cp; +{ + free (cp); +} + +void +coproc_dispose (cp) + struct coproc *cp; +{ + sigset_t set, oset; + + if (cp == 0) + return; + + BLOCK_SIGNAL (SIGCHLD, set, oset); + cp->c_lock = 3; + coproc_unsetvars (cp); + FREE (cp->c_name); + coproc_close (cp); +#if MULTIPLE_COPROCS + coproc_free (cp); +#else + coproc_init (cp); + cp->c_lock = 0; +#endif + UNBLOCK_SIGNAL (oset); +} + +/* Placeholder for now. Will require changes for multiple coprocs */ +void +coproc_flush () +{ +#if MULTIPLE_COPROCS + cpl_flush (); +#else + coproc_dispose (&sh_coproc); +#endif +} + +void +coproc_close (cp) + struct coproc *cp; +{ + if (cp->c_rfd >= 0) + { + close (cp->c_rfd); + cp->c_rfd = -1; + } + if (cp->c_wfd >= 0) + { + close (cp->c_wfd); + cp->c_wfd = -1; + } + cp->c_rsave = cp->c_wsave = -1; +} + +void +coproc_closeall () +{ +#if MULTIPLE_COPROCS + cpl_closeall (); +#else + coproc_close (&sh_coproc); /* XXX - will require changes for multiple coprocs */ +#endif +} + +void +coproc_reap () +{ +#if MULTIPLE_COPROCS + cpl_reap (); +#else + struct coproc *cp; + + cp = &sh_coproc; /* XXX - will require changes for multiple coprocs */ + if (cp && (cp->c_flags & COPROC_DEAD)) + coproc_dispose (cp); +#endif +} + +void +coproc_rclose (cp, fd) + struct coproc *cp; + int fd; +{ + if (cp->c_rfd >= 0 && cp->c_rfd == fd) + { + close (cp->c_rfd); + cp->c_rfd = -1; + } +} + +void +coproc_wclose (cp, fd) + struct coproc *cp; + int fd; +{ + if (cp->c_wfd >= 0 && cp->c_wfd == fd) + { + close (cp->c_wfd); + cp->c_wfd = -1; + } +} + +void +coproc_checkfd (cp, fd) + struct coproc *cp; + int fd; +{ + int update; + + update = 0; + if (cp->c_rfd >= 0 && cp->c_rfd == fd) + update = cp->c_rfd = -1; + if (cp->c_wfd >= 0 && cp->c_wfd == fd) + update = cp->c_wfd = -1; + if (update) + coproc_setvars (cp); +} + +void +coproc_fdchk (fd) + int fd; +{ +#if MULTIPLE_COPROCS + cpl_fdchk (fd); +#else + coproc_checkfd (&sh_coproc, fd); +#endif +} + +void +coproc_fdclose (cp, fd) + struct coproc *cp; + int fd; +{ + coproc_rclose (cp, fd); + coproc_wclose (cp, fd); + coproc_setvars (cp); +} + +void +coproc_fdsave (cp) + struct coproc *cp; +{ + cp->c_rsave = cp->c_rfd; + cp->c_wsave = cp->c_wfd; +} + +void +coproc_fdrestore (cp) + struct coproc *cp; +{ + cp->c_rfd = cp->c_rsave; + cp->c_wfd = cp->c_wsave; +} + +static void +coproc_setstatus (cp, status) + struct coproc *cp; + int status; +{ + cp->c_lock = 4; + cp->c_status = status; + cp->c_flags |= COPROC_DEAD; + cp->c_flags &= ~COPROC_RUNNING; + /* Don't dispose the coproc or unset the COPROC_XXX variables because + this is executed in a signal handler context. Wait until coproc_reap + takes care of it. */ + cp->c_lock = 0; +} + +void +coproc_pidchk (pid, status) + pid_t pid; + int status; +{ + struct coproc *cp; + +#if MULTIPLE_COPROCS + struct cpelement *cpe; + + /* We're not disposing the coproc because this is executed in a signal + handler context */ + cpe = cpl_search (pid); + cp = cpe ? cpe->coproc : 0; +#else + cp = getcoprocbypid (pid); +#endif + if (cp) + coproc_setstatus (cp, status); +} + +pid_t +coproc_active () +{ +#if MULTIPLE_COPROCS + return (cpl_firstactive ()); +#else + return ((sh_coproc.c_flags & COPROC_DEAD) ? NO_PID : sh_coproc.c_pid); +#endif +} +void +coproc_setvars (cp) + struct coproc *cp; +{ + SHELL_VAR *v; + char *namevar, *t; + size_t l; + WORD_DESC w; +#if defined (ARRAY_VARS) + arrayind_t ind; +#endif + + if (cp->c_name == 0) + return; + + /* We could do more here but right now we only check the name, warn if it's + not a valid identifier, and refuse to create variables with invalid names + if a coproc with such a name is supplied. */ + w.word = cp->c_name; + w.flags = 0; + if (check_identifier (&w, 1) == 0) + return; + + l = strlen (cp->c_name); + namevar = xmalloc (l + 16); + +#if defined (ARRAY_VARS) + v = find_variable (cp->c_name); + + /* This is the same code as in find_or_make_array_variable */ + if (v == 0) + { + v = find_variable_nameref_for_create (cp->c_name, 1); + if (v == INVALID_NAMEREF_VALUE) + { + free (namevar); + return; + } + if (v && nameref_p (v)) + { + free (cp->c_name); + cp->c_name = savestring (nameref_cell (v)); + v = make_new_array_variable (cp->c_name); + } + } + + if (v && (readonly_p (v) || noassign_p (v))) + { + if (readonly_p (v)) + err_readonly (cp->c_name); + free (namevar); + return; + } + if (v == 0) + v = make_new_array_variable (cp->c_name); + if (array_p (v) == 0) + v = convert_var_to_array (v); + + t = itos (cp->c_rfd); + ind = 0; + v = bind_array_variable (cp->c_name, ind, t, 0); + free (t); + + t = itos (cp->c_wfd); + ind = 1; + v = bind_array_variable (cp->c_name, ind, t, 0); + free (t); +#else + sprintf (namevar, "%s_READ", cp->c_name); + t = itos (cp->c_rfd); + bind_variable (namevar, t, 0); + free (t); + sprintf (namevar, "%s_WRITE", cp->c_name); + t = itos (cp->c_wfd); + bind_variable (namevar, t, 0); + free (t); +#endif + + sprintf (namevar, "%s_PID", cp->c_name); + t = itos (cp->c_pid); + v = bind_variable (namevar, t, 0); + free (t); + + free (namevar); +} + +void +coproc_unsetvars (cp) + struct coproc *cp; +{ + int l; + char *namevar; + + if (cp->c_name == 0) + return; + + l = strlen (cp->c_name); + namevar = xmalloc (l + 16); + + sprintf (namevar, "%s_PID", cp->c_name); + unbind_variable_noref (namevar); + +#if defined (ARRAY_VARS) + check_unbind_variable (cp->c_name); +#else + sprintf (namevar, "%s_READ", cp->c_name); + unbind_variable (namevar); + sprintf (namevar, "%s_WRITE", cp->c_name); + unbind_variable (namevar); +#endif + + free (namevar); +} + +static int +execute_coproc (command, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + int rpipe[2], wpipe[2], estat, invert; + pid_t coproc_pid; + Coproc *cp; + char *tcmd, *p, *name; + sigset_t set, oset; + + /* XXX -- can be removed after changes to handle multiple coprocs */ +#if !MULTIPLE_COPROCS + if (sh_coproc.c_pid != NO_PID && (sh_coproc.c_rfd >= 0 || sh_coproc.c_wfd >= 0)) + internal_warning (_("execute_coproc: coproc [%d:%s] still exists"), sh_coproc.c_pid, sh_coproc.c_name); + coproc_init (&sh_coproc); +#endif + + invert = (command->flags & CMD_INVERT_RETURN) != 0; + + /* expand name without splitting - could make this dependent on a shopt option */ + name = expand_string_unsplit_to_string (command->value.Coproc->name, 0); + /* Optional check -- could be relaxed */ + if (legal_identifier (name) == 0) + { + internal_error (_("`%s': not a valid identifier"), name); + free (name); + return (invert ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + } + else + { + free (command->value.Coproc->name); + command->value.Coproc->name = name; + } + + command_string_index = 0; + tcmd = make_command_string (command); + + sh_openpipe ((int *)&rpipe); /* 0 = parent read, 1 = child write */ + sh_openpipe ((int *)&wpipe); /* 0 = child read, 1 = parent write */ + + BLOCK_SIGNAL (SIGCHLD, set, oset); + + coproc_pid = make_child (p = savestring (tcmd), FORK_ASYNC); + + if (coproc_pid == 0) + { + close (rpipe[0]); + close (wpipe[1]); + +#if defined (JOB_CONTROL) + FREE (p); +#endif + + UNBLOCK_SIGNAL (oset); + estat = execute_in_subshell (command, 1, wpipe[0], rpipe[1], fds_to_close); + + fflush (stdout); + fflush (stderr); + + exit (estat); + } + + close (rpipe[1]); + close (wpipe[0]); + + cp = coproc_alloc (command->value.Coproc->name, coproc_pid); + cp->c_rfd = rpipe[0]; + cp->c_wfd = wpipe[1]; + + cp->c_flags |= COPROC_RUNNING; + + SET_CLOSE_ON_EXEC (cp->c_rfd); + SET_CLOSE_ON_EXEC (cp->c_wfd); + + coproc_setvars (cp); + + UNBLOCK_SIGNAL (oset); + +#if 0 + itrace ("execute_coproc (%s): [%d] %s", command->value.Coproc->name, coproc_pid, the_printed_command); +#endif + + close_pipes (pipe_in, pipe_out); +#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) + unlink_fifo_list (); +#endif + stop_pipeline (1, (COMMAND *)NULL); + DESCRIBE_PID (coproc_pid); + run_pending_traps (); + + return (invert ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} +#endif + +/* If S == -1, it's a special value saying to close stdin */ +static void +restore_stdin (s) + int s; +{ + if (s == -1) + close (0); + else + { + dup2 (s, 0); + close (s); + } +} + +/* Catch-all cleanup function for lastpipe code for unwind-protects */ +static void +lastpipe_cleanup (s) + int s; +{ + set_jobs_list_frozen (s); +} + +static int +execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int asynchronous, pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + int prev, fildes[2], new_bitmap_size, dummyfd, ignore_return, exec_result; + int lstdin, lastpipe_flag, lastpipe_jid, old_frozen, stdin_valid; + COMMAND *cmd; + struct fd_bitmap *fd_bitmap; + pid_t lastpid; + +#if defined (JOB_CONTROL) + sigset_t set, oset; + BLOCK_CHILD (set, oset); +#endif /* JOB_CONTROL */ + + ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; + + stdin_valid = sh_validfd (0); + + prev = pipe_in; + cmd = command; + + while (cmd && cmd->type == cm_connection && + cmd->value.Connection && cmd->value.Connection->connector == '|') + { + /* Make a pipeline between the two commands. */ + if (pipe (fildes) < 0) + { + sys_error (_("pipe error")); +#if defined (JOB_CONTROL) + terminate_current_pipeline (); + kill_current_pipeline (); + UNBLOCK_CHILD (oset); +#endif /* JOB_CONTROL */ + last_command_exit_value = EXECUTION_FAILURE; + /* The unwind-protects installed below will take care + of closing all of the open file descriptors. */ + throw_to_top_level (); + return (EXECUTION_FAILURE); /* XXX */ + } + + /* Here is a problem: with the new file close-on-exec + code, the read end of the pipe (fildes[0]) stays open + in the first process, so that process will never get a + SIGPIPE. There is no way to signal the first process + that it should close fildes[0] after forking, so it + remains open. No SIGPIPE is ever sent because there + is still a file descriptor open for reading connected + to the pipe. We take care of that here. This passes + around a bitmap of file descriptors that must be + closed after making a child process in execute_simple_command. */ + + /* We need fd_bitmap to be at least as big as fildes[0]. + If fildes[0] is less than fds_to_close->size, then + use fds_to_close->size. */ + new_bitmap_size = (fildes[0] < fds_to_close->size) + ? fds_to_close->size + : fildes[0] + 8; + + fd_bitmap = new_fd_bitmap (new_bitmap_size); + + /* Now copy the old information into the new bitmap. */ + xbcopy ((char *)fds_to_close->bitmap, (char *)fd_bitmap->bitmap, fds_to_close->size); + + /* And mark the pipe file descriptors to be closed. */ + fd_bitmap->bitmap[fildes[0]] = 1; + + /* In case there are pipe or out-of-processes errors, we + want all these file descriptors to be closed when + unwind-protects are run, and the storage used for the + bitmaps freed up. */ + begin_unwind_frame ("pipe-file-descriptors"); + add_unwind_protect (dispose_fd_bitmap, fd_bitmap); + add_unwind_protect (close_fd_bitmap, fd_bitmap); + if (prev >= 0) + add_unwind_protect (close, prev); + dummyfd = fildes[1]; + add_unwind_protect (close, dummyfd); + +#if defined (JOB_CONTROL) + add_unwind_protect (restore_signal_mask, &oset); +#endif /* JOB_CONTROL */ + + if (ignore_return && cmd->value.Connection->first) + cmd->value.Connection->first->flags |= CMD_IGNORE_RETURN; + execute_command_internal (cmd->value.Connection->first, asynchronous, + prev, fildes[1], fd_bitmap); + + if (prev >= 0) + close (prev); + + prev = fildes[0]; + close (fildes[1]); + + dispose_fd_bitmap (fd_bitmap); + discard_unwind_frame ("pipe-file-descriptors"); + + cmd = cmd->value.Connection->second; + } + + lastpid = last_made_pid; + + /* Now execute the rightmost command in the pipeline. */ + if (ignore_return && cmd) + cmd->flags |= CMD_IGNORE_RETURN; + + lastpipe_flag = 0; + + begin_unwind_frame ("lastpipe-exec"); + lstdin = -2; /* -1 is special, meaning fd 0 is closed */ + /* If the `lastpipe' option is set with shopt, and job control is not + enabled, execute the last element of non-async pipelines in the + current shell environment. */ + /* prev can be 0 if fd 0 was closed when this function was executed. prev + will never be 0 at this point if fd 0 was valid when this function was + executed (though we check above). */ + if (lastpipe_opt && job_control == 0 && asynchronous == 0 && pipe_out == NO_PIPE && prev >= 0) + { + /* -1 is a special value meaning to close stdin */ + lstdin = (prev > 0 && stdin_valid) ? move_to_high_fd (0, 1, -1) : -1; + if (lstdin > 0 || lstdin == -1) + { + do_piping (prev, pipe_out); + prev = NO_PIPE; + add_unwind_protect (restore_stdin, lstdin); + lastpipe_flag = 1; + old_frozen = freeze_jobs_list (); + lastpipe_jid = stop_pipeline (0, (COMMAND *)NULL); /* XXX */ + add_unwind_protect (lastpipe_cleanup, old_frozen); +#if defined (JOB_CONTROL) + UNBLOCK_CHILD (oset); /* XXX */ +#endif + } + if (cmd) + cmd->flags |= CMD_LASTPIPE; + } + if (prev >= 0) + add_unwind_protect (close, prev); + + exec_result = execute_command_internal (cmd, asynchronous, prev, pipe_out, fds_to_close); + + if (prev >= 0) + close (prev); + + if (lstdin > 0 || lstdin == -1) + restore_stdin (lstdin); + +#if defined (JOB_CONTROL) + UNBLOCK_CHILD (oset); +#endif + + QUIT; + + if (lastpipe_flag) + { +#if defined (JOB_CONTROL) + if (INVALID_JOB (lastpipe_jid) == 0) + { + append_process (savestring (the_printed_command_except_trap), dollar_dollar_pid, exec_result, lastpipe_jid); + lstdin = wait_for (lastpid, 0); + } + else + { + lstdin = wait_for_single_pid (lastpid, 0); /* checks bgpids list */ + if (lstdin > 256) /* error sentinel */ + lstdin = 127; + } +#else + lstdin = wait_for (lastpid, 0); +#endif + +#if defined (JOB_CONTROL) + /* If wait_for removes the job from the jobs table, use result of last + command as pipeline's exit status as usual. The jobs list can get + frozen and unfrozen at inconvenient times if there are multiple pipelines + running simultaneously. */ + if (INVALID_JOB (lastpipe_jid) == 0) + exec_result = job_exit_status (lastpipe_jid); + else if (pipefail_opt) + exec_result = exec_result | lstdin; /* XXX */ + /* otherwise we use exec_result */ +#endif + + set_jobs_list_frozen (old_frozen); + } + + discard_unwind_frame ("lastpipe-exec"); + + return (exec_result); +} + +static int +execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int asynchronous, pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + COMMAND *tc, *second; + int ignore_return, exec_result, was_error_trap, invert; + volatile int save_line_number; + + ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; + + switch (command->value.Connection->connector) + { + /* Do the first command asynchronously. */ + case '&': + tc = command->value.Connection->first; + if (tc == 0) + return (EXECUTION_SUCCESS); + + if (ignore_return) + tc->flags |= CMD_IGNORE_RETURN; + tc->flags |= CMD_AMPERSAND; + + /* If this shell was compiled without job control support, + if we are currently in a subshell via `( xxx )', or if job + control is not active then the standard input for an + asynchronous command is forced to /dev/null. */ +#if defined (JOB_CONTROL) + if ((subshell_environment || !job_control) && !stdin_redir) +#else + if (!stdin_redir) +#endif /* JOB_CONTROL */ + tc->flags |= CMD_STDIN_REDIR; + + exec_result = execute_command_internal (tc, 1, pipe_in, pipe_out, fds_to_close); + QUIT; + + if (tc->flags & CMD_STDIN_REDIR) + tc->flags &= ~CMD_STDIN_REDIR; + + second = command->value.Connection->second; + if (second) + { + if (ignore_return) + second->flags |= CMD_IGNORE_RETURN; + + exec_result = execute_command_internal (second, asynchronous, pipe_in, pipe_out, fds_to_close); + } + + break; + + /* Just call execute command on both sides. */ + case ';': + case '\n': /* special case, happens in command substitutions */ + if (ignore_return) + { + if (command->value.Connection->first) + command->value.Connection->first->flags |= CMD_IGNORE_RETURN; + if (command->value.Connection->second) + command->value.Connection->second->flags |= CMD_IGNORE_RETURN; + } + executing_list++; + QUIT; + +#if 1 + execute_command (command->value.Connection->first); +#else + execute_command_internal (command->value.Connection->first, + asynchronous, pipe_in, pipe_out, + fds_to_close); +#endif + + QUIT; + optimize_connection_fork (command); /* XXX */ + exec_result = execute_command_internal (command->value.Connection->second, + asynchronous, pipe_in, pipe_out, + fds_to_close); + executing_list--; + break; + + case '|': + was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0; + invert = (command->flags & CMD_INVERT_RETURN) != 0; + ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; + + SET_LINE_NUMBER (line_number); /* XXX - save value? */ + exec_result = execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close); + + if (asynchronous) + { + exec_result = EXECUTION_SUCCESS; + invert = 0; + } + + if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS) + { + last_command_exit_value = exec_result; + save_line_number = line_number; + line_number = line_number_for_err_trap; + run_error_trap (); + line_number = save_line_number; + } + + if (ignore_return == 0 && invert == 0 && exit_immediately_on_error && exec_result != EXECUTION_SUCCESS) + { + last_command_exit_value = exec_result; + run_pending_traps (); + jump_to_top_level (ERREXIT); + } + + break; + + case AND_AND: + case OR_OR: + if (asynchronous) + { + /* If we have something like `a && b &' or `a || b &', run the + && or || stuff in a subshell. Force a subshell and just call + execute_command_internal again. Leave asynchronous on + so that we get a report from the parent shell about the + background job. */ + command->flags |= CMD_FORCE_SUBSHELL; + exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); + break; + } + + /* Execute the first command. If the result of that is successful + and the connector is AND_AND, or the result is not successful + and the connector is OR_OR, then execute the second command, + otherwise return. */ + + executing_list++; + if (command->value.Connection->first) + command->value.Connection->first->flags |= CMD_IGNORE_RETURN; + +#if 1 + exec_result = execute_command (command->value.Connection->first); +#else + exec_result = execute_command_internal (command->value.Connection->first, 0, NO_PIPE, NO_PIPE, fds_to_close); +#endif + QUIT; + if (((command->value.Connection->connector == AND_AND) && + (exec_result == EXECUTION_SUCCESS)) || + ((command->value.Connection->connector == OR_OR) && + (exec_result != EXECUTION_SUCCESS))) + { + optimize_connection_fork (command); + + second = command->value.Connection->second; + if (ignore_return && second) + second->flags |= CMD_IGNORE_RETURN; + + exec_result = execute_command (second); + } + executing_list--; + break; + + default: + command_error ("execute_connection", CMDERR_BADCONN, command->value.Connection->connector, 0); + jump_to_top_level (DISCARD); + exec_result = EXECUTION_FAILURE; + } + + return exec_result; +} + +/* The test used to be only for interactive_shell, but we don't want to report + job status when the shell is not interactive or when job control isn't + enabled. */ +#define REAP() \ + do \ + { \ + if (job_control == 0 || interactive_shell == 0) \ + reap_dead_jobs (); \ + } \ + while (0) + +/* Execute a FOR command. The syntax is: FOR word_desc IN word_list; + DO command; DONE */ +static int +execute_for_command (for_command) + FOR_COM *for_command; +{ + register WORD_LIST *releaser, *list; + SHELL_VAR *v; + char *identifier; + int retval, save_line_number; +#if 0 + SHELL_VAR *old_value = (SHELL_VAR *)NULL; /* Remember the old value of x. */ +#endif + + save_line_number = line_number; + if (check_identifier (for_command->name, 1) == 0) + { + if (posixly_correct && interactive_shell == 0) + { + last_command_exit_value = EX_BADUSAGE; + jump_to_top_level (ERREXIT); + } + return (EXECUTION_FAILURE); + } + + loop_level++; + identifier = for_command->name->word; + + line_number = for_command->line; /* for expansion error messages */ + list = releaser = expand_words_no_vars (for_command->map_list); + + begin_unwind_frame ("for"); + add_unwind_protect (dispose_words, releaser); + +#if 0 + if (lexical_scoping) + { + old_value = copy_variable (find_variable (identifier)); + if (old_value) + add_unwind_protect (dispose_variable, old_value); + } +#endif + + if (for_command->flags & CMD_IGNORE_RETURN) + for_command->action->flags |= CMD_IGNORE_RETURN; + + for (retval = EXECUTION_SUCCESS; list; list = list->next) + { + QUIT; + + line_number = for_command->line; + + /* Remember what this command looks like, for debugger. */ + command_string_index = 0; + print_for_command_head (for_command); + + if (echo_command_at_execute) + xtrace_print_for_command_head (for_command); + + /* Save this command unless it's a trap command and we're not running + a debug trap. */ + if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0) + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + retval = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + continue; +#endif + + this_command_name = (char *)NULL; + /* XXX - special ksh93 for command index variable handling */ + v = find_variable_last_nameref (identifier, 1); + if (v && nameref_p (v)) + { + if (valid_nameref_value (list->word->word, 1) == 0) + { + sh_invalidid (list->word->word); + v = 0; + } + else if (readonly_p (v)) + err_readonly (name_cell (v)); + else + v = bind_variable_value (v, list->word->word, ASS_NAMEREF); + } + else + v = bind_variable (identifier, list->word->word, 0); + + if (v == 0 || readonly_p (v) || noassign_p (v)) + { + line_number = save_line_number; + if (v && readonly_p (v) && interactive_shell == 0 && posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; + jump_to_top_level (FORCE_EOF); + } + else + { + dispose_words (releaser); + discard_unwind_frame ("for"); + loop_level--; + return (EXECUTION_FAILURE); + } + } + + if (ifsname (identifier)) + setifs (v); + else + stupidly_hack_special_variables (identifier); + + retval = execute_command (for_command->action); + REAP (); + QUIT; + + if (breaking) + { + breaking--; + break; + } + + if (continuing) + { + continuing--; + if (continuing) + break; + } + } + + loop_level--; + line_number = save_line_number; + +#if 0 + if (lexical_scoping) + { + if (!old_value) + unbind_variable (identifier); + else + { + SHELL_VAR *new_value; + + new_value = bind_variable (identifier, value_cell (old_value), 0); + new_value->attributes = old_value->attributes; + dispose_variable (old_value); + } + } +#endif + + dispose_words (releaser); + discard_unwind_frame ("for"); + return (retval); +} + +#if defined (ARITH_FOR_COMMAND) +/* Execute an arithmetic for command. The syntax is + + for (( init ; step ; test )) + do + body + done + + The execution should be exactly equivalent to + + eval \(\( init \)\) + while eval \(\( test \)\) ; do + body; + eval \(\( step \)\) + done +*/ +static intmax_t +eval_arith_for_expr (l, okp) + WORD_LIST *l; + int *okp; +{ + WORD_LIST *new; + intmax_t expresult; + int r, eflag; + char *expr, *temp; + + expr = l->next ? string_list (l) : l->word->word; + temp = expand_arith_string (expr, Q_DOUBLE_QUOTES|Q_ARITH); + if (l->next) + free (expr); + new = make_word_list (make_word (temp), (WORD_LIST *)NULL); + free (temp); + + if (new) + { + if (echo_command_at_execute) + xtrace_print_arith_cmd (new); + + command_string_index = 0; + print_arith_command (new); + if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0) + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + r = run_debug_trap (); + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED; + this_command_name = "(("; /* )) for expression error messages */ + +#if defined (DEBUGGER) + if (debugging_mode == 0 || r == EXECUTION_SUCCESS) + expresult = evalexp (new->word->word, eflag, okp); + else + { + expresult = 0; + if (okp) + *okp = 1; + } +#else + expresult = evalexp (new->word->word, eflag, okp); +#endif + dispose_words (new); + } + else + { + expresult = 0; + if (okp) + *okp = 1; + } + return (expresult); +} + +static int +execute_arith_for_command (arith_for_command) + ARITH_FOR_COM *arith_for_command; +{ + intmax_t expresult; + int expok, body_status, arith_lineno, save_lineno; + + body_status = EXECUTION_SUCCESS; + loop_level++; + save_lineno = line_number; + + if (arith_for_command->flags & CMD_IGNORE_RETURN) + arith_for_command->action->flags |= CMD_IGNORE_RETURN; + + this_command_name = "(("; /* )) for expression error messages */ + + /* save the starting line number of the command so we can reset + line_number before executing each expression -- for $LINENO + and the DEBUG trap. */ + line_number = arith_lineno = arith_for_command->line; + if (variable_context && interactive_shell && sourcelevel == 0) + { + /* line numbers in a function start at 1 */ + line_number -= function_line_number - 1; + if (line_number <= 0) + line_number = 1; + } + + /* Evaluate the initialization expression. */ + expresult = eval_arith_for_expr (arith_for_command->init, &expok); + if (expok == 0) + { + line_number = save_lineno; + return (EXECUTION_FAILURE); + } + + while (1) + { + /* Evaluate the test expression. */ + line_number = arith_lineno; + expresult = eval_arith_for_expr (arith_for_command->test, &expok); + line_number = save_lineno; + + if (expok == 0) + { + body_status = EXECUTION_FAILURE; + break; + } + REAP (); + if (expresult == 0) + break; + + /* Execute the body of the arithmetic for command. */ + QUIT; + body_status = execute_command (arith_for_command->action); + QUIT; + + /* Handle any `break' or `continue' commands executed by the body. */ + if (breaking) + { + breaking--; + break; + } + + if (continuing) + { + continuing--; + if (continuing) + break; + } + + /* Evaluate the step expression. */ + line_number = arith_lineno; + expresult = eval_arith_for_expr (arith_for_command->step, &expok); + line_number = save_lineno; + + if (expok == 0) + { + body_status = EXECUTION_FAILURE; + break; + } + } + + loop_level--; + line_number = save_lineno; + + return (body_status); +} +#endif + +#if defined (SELECT_COMMAND) +static int LINES, COLS, tabsize; + +#define RP_SPACE ") " +#define RP_SPACE_LEN 2 + +/* XXX - does not handle numbers > 1000000 at all. */ +#define NUMBER_LEN(s) \ +((s < 10) ? 1 \ + : ((s < 100) ? 2 \ + : ((s < 1000) ? 3 \ + : ((s < 10000) ? 4 \ + : ((s < 100000) ? 5 \ + : 6))))) + +static int +displen (s) + const char *s; +{ +#if defined (HANDLE_MULTIBYTE) + wchar_t *wcstr; + size_t slen; + int wclen; + + wcstr = 0; + slen = mbstowcs (wcstr, s, 0); + if (slen == -1) + slen = 0; + wcstr = (wchar_t *)xmalloc (sizeof (wchar_t) * (slen + 1)); + mbstowcs (wcstr, s, slen + 1); + wclen = wcswidth (wcstr, slen); + free (wcstr); + return (wclen < 0 ? STRLEN(s) : wclen); +#else + return (STRLEN (s)); +#endif +} + +static int +print_index_and_element (len, ind, list) + int len, ind; + WORD_LIST *list; +{ + register WORD_LIST *l; + register int i; + + if (list == 0) + return (0); + for (i = ind, l = list; l && --i; l = l->next) + ; + if (l == 0) /* don't think this can happen */ + return (0); + fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word); + return (displen (l->word->word)); +} + +static void +indent (from, to) + int from, to; +{ + while (from < to) + { + if ((to / tabsize) > (from / tabsize)) + { + putc ('\t', stderr); + from += tabsize - from % tabsize; + } + else + { + putc (' ', stderr); + from++; + } + } +} + +static void +print_select_list (list, list_len, max_elem_len, indices_len) + WORD_LIST *list; + int list_len, max_elem_len, indices_len; +{ + int ind, row, elem_len, pos, cols, rows; + int first_column_indices_len, other_indices_len; + + if (list == 0) + { + putc ('\n', stderr); + return; + } + + cols = max_elem_len ? COLS / max_elem_len : 1; + if (cols == 0) + cols = 1; + rows = list_len ? list_len / cols + (list_len % cols != 0) : 1; + cols = list_len ? list_len / rows + (list_len % rows != 0) : 1; + + if (rows == 1) + { + rows = cols; + cols = 1; + } + + first_column_indices_len = NUMBER_LEN (rows); + other_indices_len = indices_len; + + for (row = 0; row < rows; row++) + { + ind = row; + pos = 0; + while (1) + { + indices_len = (pos == 0) ? first_column_indices_len : other_indices_len; + elem_len = print_index_and_element (indices_len, ind + 1, list); + elem_len += indices_len + RP_SPACE_LEN; + ind += rows; + if (ind >= list_len) + break; + indent (pos + elem_len, pos + max_elem_len); + pos += max_elem_len; + } + putc ('\n', stderr); + } +} + +/* Print the elements of LIST, one per line, preceded by an index from 1 to + LIST_LEN. Then display PROMPT and wait for the user to enter a number. + If the number is between 1 and LIST_LEN, return that selection. If EOF + is read, return a null string. If a blank line is entered, or an invalid + number is entered, the loop is executed again. */ +static char * +select_query (list, list_len, prompt, print_menu) + WORD_LIST *list; + int list_len; + char *prompt; + int print_menu; +{ + int max_elem_len, indices_len, len, r, oe; + intmax_t reply; + WORD_LIST *l; + char *repl_string, *t; + + COLS = default_columns (); + +#if 0 + t = get_string_value ("TABSIZE"); + tabsize = (t && *t) ? atoi (t) : 8; + if (tabsize <= 0) + tabsize = 8; +#else + tabsize = 8; +#endif + + max_elem_len = 0; + for (l = list; l; l = l->next) + { + len = displen (l->word->word); + if (len > max_elem_len) + max_elem_len = len; + } + indices_len = NUMBER_LEN (list_len); + max_elem_len += indices_len + RP_SPACE_LEN + 2; + + while (1) + { + if (print_menu) + print_select_list (list, list_len, max_elem_len, indices_len); + fprintf (stderr, "%s", prompt); + fflush (stderr); + QUIT; + + oe = executing_builtin; + executing_builtin = 1; + r = read_builtin ((WORD_LIST *)NULL); + executing_builtin = oe; + if (r != EXECUTION_SUCCESS) + { + putchar ('\n'); + return ((char *)NULL); + } + repl_string = get_string_value ("REPLY"); + if (repl_string == 0) + return ((char *)NULL); + if (*repl_string == 0) + { + print_menu = 1; + continue; + } + if (legal_number (repl_string, &reply) == 0) + return ""; + if (reply < 1 || reply > list_len) + return ""; + + for (l = list; l && --reply; l = l->next) + ; + return (l->word->word); /* XXX - can't be null? */ + } +} + +/* Execute a SELECT command. The syntax is: + SELECT word IN list DO command_list DONE + Only `break' or `return' in command_list will terminate + the command. */ +static int +execute_select_command (select_command) + SELECT_COM *select_command; +{ + WORD_LIST *releaser, *list; + SHELL_VAR *v; + char *identifier, *ps3_prompt, *selection; + int retval, list_len, show_menu, save_line_number; + + if (check_identifier (select_command->name, 1) == 0) + return (EXECUTION_FAILURE); + + save_line_number = line_number; + line_number = select_command->line; + + command_string_index = 0; + print_select_command_head (select_command); + + if (echo_command_at_execute) + xtrace_print_select_command_head (select_command); + +#if 0 + if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0))) +#else + if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0) +#endif + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + retval = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + return (EXECUTION_SUCCESS); +#endif + + this_command_name = (char *)0; + + loop_level++; + identifier = select_command->name->word; + + /* command and arithmetic substitution, parameter and variable expansion, + word splitting, pathname expansion, and quote removal. */ + list = releaser = expand_words_no_vars (select_command->map_list); + list_len = list_length (list); + if (list == 0 || list_len == 0) + { + if (list) + dispose_words (list); + line_number = save_line_number; + return (EXECUTION_SUCCESS); + } + + begin_unwind_frame ("select"); + add_unwind_protect (dispose_words, releaser); + + if (select_command->flags & CMD_IGNORE_RETURN) + select_command->action->flags |= CMD_IGNORE_RETURN; + + retval = EXECUTION_SUCCESS; + show_menu = 1; + + while (1) + { + line_number = select_command->line; + ps3_prompt = get_string_value ("PS3"); + if (ps3_prompt == 0) + ps3_prompt = "#? "; + + QUIT; + selection = select_query (list, list_len, ps3_prompt, show_menu); + QUIT; + if (selection == 0) + { + /* select_query returns EXECUTION_FAILURE if the read builtin + fails, so we want to return failure in this case. */ + retval = EXECUTION_FAILURE; + break; + } + + v = bind_variable (identifier, selection, 0); + if (v == 0 || readonly_p (v) || noassign_p (v)) + { + if (v && readonly_p (v) && interactive_shell == 0 && posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; + jump_to_top_level (FORCE_EOF); + } + else + { + dispose_words (releaser); + discard_unwind_frame ("select"); + loop_level--; + line_number = save_line_number; + return (EXECUTION_FAILURE); + } + } + + stupidly_hack_special_variables (identifier); + + retval = execute_command (select_command->action); + + REAP (); + QUIT; + + if (breaking) + { + breaking--; + break; + } + + if (continuing) + { + continuing--; + if (continuing) + break; + } + +#if defined (KSH_COMPATIBLE_SELECT) + show_menu = 0; + selection = get_string_value ("REPLY"); + if (selection && *selection == '\0') + show_menu = 1; +#endif + } + + loop_level--; + line_number = save_line_number; + + dispose_words (releaser); + discard_unwind_frame ("select"); + return (retval); +} +#endif /* SELECT_COMMAND */ + +/* Execute a CASE command. The syntax is: CASE word_desc IN pattern_list ESAC. + The pattern_list is a linked list of pattern clauses; each clause contains + some patterns to compare word_desc against, and an associated command to + execute. */ +static int +execute_case_command (case_command) + CASE_COM *case_command; +{ + register WORD_LIST *list; + WORD_LIST *wlist, *es; + PATTERN_LIST *clauses; + char *word, *pattern; + int retval, match, ignore_return, save_line_number, qflags; + + save_line_number = line_number; + line_number = case_command->line; + + command_string_index = 0; + print_case_command_head (case_command); + + if (echo_command_at_execute) + xtrace_print_case_command_head (case_command); + +#if 0 + if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0))) +#else + if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0) +#endif + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + retval = run_debug_trap(); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + { + line_number = save_line_number; + return (EXECUTION_SUCCESS); + } +#endif + + /* Use the same expansions (the ones POSIX specifies) as the patterns; + dequote the resulting string (as POSIX specifies) since the quotes in + patterns are handled specially below. We have to do it in this order + because we're not supposed to perform word splitting. */ + wlist = expand_word_leave_quoted (case_command->word, 0); + if (wlist) + { + char *t; + t = string_list (wlist); + word = dequote_string (t); + free (t); + } + else + word = savestring (""); + dispose_words (wlist); + + retval = EXECUTION_SUCCESS; + ignore_return = case_command->flags & CMD_IGNORE_RETURN; + + begin_unwind_frame ("case"); + add_unwind_protect (xfree, word); + +#define EXIT_CASE() goto exit_case_command + + for (clauses = case_command->clauses; clauses; clauses = clauses->next) + { + QUIT; + for (list = clauses->patterns; list; list = list->next) + { + es = expand_word_leave_quoted (list->word, 0); + + if (es && es->word && es->word->word && *(es->word->word)) + { + /* Convert quoted null strings into empty strings. */ + qflags = QGLOB_CVTNULL; + + /* We left CTLESC in place quoting CTLESC and CTLNUL after the + call to expand_word_leave_quoted; tell quote_string_for_globbing + to remove those here. This works for both unquoted portions of + the word (which call quote_escapes) and quoted portions + (which call quote_string). */ + qflags |= QGLOB_CTLESC; + pattern = quote_string_for_globbing (es->word->word, qflags); + } + else + { + pattern = (char *)xmalloc (1); + pattern[0] = '\0'; + } + + /* Since the pattern does not undergo quote removal (as per + Posix.2, section 3.9.4.3), the strmatch () call must be able + to recognize backslashes as escape characters. */ + match = strmatch (pattern, word, FNMATCH_EXTFLAG|FNMATCH_IGNCASE) != FNM_NOMATCH; + free (pattern); + + dispose_words (es); + + if (match) + { + do + { + if (clauses->action && ignore_return) + clauses->action->flags |= CMD_IGNORE_RETURN; + retval = execute_command (clauses->action); + } + while ((clauses->flags & CASEPAT_FALLTHROUGH) && (clauses = clauses->next)); + if (clauses == 0 || (clauses->flags & CASEPAT_TESTNEXT) == 0) + EXIT_CASE (); + else + break; + } + + QUIT; + } + } + +exit_case_command: + free (word); + discard_unwind_frame ("case"); + line_number = save_line_number; + return (retval); +} + +#define CMD_WHILE 0 +#define CMD_UNTIL 1 + +/* The WHILE command. Syntax: WHILE test DO action; DONE. + Repeatedly execute action while executing test produces + EXECUTION_SUCCESS. */ +static int +execute_while_command (while_command) + WHILE_COM *while_command; +{ + return (execute_while_or_until (while_command, CMD_WHILE)); +} + +/* UNTIL is just like WHILE except that the test result is negated. */ +static int +execute_until_command (while_command) + WHILE_COM *while_command; +{ + return (execute_while_or_until (while_command, CMD_UNTIL)); +} + +/* The body for both while and until. The only difference between the + two is that the test value is treated differently. TYPE is + CMD_WHILE or CMD_UNTIL. The return value for both commands should + be EXECUTION_SUCCESS if no commands in the body are executed, and + the status of the last command executed in the body otherwise. */ +static int +execute_while_or_until (while_command, type) + WHILE_COM *while_command; + int type; +{ + int return_value, body_status; + + body_status = EXECUTION_SUCCESS; + loop_level++; + + while_command->test->flags |= CMD_IGNORE_RETURN; + if (while_command->flags & CMD_IGNORE_RETURN) + while_command->action->flags |= CMD_IGNORE_RETURN; + + while (1) + { + return_value = execute_command (while_command->test); + REAP (); + + /* Need to handle `break' in the test when we would break out of the + loop. The job control code will set `breaking' to loop_level + when a job in a loop is stopped with SIGTSTP. If the stopped job + is in the loop test, `breaking' will not be reset unless we do + this, and the shell will cease to execute commands. The same holds + true for `continue'. */ + if (type == CMD_WHILE && return_value != EXECUTION_SUCCESS) + { + if (breaking) + breaking--; + if (continuing) + continuing--; + break; + } + if (type == CMD_UNTIL && return_value == EXECUTION_SUCCESS) + { + if (breaking) + breaking--; + if (continuing) + continuing--; + break; + } + + QUIT; + body_status = execute_command (while_command->action); + QUIT; + + if (breaking) + { + breaking--; + break; + } + + if (continuing) + { + continuing--; + if (continuing) + break; + } + } + loop_level--; + + return (body_status); +} + +/* IF test THEN command [ELSE command]. + IF also allows ELIF in the place of ELSE IF, but + the parser makes *that* stupidity transparent. */ +static int +execute_if_command (if_command) + IF_COM *if_command; +{ + int return_value, save_line_number; + + save_line_number = line_number; + if_command->test->flags |= CMD_IGNORE_RETURN; + return_value = execute_command (if_command->test); + line_number = save_line_number; + + if (return_value == EXECUTION_SUCCESS) + { + QUIT; + + if (if_command->true_case && (if_command->flags & CMD_IGNORE_RETURN)) + if_command->true_case->flags |= CMD_IGNORE_RETURN; + + return (execute_command (if_command->true_case)); + } + else + { + QUIT; + + if (if_command->false_case && (if_command->flags & CMD_IGNORE_RETURN)) + if_command->false_case->flags |= CMD_IGNORE_RETURN; + + return (execute_command (if_command->false_case)); + } +} + +#if defined (DPAREN_ARITHMETIC) +static int +execute_arith_command (arith_command) + ARITH_COM *arith_command; +{ + int expok, save_line_number, retval, eflag; + intmax_t expresult; + WORD_LIST *new; + char *exp, *t; + + expresult = 0; + + save_line_number = line_number; + this_command_name = "(("; /* )) */ + SET_LINE_NUMBER (arith_command->line); + /* If we're in a function, update the line number information. */ + if (variable_context && interactive_shell && sourcelevel == 0) + { + /* line numbers in a function start at 1 */ + line_number -= function_line_number - 1; + if (line_number <= 0) + line_number = 1; + } + + command_string_index = 0; + print_arith_command (arith_command->exp); + + if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0) + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + /* Run the debug trap before each arithmetic command, but do it after we + update the line number information and before we expand the various + words in the expression. */ + retval = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + { + line_number = save_line_number; + return (EXECUTION_SUCCESS); + } +#endif + + this_command_name = "(("; /* )) */ + t = (char *)NULL; + new = arith_command->exp; + exp = (new->next) ? (t = string_list (new)) : new->word->word; + + exp = expand_arith_string (exp, Q_DOUBLE_QUOTES|Q_ARITH); + FREE (t); + + /* If we're tracing, make a new word list with `((' at the front and `))' + at the back and print it. Change xtrace_print_arith_cmd to take a string + when I change eval_arith_for_expr to use expand_arith_string(). */ + if (echo_command_at_execute) + { + new = make_word_list (make_word (exp ? exp : ""), (WORD_LIST *)NULL); + xtrace_print_arith_cmd (new); + dispose_words (new); + } + + if (exp) + { + eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED; + expresult = evalexp (exp, eflag, &expok); + line_number = save_line_number; + free (exp); + } + else + { + expresult = 0; + expok = 1; + } + + if (expok == 0) + return (EXECUTION_FAILURE); + + return (expresult == 0 ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} +#endif /* DPAREN_ARITHMETIC */ + +#if defined (COND_COMMAND) + +static char * const nullstr = ""; + +/* XXX - can COND ever be NULL when this is called? */ +static int +execute_cond_node (cond) + COND_COM *cond; +{ + int result, invert, patmatch, rmatch, arith, mode, mflags, ignore; + char *arg1, *arg2, *op; +#if 0 + char *t1, *t2; +#endif + + invert = (cond->flags & CMD_INVERT_RETURN); + ignore = (cond->flags & CMD_IGNORE_RETURN); + if (ignore) + { + if (cond->left) + cond->left->flags |= CMD_IGNORE_RETURN; + if (cond->right) + cond->right->flags |= CMD_IGNORE_RETURN; + } + + if (cond->type == COND_EXPR) + result = execute_cond_node (cond->left); + else if (cond->type == COND_OR) + { + result = execute_cond_node (cond->left); + if (result != EXECUTION_SUCCESS) + result = execute_cond_node (cond->right); + } + else if (cond->type == COND_AND) + { + result = execute_cond_node (cond->left); + if (result == EXECUTION_SUCCESS) + result = execute_cond_node (cond->right); + } + else if (cond->type == COND_UNARY) + { + int oa, varop, varflag; + + if (ignore) + comsub_ignore_return++; + varop = STREQ (cond->op->word, "-v"); +#if defined (ARRAY_VARS) + varflag = (varop && valid_array_reference (cond->left->op->word, VA_NOEXPAND)) ? TEST_ARRAYEXP : 0; +#else + varflag = 0; +#endif + arg1 = cond_expand_word (cond->left->op, varop ? 3 : 0); + if (ignore) + comsub_ignore_return--; + if (arg1 == 0) + arg1 = nullstr; + if (echo_command_at_execute) + xtrace_print_cond_term (cond->type, invert, cond->op, arg1, (char *)NULL); +#if defined (ARRAY_VARS) + if (varop) + oa = set_expand_once (0, 0); /* no-op for compatibility levels <= 51 */ +#endif + result = unary_test (cond->op->word, arg1, varflag) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; +#if defined (ARRAY_VARS) + if (varop) + assoc_expand_once = oa; +#endif + if (arg1 != nullstr) + free (arg1); + } + else if (cond->type == COND_BINARY) + { + rmatch = 0; + op = cond->op->word; + mode = 0; + patmatch = (((op[1] == '=') && (op[2] == '\0') && + (op[0] == '!' || op[0] == '=')) || + (op[0] == '=' && op[1] == '\0')); +#if defined (COND_REGEXP) + rmatch = (op[0] == '=' && op[1] == '~' && op[2] == '\0'); +#endif + arith = STREQ (op, "-eq") || STREQ (op, "-ne") || STREQ (op, "-lt") || + STREQ (op, "-le") || STREQ (op, "-gt") || STREQ (op, "-ge"); + + if (arith) + mode = 3; + else if (rmatch && shell_compatibility_level > 31) + mode = 2; + else if (patmatch) + mode = 1; + + if (ignore) + comsub_ignore_return++; + arg1 = cond_expand_word (cond->left->op, arith ? mode : 0); + if (ignore) + comsub_ignore_return--; + if (arg1 == 0) + arg1 = nullstr; + if (ignore) + comsub_ignore_return++; + arg2 = cond_expand_word (cond->right->op, mode); + if (ignore) + comsub_ignore_return--; + if (arg2 == 0) + arg2 = nullstr; + + if (echo_command_at_execute) + xtrace_print_cond_term (cond->type, invert, cond->op, arg1, arg2); + +#if defined (COND_REGEXP) + if (rmatch) + { + mflags = SHMAT_PWARN; +#if defined (ARRAY_VARS) + mflags |= SHMAT_SUBEXP; +#endif + +#if 0 + t1 = strescape(arg1); + t2 = strescape(arg2); + itrace("execute_cond_node: sh_regmatch on `%s' and `%s'", t1, t2); + free(t1); + free(t2); +#endif + + result = sh_regmatch (arg1, arg2, mflags); + } + else +#endif /* COND_REGEXP */ + { + int oe; + oe = extended_glob; + extended_glob = 1; + result = binary_test (cond->op->word, arg1, arg2, TEST_PATMATCH|TEST_ARITHEXP|TEST_LOCALE) + ? EXECUTION_SUCCESS + : EXECUTION_FAILURE; + extended_glob = oe; + } + if (arg1 != nullstr) + free (arg1); + if (arg2 != nullstr) + free (arg2); + } + else + { + command_error ("execute_cond_node", CMDERR_BADTYPE, cond->type, 0); + jump_to_top_level (DISCARD); + result = EXECUTION_FAILURE; + } + + if (invert) + result = (result == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; + + return result; +} + +static int +execute_cond_command (cond_command) + COND_COM *cond_command; +{ + int retval, save_line_number; + + save_line_number = line_number; + + SET_LINE_NUMBER (cond_command->line); + /* If we're in a function, update the line number information. */ + if (variable_context && interactive_shell && sourcelevel == 0) + { + /* line numbers in a function start at 1 */ + line_number -= function_line_number - 1; + if (line_number <= 0) + line_number = 1; + } + command_string_index = 0; + print_cond_command (cond_command); + + if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0) + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + /* Run the debug trap before each conditional command, but do it after we + update the line number information. */ + retval = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + { + line_number = save_line_number; + return (EXECUTION_SUCCESS); + } +#endif + + this_command_name = "[["; /* ]] */ + +#if 0 + debug_print_cond_command (cond_command); +#endif + + last_command_exit_value = retval = execute_cond_node (cond_command); + line_number = save_line_number; + return (retval); +} +#endif /* COND_COMMAND */ + +static void +bind_lastarg (arg) + char *arg; +{ + SHELL_VAR *var; + + if (arg == 0) + arg = ""; + var = bind_variable ("_", arg, 0); + if (var) + VUNSETATTR (var, att_exported); +} + +/* Execute a null command. Fork a subshell if the command uses pipes or is + to be run asynchronously. This handles all the side effects that are + supposed to take place. */ +static int +execute_null_command (redirects, pipe_in, pipe_out, async) + REDIRECT *redirects; + int pipe_in, pipe_out, async; +{ + int r; + int forcefork, fork_flags; + REDIRECT *rd; + + for (forcefork = 0, rd = redirects; rd; rd = rd->next) + { + forcefork += rd->rflags & REDIR_VARASSIGN; + /* Safety */ + forcefork += (rd->redirector.dest == 0 || fd_is_bash_input (rd->redirector.dest)) && (INPUT_REDIRECT (rd->instruction) || TRANSLATE_REDIRECT (rd->instruction) || rd->instruction == r_close_this); + } + + if (forcefork || pipe_in != NO_PIPE || pipe_out != NO_PIPE || async) + { + /* We have a null command, but we really want a subshell to take + care of it. Just fork, do piping and redirections, and exit. */ + fork_flags = async ? FORK_ASYNC : 0; + if (make_child ((char *)NULL, fork_flags) == 0) + { + /* Cancel traps, in trap.c. */ + restore_original_signals (); /* XXX */ + + do_piping (pipe_in, pipe_out); + +#if defined (COPROCESS_SUPPORT) + coproc_closeall (); +#endif + + interactive = 0; /* XXX */ + + subshell_environment = 0; + if (async) + subshell_environment |= SUBSHELL_ASYNC; + if (pipe_in != NO_PIPE || pipe_out != NO_PIPE) + subshell_environment |= SUBSHELL_PIPE; + + if (do_redirections (redirects, RX_ACTIVE) == 0) + exit (EXECUTION_SUCCESS); + else + exit (EXECUTION_FAILURE); + } + else + { + close_pipes (pipe_in, pipe_out); +#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) + if (pipe_out == NO_PIPE) + unlink_fifo_list (); +#endif + return (EXECUTION_SUCCESS); + } + } + else + { + /* Even if there aren't any command names, pretend to do the + redirections that are specified. The user expects the side + effects to take place. If the redirections fail, then return + failure. Otherwise, if a command substitution took place while + expanding the command or a redirection, return the value of that + substitution. Otherwise, return EXECUTION_SUCCESS. */ + + r = do_redirections (redirects, RX_ACTIVE|RX_UNDOABLE); + cleanup_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + + if (r != 0) + return (EXECUTION_FAILURE); + else if (last_command_subst_pid != NO_PID) + return (last_command_exit_value); + else + return (EXECUTION_SUCCESS); + } +} + +/* This is a hack to suppress word splitting for assignment statements + given as arguments to builtins with the ASSIGNMENT_BUILTIN flag set. */ +static void +fix_assignment_words (words) + WORD_LIST *words; +{ + WORD_LIST *w, *wcmd; + struct builtin *b; + int assoc, global, array, integer; + + if (words == 0) + return; + + b = 0; + assoc = global = array = integer = 0; + + /* Skip over assignment statements preceding a command name */ + wcmd = words; + for (wcmd = words; wcmd; wcmd = wcmd->next) + if ((wcmd->word->flags & W_ASSIGNMENT) == 0) + break; + /* Posix (post-2008) says that `command' doesn't change whether + or not the builtin it shadows is a `declaration command', even + though it removes other special builtin properties. In Posix + mode, we skip over one or more instances of `command' and + deal with the next word as the assignment builtin. */ + while (posixly_correct && wcmd && wcmd->word && wcmd->word->word && STREQ (wcmd->word->word, "command")) + wcmd = wcmd->next; + + for (w = wcmd; w; w = w->next) + if (w->word->flags & W_ASSIGNMENT) + { + /* Lazy builtin lookup, only do it if we find an assignment */ + if (b == 0) + { + b = builtin_address_internal (wcmd->word->word, 0); + if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0) + return; + else if (b && (b->flags & ASSIGNMENT_BUILTIN)) + wcmd->word->flags |= W_ASSNBLTIN; + } + w->word->flags |= (W_NOSPLIT|W_NOGLOB|W_TILDEEXP|W_ASSIGNARG); +#if defined (ARRAY_VARS) + if (assoc) + w->word->flags |= W_ASSIGNASSOC; + if (array) + w->word->flags |= W_ASSIGNARRAY; +#endif + if (global) + w->word->flags |= W_ASSNGLOBAL; + + /* If we have an assignment builtin that does not create local variables, + make sure we create global variables even if we internally call + `declare'. The CHKLOCAL flag means to set attributes or values on + an existing local variable, if there is one. */ + if (b && ((b->flags & (ASSIGNMENT_BUILTIN|LOCALVAR_BUILTIN)) == ASSIGNMENT_BUILTIN)) + w->word->flags |= W_ASSNGLOBAL|W_CHKLOCAL; + else if (b && (b->flags & ASSIGNMENT_BUILTIN) && (b->flags & LOCALVAR_BUILTIN) && variable_context) + w->word->flags |= W_FORCELOCAL; + } +#if defined (ARRAY_VARS) + /* Note that we saw an associative array option to a builtin that takes + assignment statements. This is a bit of a kludge. */ + else if (w->word->word[0] == '-' && (strpbrk (w->word->word+1, "Aag") != 0)) +#else + else if (w->word->word[0] == '-' && strchr (w->word->word+1, 'g')) +#endif + { + if (b == 0) + { + b = builtin_address_internal (wcmd->word->word, 0); + if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0) + return; + else if (b && (b->flags & ASSIGNMENT_BUILTIN)) + wcmd->word->flags |= W_ASSNBLTIN; + } + if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'A')) + assoc = 1; + else if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'a')) + array = 1; + if ((wcmd->word->flags & W_ASSNBLTIN) && strchr (w->word->word+1, 'g')) + global = 1; + } +} + +#if defined (ARRAY_VARS) +/* Set W_ARRAYREF on words that are valid array references to a builtin that + accepts them. This is intended to completely replace assoc_expand_once in + time. */ +static void +fix_arrayref_words (words) + WORD_LIST *words; +{ + WORD_LIST *w, *wcmd; + struct builtin *b; + + if (words == 0) + return; + + b = 0; + + /* Skip over assignment statements preceding a command name */ + wcmd = words; + for (wcmd = words; wcmd; wcmd = wcmd->next) + if ((wcmd->word->flags & W_ASSIGNMENT) == 0) + break; + + /* Skip over `command' */ + while (wcmd && wcmd->word && wcmd->word->word && STREQ (wcmd->word->word, "command")) + wcmd = wcmd->next; + + if (wcmd == 0) + return; + + /* If it's not an array reference builtin, we have nothing to do. */ + b = builtin_address_internal (wcmd->word->word, 0); + if (b == 0 || (b->flags & ARRAYREF_BUILTIN) == 0) + return; + + for (w = wcmd->next; w; w = w->next) + { + if (w->word && w->word->word && valid_array_reference (w->word->word, 0)) + w->word->flags |= W_ARRAYREF; + } +} +#endif + +#ifndef ISOPTION +# define ISOPTION(s, c) (s[0] == '-' && s[1] == c && s[2] == 0) +#endif + +#define RETURN_NOT_COMMAND() \ + do { if (typep) *typep = 0; return words; } while (0) + +/* Make sure we have `command [-p] command_name [args]', and handle skipping + over the usual `--' that ends the options. Returns the updated WORDS with + the command and options stripped and sets *TYPEP to a non-zero value. If + any other options are supplied, or there is not a command_name, we punt + and return a zero value in *TYPEP without updating WORDS. */ +static WORD_LIST * +check_command_builtin (words, typep) + WORD_LIST *words; + int *typep; +{ + int type; + WORD_LIST *w; + + w = words->next; + type = 1; + + if (w && ISOPTION (w->word->word, 'p')) /* command -p */ + { +#if defined (RESTRICTED_SHELL) + if (restricted) + RETURN_NOT_COMMAND(); +#endif + w = w->next; + type = 2; + } + + if (w && ISOPTION (w->word->word, '-')) /* command [-p] -- */ + w = w->next; + else if (w && w->word->word[0] == '-') /* any other option */ + RETURN_NOT_COMMAND(); + + if (w == 0 || w->word->word == 0) /* must have a command_name */ + RETURN_NOT_COMMAND(); + + if (typep) + *typep = type; + return w; +} + +/* Return 1 if the file found by searching $PATH for PATHNAME, defaulting + to PATHNAME, is a directory. Used by the autocd code below. */ +static int +is_dirname (pathname) + char *pathname; +{ + char *temp; + int ret; + + temp = search_for_command (pathname, 0); + ret = temp ? file_isdir (temp) : file_isdir (pathname); + free (temp); + return ret; +} + +/* The meaty part of all the executions. We have to start hacking the + real execution of commands here. Fork a process, set things up, + execute the command. */ +static int +execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close) + SIMPLE_COM *simple_command; + int pipe_in, pipe_out, async; + struct fd_bitmap *fds_to_close; +{ + WORD_LIST *words, *lastword; + char *command_line, *lastarg, *temp; + int first_word_quoted, result, builtin_is_special, already_forked, dofork; + int fork_flags, cmdflags; + pid_t old_last_async_pid; + sh_builtin_func_t *builtin; + SHELL_VAR *func; + volatile int old_builtin, old_command_builtin; + + result = EXECUTION_SUCCESS; + special_builtin_failed = builtin_is_special = 0; + command_line = (char *)0; + + QUIT; + + /* If we're in a function, update the line number information. */ + if (variable_context && interactive_shell && sourcelevel == 0) + { + /* line numbers in a function start at 1 */ + line_number -= function_line_number - 1; + if (line_number <= 0) + line_number = 1; + } + + /* Remember what this command line looks like at invocation. */ + command_string_index = 0; + print_simple_command (simple_command); + +#if 0 + if (signal_in_progress (DEBUG_TRAP) == 0 && (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0))) +#else + if (signal_in_progress (DEBUG_TRAP) == 0 && running_trap == 0) +#endif + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = the_printed_command ? savestring (the_printed_command) : (char *)0; + } + + /* Run the debug trap before each simple command, but do it after we + update the line number information. */ + result = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && result != EXECUTION_SUCCESS) + return (EXECUTION_SUCCESS); +#endif + + cmdflags = simple_command->flags; + + first_word_quoted = + simple_command->words ? (simple_command->words->word->flags & W_QUOTED) : 0; + + last_command_subst_pid = NO_PID; + old_last_async_pid = last_asynchronous_pid; + + already_forked = 0; + + /* If we're in a pipeline or run in the background, set DOFORK so we + make the child early, before word expansion. This keeps assignment + statements from affecting the parent shell's environment when they + should not. */ + dofork = pipe_in != NO_PIPE || pipe_out != NO_PIPE || async; + + /* Something like `%2 &' should restart job 2 in the background, not cause + the shell to fork here. */ + if (dofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE && + simple_command->words && simple_command->words->word && + simple_command->words->word->word && + (simple_command->words->word->word[0] == '%')) + dofork = 0; + + if (dofork) + { + char *p; + + /* Do this now, because execute_disk_command will do it anyway in the + vast majority of cases. */ + maybe_make_export_env (); + + /* Don't let a DEBUG trap overwrite the command string to be saved with + the process/job associated with this child. */ + fork_flags = async ? FORK_ASYNC : 0; + if (make_child (p = savestring (the_printed_command_except_trap), fork_flags) == 0) + { + already_forked = 1; + cmdflags |= CMD_NO_FORK; + + /* We redo some of what make_child() does with SUBSHELL_IGNTRAP */ + subshell_environment = SUBSHELL_FORK|SUBSHELL_IGNTRAP; /* XXX */ + if (pipe_in != NO_PIPE || pipe_out != NO_PIPE) + subshell_environment |= SUBSHELL_PIPE; + if (async) + subshell_environment |= SUBSHELL_ASYNC; + + /* We need to do this before piping to handle some really + pathological cases where one of the pipe file descriptors + is < 2. */ + if (fds_to_close) + close_fd_bitmap (fds_to_close); + + /* If we fork because of an input pipe, note input pipe for later to + inhibit async commands from redirecting stdin from /dev/null */ + stdin_redir |= pipe_in != NO_PIPE; + + do_piping (pipe_in, pipe_out); + pipe_in = pipe_out = NO_PIPE; +#if defined (COPROCESS_SUPPORT) + coproc_closeall (); +#endif + + last_asynchronous_pid = old_last_async_pid; + + if (async) + subshell_level++; /* not for pipes yet */ + +#if defined (JOB_CONTROL) + FREE (p); /* child doesn't use pointer */ +#endif + } + else + { + /* Don't let simple commands that aren't the last command in a + pipeline change $? for the rest of the pipeline (or at all). */ + if (pipe_out != NO_PIPE) + result = last_command_exit_value; + close_pipes (pipe_in, pipe_out); + command_line = (char *)NULL; /* don't free this. */ + return (result); + } + } + + QUIT; /* XXX */ + + /* If we are re-running this as the result of executing the `command' + builtin, do not expand the command words a second time. */ + if ((cmdflags & CMD_INHIBIT_EXPANSION) == 0) + { + current_fds_to_close = fds_to_close; + fix_assignment_words (simple_command->words); +#if defined (ARRAY_VARS) + fix_arrayref_words (simple_command->words); +#endif + /* Pass the ignore return flag down to command substitutions */ + if (cmdflags & CMD_IGNORE_RETURN) /* XXX */ + comsub_ignore_return++; + words = expand_words (simple_command->words); + if (cmdflags & CMD_IGNORE_RETURN) + comsub_ignore_return--; + current_fds_to_close = (struct fd_bitmap *)NULL; + } + else + words = copy_word_list (simple_command->words); + + /* It is possible for WORDS not to have anything left in it. + Perhaps all the words consisted of `$foo', and there was + no variable `$foo'. */ + if (words == 0) + { + this_command_name = 0; + result = execute_null_command (simple_command->redirects, + pipe_in, pipe_out, + already_forked ? 0 : async); + if (already_forked) + sh_exit (result); + else + { + bind_lastarg ((char *)NULL); + set_pipestatus_from_exit (result); + return (result); + } + } + + lastarg = (char *)NULL; + + begin_unwind_frame ("simple-command"); + + if (echo_command_at_execute && (cmdflags & CMD_COMMAND_BUILTIN) == 0) + xtrace_print_word_list (words, 1); + + builtin = (sh_builtin_func_t *)NULL; + func = (SHELL_VAR *)NULL; + + /* This test is still here in case we want to change the command builtin + handler code below to recursively call execute_simple_command (after + modifying the simple_command struct). */ + if ((cmdflags & CMD_NO_FUNCTIONS) == 0) + { + /* Posix.2 says special builtins are found before functions. We + don't set builtin_is_special anywhere other than here, because + this path is followed only when the `command' builtin is *not* + being used, and we don't want to exit the shell if a special + builtin executed with `command builtin' fails. `command' is not + a special builtin. */ + if (posixly_correct) + { + builtin = find_special_builtin (words->word->word); + if (builtin) + builtin_is_special = 1; + } + if (builtin == 0) + func = find_function (words->word->word); + } + + /* What happens in posix mode when an assignment preceding a command name + fails. This should agree with the code in execute_cmd.c: + do_assignment_statements(), even though I don't think it's executed any + more. */ + if (posixly_correct && tempenv_assign_error) + { +#if defined (DEBUG) + /* I don't know if this clause is ever executed, so let's check */ +itrace("execute_simple_command: posix mode tempenv assignment error"); +#endif + last_command_exit_value = EXECUTION_FAILURE; +#if defined (STRICT_POSIX) + jump_to_top_level ((interactive_shell == 0) ? FORCE_EOF : DISCARD); +#else + if (interactive_shell == 0 && builtin_is_special) + jump_to_top_level (FORCE_EOF); + else if (interactive_shell == 0) + jump_to_top_level (DISCARD); /* XXX - maybe change later */ + else + jump_to_top_level (DISCARD); +#endif + } + tempenv_assign_error = 0; /* don't care about this any more */ + + /* This is where we handle the command builtin as a pseudo-reserved word + prefix. This allows us to optimize away forks if we can. */ + old_command_builtin = -1; + if (builtin == 0 && func == 0) + { + WORD_LIST *disposer, *l; + int cmdtype; + + builtin = find_shell_builtin (words->word->word); + while (builtin == command_builtin) + { + disposer = words; + cmdtype = 0; + words = check_command_builtin (words, &cmdtype); + if (cmdtype > 0) /* command -p [--] words */ + { + for (l = disposer; l->next != words; l = l->next) + ; + l->next = 0; + dispose_words (disposer); + cmdflags |= CMD_COMMAND_BUILTIN | CMD_NO_FUNCTIONS; + if (cmdtype == 2) + cmdflags |= CMD_STDPATH; + builtin = find_shell_builtin (words->word->word); + } + else + break; + } + if (cmdflags & CMD_COMMAND_BUILTIN) + { + old_command_builtin = executing_command_builtin; + unwind_protect_int (executing_command_builtin); + executing_command_builtin |= 1; + } + builtin = 0; + } + + add_unwind_protect (dispose_words, words); + QUIT; + + /* Bind the last word in this command to "$_" after execution. */ + for (lastword = words; lastword->next; lastword = lastword->next) + ; + lastarg = lastword->word->word; + +#if defined (JOB_CONTROL) + /* Is this command a job control related thing? */ + if (words->word->word[0] == '%' && already_forked == 0) + { + this_command_name = async ? "bg" : "fg"; + last_shell_builtin = this_shell_builtin; + this_shell_builtin = builtin_address (this_command_name); + result = (*this_shell_builtin) (words); + goto return_result; + } + + /* One other possibililty. The user may want to resume an existing job. + If they do, find out whether this word is a candidate for a running + job. */ + if (job_control && already_forked == 0 && async == 0 && + !first_word_quoted && + !words->next && + words->word->word[0] && + !simple_command->redirects && + pipe_in == NO_PIPE && + pipe_out == NO_PIPE && + (temp = get_string_value ("auto_resume"))) + { + int job, jflags, started_status; + + jflags = JM_STOPPED|JM_FIRSTMATCH; + if (STREQ (temp, "exact")) + jflags |= JM_EXACT; + else if (STREQ (temp, "substring")) + jflags |= JM_SUBSTRING; + else + jflags |= JM_PREFIX; + job = get_job_by_name (words->word->word, jflags); + if (job != NO_JOB) + { + run_unwind_frame ("simple-command"); + this_command_name = "fg"; + last_shell_builtin = this_shell_builtin; + this_shell_builtin = builtin_address ("fg"); + + started_status = start_job (job, 1); + return ((started_status < 0) ? EXECUTION_FAILURE : started_status); + } + } +#endif /* JOB_CONTROL */ + +run_builtin: + /* Remember the name of this command globally. */ + this_command_name = words->word->word; + + QUIT; + + /* This command could be a shell builtin or a user-defined function. + We have already found special builtins by this time, so we do not + set builtin_is_special. If this is a function or builtin, and we + have pipes, then fork a subshell in here. Otherwise, just execute + the command directly. */ + if (func == 0 && builtin == 0) + builtin = find_shell_builtin (this_command_name); + + last_shell_builtin = this_shell_builtin; + this_shell_builtin = builtin; + + if (builtin || func) + { + if (builtin) + { + old_builtin = executing_builtin; + unwind_protect_int (executing_builtin); /* modified in execute_builtin */ + if (old_command_builtin == -1) /* sentinel, can be set above */ + { + old_command_builtin = executing_command_builtin; + unwind_protect_int (executing_command_builtin); /* ditto and set above */ + } + } + if (already_forked) + { + /* reset_terminating_signals (); */ /* XXX */ + /* Reset the signal handlers in the child, but don't free the + trap strings. Set a flag noting that we have to free the + trap strings if we run trap to change a signal disposition. */ + reset_signal_handlers (); + subshell_environment |= SUBSHELL_RESETTRAP; + subshell_environment &= ~SUBSHELL_IGNTRAP; + + if (async) + { + if ((cmdflags & CMD_STDIN_REDIR) && + pipe_in == NO_PIPE && + (stdin_redirects (simple_command->redirects) == 0)) + async_redirect_stdin (); + setup_async_signals (); + } + + if (async == 0) + subshell_level++; + execute_subshell_builtin_or_function + (words, simple_command->redirects, builtin, func, + pipe_in, pipe_out, async, fds_to_close, + cmdflags); + subshell_level--; + } + else + { + result = execute_builtin_or_function + (words, builtin, func, simple_command->redirects, fds_to_close, + cmdflags); + if (builtin) + { + if (result > EX_SHERRBASE) + { + switch (result) + { + case EX_REDIRFAIL: + case EX_BADASSIGN: + case EX_EXPFAIL: + /* These errors cause non-interactive posix mode shells to exit */ + if (posixly_correct && builtin_is_special && interactive_shell == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + jump_to_top_level (ERREXIT); + } + break; + case EX_DISKFALLBACK: + /* XXX - experimental */ + executing_builtin = old_builtin; + executing_command_builtin = old_command_builtin; + builtin = 0; + + /* The redirections have already been `undone', so this + will have to do them again. But piping is forever. */ + pipe_in = pipe_out = -1; + goto execute_from_filesystem; + } + result = builtin_status (result); + if (builtin_is_special) + special_builtin_failed = 1; /* XXX - take command builtin into account? */ + } + /* In POSIX mode, if there are assignment statements preceding + a special builtin, they persist after the builtin + completes. */ + if (posixly_correct && builtin_is_special && temporary_env) + merge_temporary_env (); + } + else /* function */ + { + if (result == EX_USAGE) + result = EX_BADUSAGE; + else if (result > EX_SHERRBASE) + result = builtin_status (result); + } + + set_pipestatus_from_exit (result); + + goto return_result; + } + } + + if (autocd && interactive && words->word && is_dirname (words->word->word)) + { + words = make_word_list (make_word ("--"), words); + words = make_word_list (make_word ("cd"), words); + xtrace_print_word_list (words, 0); + func = find_function ("cd"); + goto run_builtin; + } + +execute_from_filesystem: + if (command_line == 0) + command_line = savestring (the_printed_command_except_trap ? the_printed_command_except_trap : ""); + +#if defined (PROCESS_SUBSTITUTION) + /* The old code did not test already_forked and only did this if + subshell_environment&SUBSHELL_COMSUB != 0 (comsubs and procsubs). Other + uses of the no-fork optimization left FIFOs in $TMPDIR */ + if (already_forked == 0 && (cmdflags & CMD_NO_FORK) && fifos_pending() > 0) + cmdflags &= ~CMD_NO_FORK; +#endif + result = execute_disk_command (words, simple_command->redirects, command_line, + pipe_in, pipe_out, async, fds_to_close, + cmdflags); + + return_result: + bind_lastarg (lastarg); + FREE (command_line); + dispose_words (words); + if (builtin) + { + executing_builtin = old_builtin; + executing_command_builtin = old_command_builtin; + } + discard_unwind_frame ("simple-command"); + this_command_name = (char *)NULL; /* points to freed memory now */ + return (result); +} + +/* Translate the special builtin exit statuses. We don't really need a + function for this; it's a placeholder for future work. */ +static int +builtin_status (result) + int result; +{ + int r; + + switch (result) + { + case EX_USAGE: + case EX_BADSYNTAX: + r = EX_BADUSAGE; + break; + case EX_REDIRFAIL: + case EX_BADASSIGN: + case EX_EXPFAIL: + r = EXECUTION_FAILURE; + break; + default: + /* other special exit statuses not yet defined */ + r = (result > EX_SHERRBASE) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; + break; + } + return (r); +} + +static int +execute_builtin (builtin, words, flags, subshell) + sh_builtin_func_t *builtin; + WORD_LIST *words; + int flags, subshell; +{ + int result, eval_unwind, ignexit_flag; + int isbltinenv, should_keep; + char *error_trap; + + error_trap = 0; + should_keep = 0; + + /* The eval builtin calls parse_and_execute, which does not know about + the setting of flags, and always calls the execution functions with + flags that will exit the shell on an error if -e is set. If the + eval builtin is being called, and we're supposed to ignore the exit + value of the command, we turn the -e flag off ourselves and disable + the ERR trap, then restore them when the command completes. This is + also a problem (as below) for the command and source/. builtins. */ + if (subshell == 0 && (flags & CMD_IGNORE_RETURN) && + (builtin == eval_builtin || (flags & CMD_COMMAND_BUILTIN) || builtin == source_builtin)) + { + begin_unwind_frame ("eval_builtin"); + unwind_protect_int (exit_immediately_on_error); + unwind_protect_int (builtin_ignoring_errexit); + error_trap = TRAP_STRING (ERROR_TRAP); + if (error_trap) + { + error_trap = savestring (error_trap); + add_unwind_protect (xfree, error_trap); + add_unwind_protect (set_error_trap, error_trap); + restore_default_signal (ERROR_TRAP); + } + exit_immediately_on_error = 0; + ignexit_flag = builtin_ignoring_errexit; + builtin_ignoring_errexit = 1; + eval_unwind = 1; + } + else + eval_unwind = 0; + + /* The temporary environment for a builtin is supposed to apply to + all commands executed by that builtin. Currently, this is a + problem only with the `unset', `source' and `eval' builtins. + `mapfile' is a special case because it uses evalstring (same as + eval or source) to run its callbacks. */ + /* SHOULD_KEEP is for the pop_scope call below; it only matters when + posixly_correct is set, but we should propagate the temporary environment + to the enclosing environment only for special builtins. */ + isbltinenv = (builtin == source_builtin || builtin == eval_builtin || builtin == unset_builtin || builtin == mapfile_builtin); + should_keep = isbltinenv && builtin != mapfile_builtin; +#if defined (HISTORY) && defined (READLINE) + if (builtin == fc_builtin || builtin == read_builtin) + { + isbltinenv = 1; + should_keep = 0; + } +#endif + + if (isbltinenv) + { + if (subshell == 0) + begin_unwind_frame ("builtin_env"); + + if (temporary_env) + { + push_scope (VC_BLTNENV, temporary_env); + if (flags & CMD_COMMAND_BUILTIN) + should_keep = 0; + if (subshell == 0) + add_unwind_protect (pop_scope, should_keep ? "1" : 0); + temporary_env = (HASH_TABLE *)NULL; + } + } + + if (subshell == 0 && builtin == eval_builtin) + { + if (evalnest_max > 0 && evalnest >= evalnest_max) + { + internal_error (_("eval: maximum eval nesting level exceeded (%d)"), evalnest); + evalnest = 0; + jump_to_top_level (DISCARD); + } + unwind_protect_int (evalnest); + /* The test for subshell == 0 above doesn't make a difference */ + evalnest++; /* execute_subshell_builtin_or_function sets this to 0 */ + } + else if (subshell == 0 && builtin == source_builtin) + { + if (sourcenest_max > 0 && sourcenest >= sourcenest_max) + { + internal_error (_("%s: maximum source nesting level exceeded (%d)"), this_command_name, sourcenest); + sourcenest = 0; + jump_to_top_level (DISCARD); + } + unwind_protect_int (sourcenest); + /* The test for subshell == 0 above doesn't make a difference */ + sourcenest++; /* execute_subshell_builtin_or_function sets this to 0 */ + } + + /* `return' does a longjmp() back to a saved environment in execute_function. + If a variable assignment list preceded the command, and the shell is + running in POSIX mode, we need to merge that into the shell_variables + table, since `return' is a POSIX special builtin. We don't do this if + it's being run by the `command' builtin, since that's supposed to inhibit + the special builtin properties. */ + if (posixly_correct && subshell == 0 && builtin == return_builtin && (flags & CMD_COMMAND_BUILTIN) == 0 && temporary_env) + { + begin_unwind_frame ("return_temp_env"); + add_unwind_protect (merge_temporary_env, (char *)NULL); + } + + executing_builtin++; + executing_command_builtin |= builtin == command_builtin; + result = ((*builtin) (words->next)); + + /* This shouldn't happen, but in case `return' comes back instead of + longjmp'ing, we need to unwind. */ + if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env) + discard_unwind_frame ("return_temp_env"); + + if (subshell == 0 && isbltinenv) + run_unwind_frame ("builtin_env"); + + if (eval_unwind) + { + builtin_ignoring_errexit = ignexit_flag; + exit_immediately_on_error = builtin_ignoring_errexit ? 0 : errexit_flag; + if (error_trap) + { + set_error_trap (error_trap); + free (error_trap); + } + discard_unwind_frame ("eval_builtin"); + } + + return (result); +} + +static void +maybe_restore_getopt_state (gs) + sh_getopt_state_t *gs; +{ + /* If we have a local copy of OPTIND and it's at the right (current) + context, then we restore getopt's internal state. If not, we just + let it go. We know there is a local OPTIND if gs->gs_flags & 1. + This is set below in execute_function() before the context is run. */ + if (gs->gs_flags & 1) + sh_getopt_restore_istate (gs); + else + free (gs); +} + +#if defined (ARRAY_VARS) +void +restore_funcarray_state (fa) + struct func_array_state *fa; +{ + SHELL_VAR *nfv; + ARRAY *funcname_a; + + array_pop (fa->source_a); + array_pop (fa->lineno_a); + + GET_ARRAY_FROM_VAR ("FUNCNAME", nfv, funcname_a); + if (nfv == fa->funcname_v) + array_pop (funcname_a); + + free (fa); +} +#endif + +static int +execute_function (var, words, flags, fds_to_close, async, subshell) + SHELL_VAR *var; + WORD_LIST *words; + int flags; + struct fd_bitmap *fds_to_close; + int async, subshell; +{ + int return_val, result, lineno; + COMMAND *tc, *fc, *save_current; + char *debug_trap, *error_trap, *return_trap; +#if defined (ARRAY_VARS) + SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v; + ARRAY *funcname_a; + volatile ARRAY *bash_source_a; + volatile ARRAY *bash_lineno_a; + struct func_array_state *fa; +#endif + FUNCTION_DEF *shell_fn; + char *sfile, *t; + sh_getopt_state_t *gs; + SHELL_VAR *gv; + + USE_VAR(fc); + + if (funcnest_max > 0 && funcnest >= funcnest_max) + { + internal_error (_("%s: maximum function nesting level exceeded (%d)"), var->name, funcnest); + funcnest = 0; /* XXX - should we reset it somewhere else? */ + jump_to_top_level (DISCARD); + } + +#if defined (ARRAY_VARS) + GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a); + GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a); + GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a); +#endif + + tc = (COMMAND *)copy_command (function_cell (var)); + if (tc && (flags & CMD_IGNORE_RETURN)) + tc->flags |= CMD_IGNORE_RETURN; + + /* A limited attempt at optimization: shell functions at the end of command + substitutions that are already marked NO_FORK. */ + if (tc && (flags & CMD_NO_FORK) && (subshell_environment & SUBSHELL_COMSUB)) + optimize_shell_function (tc); + + gs = sh_getopt_save_istate (); + if (subshell == 0) + { + begin_unwind_frame ("function_calling"); + /* If the shell is in posix mode, this will push the variables in + the temporary environment to the "current shell environment" (the + global scope), and dispose the temporary env before setting it to + NULL later. This behavior has disappeared from the latest edition + of the standard, so I will eventually remove it from variables.c: + push_var_context. */ + push_context (var->name, subshell, temporary_env); + /* This has to be before the pop_context(), because the unwinding of + local variables may cause the restore of a local declaration of + OPTIND to force a getopts state reset. */ + add_unwind_protect (maybe_restore_getopt_state, gs); + add_unwind_protect (pop_context, (char *)NULL); + unwind_protect_int (line_number); + unwind_protect_int (line_number_for_err_trap); + unwind_protect_int (function_line_number); + unwind_protect_int (return_catch_flag); + unwind_protect_jmp_buf (return_catch); + add_unwind_protect (dispose_command, (char *)tc); + unwind_protect_pointer (this_shell_function); + unwind_protect_int (funcnest); + unwind_protect_int (loop_level); + } + else + push_context (var->name, subshell, temporary_env); /* don't unwind-protect for subshells */ + + temporary_env = (HASH_TABLE *)NULL; + + this_shell_function = var; + make_funcname_visible (1); + + debug_trap = TRAP_STRING(DEBUG_TRAP); + error_trap = TRAP_STRING(ERROR_TRAP); + return_trap = TRAP_STRING(RETURN_TRAP); + + /* The order of the unwind protects for debug_trap, error_trap and + return_trap is important here! unwind-protect commands are run + in reverse order of registration. If this causes problems, take + out the xfree unwind-protect calls and live with the small memory leak. */ + + /* function_trace_mode != 0 means that all functions inherit the DEBUG trap. + if the function has the trace attribute set, it inherits the DEBUG trap */ + if (debug_trap && ((trace_p (var) == 0) && function_trace_mode == 0)) + { + if (subshell == 0) + { + debug_trap = savestring (debug_trap); + add_unwind_protect (xfree, debug_trap); + add_unwind_protect (maybe_set_debug_trap, debug_trap); + } + restore_default_signal (DEBUG_TRAP); + } + + /* error_trace_mode != 0 means that functions inherit the ERR trap. */ + if (error_trap && error_trace_mode == 0) + { + if (subshell == 0) + { + error_trap = savestring (error_trap); + add_unwind_protect (xfree, error_trap); + add_unwind_protect (maybe_set_error_trap, error_trap); + } + restore_default_signal (ERROR_TRAP); + } + + /* Shell functions inherit the RETURN trap if function tracing is on + globally or on individually for this function. */ + if (return_trap && (signal_in_progress (DEBUG_TRAP) || ((trace_p (var) == 0) && function_trace_mode == 0))) + { + if (subshell == 0) + { + return_trap = savestring (return_trap); + add_unwind_protect (xfree, return_trap); + add_unwind_protect (maybe_set_return_trap, return_trap); + } + restore_default_signal (RETURN_TRAP); + } + + funcnest++; +#if defined (ARRAY_VARS) + /* This is quite similar to the code in shell.c and elsewhere. */ + shell_fn = find_function_def (this_shell_function->name); + sfile = shell_fn ? shell_fn->source_file : ""; + array_push ((ARRAY *)funcname_a, this_shell_function->name); + + array_push ((ARRAY *)bash_source_a, sfile); + lineno = GET_LINE_NUMBER (); + t = itos (lineno); + array_push ((ARRAY *)bash_lineno_a, t); + free (t); +#endif + +#if defined (ARRAY_VARS) + fa = (struct func_array_state *)xmalloc (sizeof (struct func_array_state)); + fa->source_a = (ARRAY *)bash_source_a; + fa->source_v = bash_source_v; + fa->lineno_a = (ARRAY *)bash_lineno_a; + fa->lineno_v = bash_lineno_v; + fa->funcname_a = (ARRAY *)funcname_a; + fa->funcname_v = funcname_v; + if (subshell == 0) + add_unwind_protect (restore_funcarray_state, fa); +#endif + + /* The temporary environment for a function is supposed to apply to + all commands executed within the function body. */ + + /* Initialize BASH_ARGC and BASH_ARGV before we blow away the positional + parameters */ + if (debugging_mode || shell_compatibility_level <= 44) + init_bash_argv (); + + remember_args (words->next, 1); + + /* Update BASH_ARGV and BASH_ARGC */ + if (debugging_mode) + { + push_args (words->next); + if (subshell == 0) + add_unwind_protect (pop_args, 0); + } + + /* Number of the line on which the function body starts. */ + line_number = function_line_number = tc->line; + +#if defined (JOB_CONTROL) + if (subshell) + stop_pipeline (async, (COMMAND *)NULL); +#endif + + if (shell_compatibility_level > 43) + loop_level = 0; + + fc = tc; + + from_return_trap = 0; + + return_catch_flag++; + return_val = setjmp_nosigs (return_catch); + + if (return_val) + { + result = return_catch_value; + /* Run the RETURN trap in the function's context. */ + save_current = currently_executing_command; + if (from_return_trap == 0) + run_return_trap (); + currently_executing_command = save_current; + } + else + { + /* Run the debug trap here so we can trap at the start of a function's + execution rather than the execution of the body's first command. */ + showing_function_line = 1; + save_current = currently_executing_command; + result = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode == 0 || result == EXECUTION_SUCCESS) + { + showing_function_line = 0; + currently_executing_command = save_current; + result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close); + + /* Run the RETURN trap in the function's context */ + save_current = currently_executing_command; + run_return_trap (); + currently_executing_command = save_current; + } +#else + result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close); + + save_current = currently_executing_command; + run_return_trap (); + currently_executing_command = save_current; +#endif + showing_function_line = 0; + } + + /* If we have a local copy of OPTIND, note it in the saved getopts state. */ + gv = find_variable ("OPTIND"); + if (gv && gv->context == variable_context) + gs->gs_flags |= 1; + + if (subshell == 0) + run_unwind_frame ("function_calling"); +#if defined (ARRAY_VARS) + else + { + restore_funcarray_state (fa); + /* Restore BASH_ARGC and BASH_ARGV */ + if (debugging_mode) + pop_args (); + } +#endif + + if (variable_context == 0 || this_shell_function == 0) + { + make_funcname_visible (0); +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif + } + + return (result); +} + +/* A convenience routine for use by other parts of the shell to execute + a particular shell function. */ +int +execute_shell_function (var, words) + SHELL_VAR *var; + WORD_LIST *words; +{ + int ret; + struct fd_bitmap *bitmap; + + bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE); + begin_unwind_frame ("execute-shell-function"); + add_unwind_protect (dispose_fd_bitmap, (char *)bitmap); + + ret = execute_function (var, words, 0, bitmap, 0, 0); + + dispose_fd_bitmap (bitmap); + discard_unwind_frame ("execute-shell-function"); + + return ret; +} + +/* Execute a shell builtin or function in a subshell environment. This + routine does not return; it only calls exit(). If BUILTIN is non-null, + it points to a function to call to execute a shell builtin; otherwise + VAR points at the body of a function to execute. WORDS is the arguments + to the command, REDIRECTS specifies redirections to perform before the + command is executed. */ +static void +execute_subshell_builtin_or_function (words, redirects, builtin, var, + pipe_in, pipe_out, async, fds_to_close, + flags) + WORD_LIST *words; + REDIRECT *redirects; + sh_builtin_func_t *builtin; + SHELL_VAR *var; + int pipe_in, pipe_out, async; + struct fd_bitmap *fds_to_close; + int flags; +{ + int result, r, funcvalue; +#if defined (JOB_CONTROL) + int jobs_hack; + + jobs_hack = (builtin == jobs_builtin) && + ((subshell_environment & SUBSHELL_ASYNC) == 0 || pipe_out != NO_PIPE); +#endif + + /* A subshell is neither a login shell nor interactive. */ + login_shell = interactive = 0; + if (builtin == eval_builtin) + evalnest = 0; + else if (builtin == source_builtin) + sourcenest = 0; + + if (async) + subshell_environment |= SUBSHELL_ASYNC; + if (pipe_in != NO_PIPE || pipe_out != NO_PIPE) + subshell_environment |= SUBSHELL_PIPE; + + maybe_make_export_env (); /* XXX - is this needed? */ + +#if defined (JOB_CONTROL) + /* Eradicate all traces of job control after we fork the subshell, so + all jobs begun by this subshell are in the same process group as + the shell itself. */ + + /* Allow the output of `jobs' to be piped. */ + if (jobs_hack) + kill_current_pipeline (); + else + without_job_control (); + + set_sigchld_handler (); +#else + without_job_control (); +#endif /* JOB_CONTROL */ + + set_sigint_handler (); + + if (fds_to_close) + close_fd_bitmap (fds_to_close); + + do_piping (pipe_in, pipe_out); + + if (do_redirections (redirects, RX_ACTIVE) != 0) + exit (EXECUTION_FAILURE); + + if (builtin) + { + /* Give builtins a place to jump back to on failure, + so we don't go back up to main(). */ + result = setjmp_nosigs (top_level); + + /* Give the return builtin a place to jump to when executed in a subshell + or pipeline */ + funcvalue = 0; + if (return_catch_flag && builtin == return_builtin) + funcvalue = setjmp_nosigs (return_catch); + + if (result == EXITPROG || result == EXITBLTIN) + subshell_exit (last_command_exit_value); + else if (result) + subshell_exit (EXECUTION_FAILURE); + else if (funcvalue) + subshell_exit (return_catch_value); + else + { + r = execute_builtin (builtin, words, flags, 1); + fflush (stdout); + if (r == EX_USAGE) + r = EX_BADUSAGE; + /* XXX - experimental */ + else if (r == EX_DISKFALLBACK) + { + char *command_line; + + command_line = savestring (the_printed_command_except_trap ? the_printed_command_except_trap : ""); + r = execute_disk_command (words, (REDIRECT *)0, command_line, + -1, -1, async, (struct fd_bitmap *)0, flags|CMD_NO_FORK); + } + subshell_exit (r); + } + } + else + { + r = execute_function (var, words, flags, fds_to_close, async, 1); + fflush (stdout); + subshell_exit (r); + } +} + +/* Execute a builtin or function in the current shell context. If BUILTIN + is non-null, it is the builtin command to execute, otherwise VAR points + to the body of a function. WORDS are the command's arguments, REDIRECTS + are the redirections to perform. FDS_TO_CLOSE is the usual bitmap of + file descriptors to close. + + If BUILTIN is exec_builtin, the redirections specified in REDIRECTS are + not undone before this function returns. */ +static int +execute_builtin_or_function (words, builtin, var, redirects, + fds_to_close, flags) + WORD_LIST *words; + sh_builtin_func_t *builtin; + SHELL_VAR *var; + REDIRECT *redirects; + struct fd_bitmap *fds_to_close; + int flags; +{ + int result; + REDIRECT *saved_undo_list; +#if defined (PROCESS_SUBSTITUTION) + int ofifo, nfifo, osize; + void *ofifo_list; +#endif + +#if defined (PROCESS_SUBSTITUTION) + begin_unwind_frame ("saved_fifos"); + /* If we return, we longjmp and don't get a chance to restore the old + fifo list, so we add an unwind protect to free it */ + ofifo = num_fifos (); + ofifo_list = copy_fifo_list (&osize); + if (ofifo_list) + add_unwind_protect (xfree, ofifo_list); +#endif + + if (do_redirections (redirects, RX_ACTIVE|RX_UNDOABLE) != 0) + { + undo_partial_redirects (); + dispose_exec_redirects (); +#if defined (PROCESS_SUBSTITUTION) + free (ofifo_list); +#endif + return (EX_REDIRFAIL); /* was EXECUTION_FAILURE */ + } + + saved_undo_list = redirection_undo_list; + + /* Calling the "exec" builtin changes redirections forever. */ + if (builtin == exec_builtin) + { + dispose_redirects (saved_undo_list); + saved_undo_list = exec_redirection_undo_list; + exec_redirection_undo_list = (REDIRECT *)NULL; + } + else + dispose_exec_redirects (); + + if (saved_undo_list) + { + begin_unwind_frame ("saved-redirects"); + add_unwind_protect (cleanup_redirects, (char *)saved_undo_list); + } + + redirection_undo_list = (REDIRECT *)NULL; + + if (builtin) + result = execute_builtin (builtin, words, flags, 0); + else + result = execute_function (var, words, flags, fds_to_close, 0, 0); + + /* We do this before undoing the effects of any redirections. */ + fflush (stdout); + fpurge (stdout); + if (ferror (stdout)) + clearerr (stdout); + + /* If we are executing the `command' builtin, but this_shell_builtin is + set to `exec_builtin', we know that we have something like + `command exec [redirection]', since otherwise `exec' would have + overwritten the shell and we wouldn't get here. In this case, we + want to behave as if the `command' builtin had not been specified + and preserve the redirections. */ + if (builtin == command_builtin && this_shell_builtin == exec_builtin) + { + int discard; + + discard = 0; + if (saved_undo_list) + { + dispose_redirects (saved_undo_list); + discard = 1; + } + redirection_undo_list = exec_redirection_undo_list; + saved_undo_list = exec_redirection_undo_list = (REDIRECT *)NULL; + if (discard) + discard_unwind_frame ("saved-redirects"); + } + + if (saved_undo_list) + { + redirection_undo_list = saved_undo_list; + discard_unwind_frame ("saved-redirects"); + } + + undo_partial_redirects (); + +#if defined (PROCESS_SUBSTITUTION) + /* Close any FIFOs created by this builtin or function. */ + nfifo = num_fifos (); + if (nfifo > ofifo) + close_new_fifos (ofifo_list, osize); + if (ofifo_list) + free (ofifo_list); + discard_unwind_frame ("saved_fifos"); +#endif + + return (result); +} + +void +setup_async_signals () +{ +#if defined (__BEOS__) + set_signal_handler (SIGHUP, SIG_IGN); /* they want csh-like behavior */ +#endif + +#if defined (JOB_CONTROL) + if (job_control == 0) +#endif + { + /* Make sure we get the original signal dispositions now so we don't + confuse the trap builtin later if the subshell tries to use it to + reset SIGINT/SIGQUIT. Don't call set_signal_ignored; that sets + the value of original_signals to SIG_IGN. Posix interpretation 751. */ + get_original_signal (SIGINT); + set_signal_handler (SIGINT, SIG_IGN); + + get_original_signal (SIGQUIT); + set_signal_handler (SIGQUIT, SIG_IGN); + } +} + +/* Execute a simple command that is hopefully defined in a disk file + somewhere. + + 1) fork () + 2) connect pipes + 3) look up the command + 4) do redirections + 5) execve () + 6) If the execve failed, see if the file has executable mode set. + If so, and it isn't a directory, then execute its contents as + a shell script. + + Note that the filename hashing stuff has to take place up here, + in the parent. This is probably why the Bourne style shells + don't handle it, since that would require them to go through + this gnarly hair, for no good reason. + + NOTE: callers expect this to fork or exit(). */ + +/* Name of a shell function to call when a command name is not found. */ +#ifndef NOTFOUND_HOOK +# define NOTFOUND_HOOK "command_not_found_handle" +#endif + +static int +execute_disk_command (words, redirects, command_line, pipe_in, pipe_out, + async, fds_to_close, cmdflags) + WORD_LIST *words; + REDIRECT *redirects; + char *command_line; + int pipe_in, pipe_out, async; + struct fd_bitmap *fds_to_close; + int cmdflags; +{ + char *pathname, *command, **args, *p; + int nofork, stdpath, result, fork_flags; + pid_t pid; + SHELL_VAR *hookf; + WORD_LIST *wl; + + stdpath = (cmdflags & CMD_STDPATH); /* use command -p path */ + nofork = (cmdflags & CMD_NO_FORK); /* Don't fork, just exec, if no pipes */ + pathname = words->word->word; + + p = 0; + result = EXECUTION_SUCCESS; +#if defined (RESTRICTED_SHELL) + command = (char *)NULL; + if (restricted && mbschr (pathname, '/')) + { + internal_error (_("%s: restricted: cannot specify `/' in command names"), + pathname); + result = last_command_exit_value = EXECUTION_FAILURE; + + /* If we're not going to fork below, we must already be in a child + process or a context in which it's safe to call exit(2). */ + if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE) + exit (last_command_exit_value); + else + goto parent_return; + } +#endif /* RESTRICTED_SHELL */ + + /* If we want to change this so `command -p' (CMD_STDPATH) does not insert + any pathname it finds into the hash table, it should read + command = search_for_command (pathname, stdpath ? CMDSRCH_STDPATH : CMDSRCH_HASH); + */ + command = search_for_command (pathname, CMDSRCH_HASH|(stdpath ? CMDSRCH_STDPATH : 0)); + QUIT; + + if (command) + { + /* If we're optimizing out the fork (implicit `exec'), decrement the + shell level like `exec' would do. Don't do this if we are already + in a pipeline environment, assuming it's already been done. */ + if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE && (subshell_environment & SUBSHELL_PIPE) == 0) + adjust_shell_level (-1); + + maybe_make_export_env (); + put_command_name_into_env (command); + } + + /* We have to make the child before we check for the non-existence + of COMMAND, since we want the error messages to be redirected. */ + /* If we can get away without forking and there are no pipes to deal with, + don't bother to fork, just directly exec the command. */ + if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE) + pid = 0; + else + { + fork_flags = async ? FORK_ASYNC : 0; + pid = make_child (p = savestring (command_line), fork_flags); + } + + if (pid == 0) + { + int old_interactive; + + reset_terminating_signals (); /* XXX */ + /* Cancel traps, in trap.c. */ + restore_original_signals (); + subshell_environment &= ~SUBSHELL_IGNTRAP; + +#if defined (JOB_CONTROL) + FREE (p); +#endif + + /* restore_original_signals may have undone the work done + by make_child to ensure that SIGINT and SIGQUIT are ignored + in asynchronous children. */ + if (async) + { + if ((cmdflags & CMD_STDIN_REDIR) && + pipe_in == NO_PIPE && + (stdin_redirects (redirects) == 0)) + async_redirect_stdin (); + setup_async_signals (); + } + + /* This functionality is now provided by close-on-exec of the + file descriptors manipulated by redirection and piping. + Some file descriptors still need to be closed in all children + because of the way bash does pipes; fds_to_close is a + bitmap of all such file descriptors. */ + if (fds_to_close) + close_fd_bitmap (fds_to_close); + + do_piping (pipe_in, pipe_out); + + old_interactive = interactive; + if (async) + interactive = 0; + + subshell_environment |= SUBSHELL_FORK; /* XXX - was just = */ + +#if defined (PROCESS_SUBSTITUTION) && !defined (HAVE_DEV_FD) + clear_fifo_list (); /* XXX - we haven't created any FIFOs */ +#endif + + /* reset shell_pgrp to pipeline_pgrp here for word expansions performed + by the redirections here? */ + if (redirects && (do_redirections (redirects, RX_ACTIVE) != 0)) + { +#if defined (PROCESS_SUBSTITUTION) + /* Try to remove named pipes that may have been created as the + result of redirections. */ + unlink_all_fifos (); +#endif /* PROCESS_SUBSTITUTION */ + exit (EXECUTION_FAILURE); + } + +#if defined (PROCESS_SUBSTITUTION) && !defined (HAVE_DEV_FD) + /* This should only contain FIFOs created as part of redirection + expansion. */ + unlink_all_fifos (); +#endif + + if (async) + interactive = old_interactive; + + if (command == 0) + { + hookf = find_function (NOTFOUND_HOOK); + if (hookf == 0) + { + /* Make sure filenames are displayed using printable characters */ + pathname = printable_filename (pathname, 0); + internal_error (_("%s: command not found"), pathname); + exit (EX_NOTFOUND); /* Posix.2 says the exit status is 127 */ + } + + /* We don't want to manage process groups for processes we start + from here, so we turn off job control and don't attempt to + manipulate the terminal's process group. */ + without_job_control (); + +#if defined (JOB_CONTROL) + set_sigchld_handler (); +#endif + + wl = make_word_list (make_word (NOTFOUND_HOOK), words); + exit (execute_shell_function (hookf, wl)); + } + + /* Execve expects the command name to be in args[0]. So we + leave it there, in the same format that the user used to + type it in. */ + args = strvec_from_word_list (words, 0, 0, (int *)NULL); + exit (shell_execve (command, args, export_env)); + } + else + { +parent_return: + QUIT; + + /* Make sure that the pipes are closed in the parent. */ + close_pipes (pipe_in, pipe_out); +#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) +#if 0 + if (variable_context == 0) + unlink_fifo_list (); +#endif +#endif + FREE (command); + return (result); + } +} + +/* CPP defines to decide whether a particular index into the #! line + corresponds to a valid interpreter name or argument character, or + whitespace. The MSDOS define is to allow \r to be treated the same + as \n. */ + +#if !defined (MSDOS) +# define STRINGCHAR(ind) \ + (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n') +# define WHITECHAR(ind) \ + (ind < sample_len && whitespace (sample[ind])) +#else /* MSDOS */ +# define STRINGCHAR(ind) \ + (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n' && sample[ind] != '\r') +# define WHITECHAR(ind) \ + (ind < sample_len && whitespace (sample[ind])) +#endif /* MSDOS */ + +static char * +getinterp (sample, sample_len, endp) + char *sample; + int sample_len, *endp; +{ + register int i; + char *execname; + int start; + + /* Find the name of the interpreter to exec. */ + for (i = 2; i < sample_len && whitespace (sample[i]); i++) + ; + + for (start = i; STRINGCHAR(i); i++) + ; + + execname = substring (sample, start, i); + + if (endp) + *endp = i; + return execname; +} + +#if !defined (HAVE_HASH_BANG_EXEC) +/* If the operating system on which we're running does not handle + the #! executable format, then help out. SAMPLE is the text read + from the file, SAMPLE_LEN characters. COMMAND is the name of + the script; it and ARGS, the arguments given by the user, will + become arguments to the specified interpreter. ENV is the environment + to pass to the interpreter. + + The word immediately following the #! is the interpreter to execute. + A single argument to the interpreter is allowed. */ + +static int +execute_shell_script (sample, sample_len, command, args, env) + char *sample; + int sample_len; + char *command; + char **args, **env; +{ + char *execname, *firstarg; + int i, start, size_increment, larry; + + /* Find the name of the interpreter to exec. */ + execname = getinterp (sample, sample_len, &i); + size_increment = 1; + + /* Now the argument, if any. */ + for (firstarg = (char *)NULL, start = i; WHITECHAR(i); i++) + ; + + /* If there is more text on the line, then it is an argument for the + interpreter. */ + + if (STRINGCHAR(i)) + { + for (start = i; STRINGCHAR(i); i++) + ; + firstarg = substring ((char *)sample, start, i); + size_increment = 2; + } + + larry = strvec_len (args) + size_increment; + args = strvec_resize (args, larry + 1); + + for (i = larry - 1; i; i--) + args[i] = args[i - size_increment]; + + args[0] = execname; + if (firstarg) + { + args[1] = firstarg; + args[2] = command; + } + else + args[1] = command; + + args[larry] = (char *)NULL; + + return (shell_execve (execname, args, env)); +} +#undef STRINGCHAR +#undef WHITECHAR + +#endif /* !HAVE_HASH_BANG_EXEC */ + +static void +initialize_subshell () +{ +#if defined (ALIAS) + /* Forget about any aliases that we knew of. We are in a subshell. */ + delete_all_aliases (); +#endif /* ALIAS */ + +#if defined (HISTORY) + /* Forget about the history lines we have read. This is a non-interactive + subshell. */ + history_lines_this_session = 0; +#endif + + /* Forget about the way job control was working. We are in a subshell. */ + without_job_control (); + +#if defined (JOB_CONTROL) + set_sigchld_handler (); + init_job_stats (); +#endif /* JOB_CONTROL */ + + /* Reset the values of the shell flags and options. */ + reset_shell_flags (); + reset_shell_options (); + reset_shopt_options (); + + /* Zero out builtin_env, since this could be a shell script run from a + sourced file with a temporary environment supplied to the `source/.' + builtin. Such variables are not supposed to be exported (empirical + testing with sh and ksh). Just throw it away; don't worry about a + memory leak. */ + if (vc_isbltnenv (shell_variables)) + shell_variables = shell_variables->down; + + clear_unwind_protect_list (0); + /* XXX -- are there other things we should be resetting here? */ + parse_and_execute_level = 0; /* nothing left to restore it */ + + /* We're no longer inside a shell function. */ + variable_context = return_catch_flag = funcnest = evalnest = sourcenest = 0; + + executing_list = 0; /* XXX */ + + /* If we're not interactive, close the file descriptor from which we're + reading the current shell script. */ + if (interactive_shell == 0) + unset_bash_input (0); +} + +#if defined (HAVE_SETOSTYPE) && defined (_POSIX_SOURCE) +# define SETOSTYPE(x) __setostype(x) +#else +# define SETOSTYPE(x) +#endif + +#define HASH_BANG_BUFSIZ 128 + +#define READ_SAMPLE_BUF(file, buf, len) \ + do \ + { \ + fd = open(file, O_RDONLY); \ + if (fd >= 0) \ + { \ + len = read (fd, buf, HASH_BANG_BUFSIZ); \ + close (fd); \ + } \ + else \ + len = -1; \ + } \ + while (0) + +/* Call execve (), handling interpreting shell scripts, and handling + exec failures. */ +int +shell_execve (command, args, env) + char *command; + char **args, **env; +{ + int larray, i, fd; + char sample[HASH_BANG_BUFSIZ]; + int sample_len; + + SETOSTYPE (0); /* Some systems use for USG/POSIX semantics */ + execve (command, args, env); + i = errno; /* error from execve() */ + CHECK_TERMSIG; + SETOSTYPE (1); + + /* If we get to this point, then start checking out the file. + Maybe it is something we can hack ourselves. */ + if (i != ENOEXEC) + { + /* make sure this is set correctly for file_error/report_error */ + last_command_exit_value = (i == ENOENT) ? EX_NOTFOUND : EX_NOEXEC; /* XXX Posix.2 says that exit status is 126 */ + if (file_isdir (command)) +#if defined (EISDIR) + internal_error (_("%s: %s"), command, strerror (EISDIR)); +#else + internal_error (_("%s: is a directory"), command); +#endif + else if (executable_file (command) == 0) + { + errno = i; + file_error (command); + } + /* errors not involving the path argument to execve. */ + else if (i == E2BIG || i == ENOMEM) + { + errno = i; + file_error (command); + } + else if (i == ENOENT) + { + errno = i; + internal_error (_("%s: cannot execute: required file not found"), command); + } + else + { + /* The file has the execute bits set, but the kernel refuses to + run it for some reason. See why. */ +#if defined (HAVE_HASH_BANG_EXEC) + READ_SAMPLE_BUF (command, sample, sample_len); + if (sample_len > 0) + sample[sample_len - 1] = '\0'; + if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') + { + char *interp; + int ilen; + + interp = getinterp (sample, sample_len, (int *)NULL); + ilen = strlen (interp); + errno = i; + if (interp[ilen - 1] == '\r') + { + interp = xrealloc (interp, ilen + 2); + interp[ilen - 1] = '^'; + interp[ilen] = 'M'; + interp[ilen + 1] = '\0'; + } + sys_error (_("%s: %s: bad interpreter"), command, interp ? interp : ""); + FREE (interp); + return (EX_NOEXEC); + } +#endif + errno = i; + file_error (command); + } + return (last_command_exit_value); + } + + /* This file is executable. + If it begins with #!, then help out people with losing operating + systems. Otherwise, check to see if it is a binary file by seeing + if the contents of the first line (or up to 80 characters) are in the + ASCII set. If it's a text file, execute the contents as shell commands, + otherwise return 126 (EX_BINARY_FILE). */ + READ_SAMPLE_BUF (command, sample, sample_len); + + if (sample_len == 0) + return (EXECUTION_SUCCESS); + + /* Is this supposed to be an executable script? + If so, the format of the line is "#! interpreter [argument]". + A single argument is allowed. The BSD kernel restricts + the length of the entire line to 32 characters (32 bytes + being the size of the BSD exec header), but we allow up to 128 + characters. */ + if (sample_len > 0) + { +#if !defined (HAVE_HASH_BANG_EXEC) + if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') + return (execute_shell_script (sample, sample_len, command, args, env)); + else +#endif + if (check_binary_file (sample, sample_len)) + { + internal_error (_("%s: cannot execute binary file: %s"), command, strerror (i)); + errno = i; + return (EX_BINARY_FILE); + } + } + + /* We have committed to attempting to execute the contents of this file + as shell commands. */ + + reset_parser (); + initialize_subshell (); + + set_sigint_handler (); + + /* Insert the name of this shell into the argument list. */ + larray = strvec_len (args) + 1; + args = strvec_resize (args, larray + 1); + + for (i = larray - 1; i; i--) + args[i] = args[i - 1]; + + args[0] = shell_name; + args[1] = command; + args[larray] = (char *)NULL; + + if (args[0][0] == '-') + args[0]++; + +#if defined (RESTRICTED_SHELL) + if (restricted) + change_flag ('r', FLAG_OFF); +#endif + + if (subshell_argv) + { + /* Can't free subshell_argv[0]; that is shell_name. */ + for (i = 1; i < subshell_argc; i++) + free (subshell_argv[i]); + free (subshell_argv); + } + + dispose_command (currently_executing_command); /* XXX */ + currently_executing_command = (COMMAND *)NULL; + + subshell_argc = larray; + subshell_argv = args; + subshell_envp = env; + + unbind_args (); /* remove the positional parameters */ + +#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) + clear_fifo_list (); /* pipe fds are what they are now */ +#endif + + sh_longjmp (subshell_top_level, 1); + /*NOTREACHED*/ +} + +static int +execute_intern_function (name, funcdef) + WORD_DESC *name; + FUNCTION_DEF *funcdef; +{ + SHELL_VAR *var; + char *t; + + if (check_identifier (name, posixly_correct) == 0) + { + if (posixly_correct && interactive_shell == 0) + { + last_command_exit_value = EX_BADUSAGE; + jump_to_top_level (ERREXIT); + } + return (EXECUTION_FAILURE); + } + + if (strchr (name->word, CTLESC)) /* WHY? */ + { + t = dequote_escapes (name->word); + free (name->word); + name->word = t; + } + + /* Posix interpretation 383 */ + if (posixly_correct && find_special_builtin (name->word)) + { + internal_error (_("`%s': is a special builtin"), name->word); + last_command_exit_value = EX_BADUSAGE; + jump_to_top_level (interactive_shell ? DISCARD : ERREXIT); + } + + var = find_function (name->word); + if (var && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + internal_error (_("%s: readonly function"), var->name); + return (EXECUTION_FAILURE); + } + +#if defined (DEBUGGER) + bind_function_def (name->word, funcdef, 1); +#endif + + bind_function (name->word, funcdef->command); + return (EXECUTION_SUCCESS); +} + +#if defined (INCLUDE_UNUSED) +#if defined (PROCESS_SUBSTITUTION) +void +close_all_files () +{ + register int i, fd_table_size; + + fd_table_size = getdtablesize (); + if (fd_table_size > 256) /* clamp to a reasonable value */ + fd_table_size = 256; + + for (i = 3; i < fd_table_size; i++) + close (i); +} +#endif /* PROCESS_SUBSTITUTION */ +#endif + +static void +close_pipes (in, out) + int in, out; +{ + if (in >= 0) + close (in); + if (out >= 0) + close (out); +} + +static void +dup_error (oldd, newd) + int oldd, newd; +{ + sys_error (_("cannot duplicate fd %d to fd %d"), oldd, newd); +} + +/* Redirect input and output to be from and to the specified pipes. + NO_PIPE and REDIRECT_BOTH are handled correctly. */ +static void +do_piping (pipe_in, pipe_out) + int pipe_in, pipe_out; +{ + if (pipe_in != NO_PIPE) + { + if (dup2 (pipe_in, 0) < 0) + dup_error (pipe_in, 0); + if (pipe_in > 0) + close (pipe_in); +#ifdef __CYGWIN__ + /* Let stdio know the fd may have changed from text to binary mode. */ + freopen (NULL, "r", stdin); +#endif /* __CYGWIN__ */ + } + if (pipe_out != NO_PIPE) + { + if (pipe_out != REDIRECT_BOTH) + { + if (dup2 (pipe_out, 1) < 0) + dup_error (pipe_out, 1); + if (pipe_out == 0 || pipe_out > 1) + close (pipe_out); + } + else + { + if (dup2 (1, 2) < 0) + dup_error (1, 2); + } +#ifdef __CYGWIN__ + /* Let stdio know the fd may have changed from text to binary mode, and + make sure to preserve stdout line buffering. */ + freopen (NULL, "w", stdout); + sh_setlinebuf (stdout); +#endif /* __CYGWIN__ */ + } +} diff --git a/third_party/bash/execute_cmd.h b/third_party/bash/execute_cmd.h new file mode 100644 index 000000000..465030aef --- /dev/null +++ b/third_party/bash/execute_cmd.h @@ -0,0 +1,123 @@ +/* execute_cmd.h - functions from execute_cmd.c. */ + +/* Copyright (C) 1993-2017 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_EXECUTE_CMD_H_) +#define _EXECUTE_CMD_H_ + +#include "stdc.h" + +#if defined (ARRAY_VARS) +struct func_array_state + { + ARRAY *funcname_a; + SHELL_VAR *funcname_v; + ARRAY *source_a; + SHELL_VAR *source_v; + ARRAY *lineno_a; + SHELL_VAR *lineno_v; + }; +#endif + +/* Placeholder for later expansion to include more execution state */ +/* XXX - watch out for pid_t */ +struct execstate + { + pid_t pid; + int subshell_env; + }; + + +/* Variables declared in execute_cmd.c, used by many other files */ +extern int return_catch_flag; +extern int return_catch_value; +extern volatile int last_command_exit_value; +extern int last_command_exit_signal; +extern int builtin_ignoring_errexit; +extern int executing_builtin; +extern int executing_list; +extern int comsub_ignore_return; +extern int subshell_level; +extern int match_ignore_case; +extern int executing_command_builtin; +extern int funcnest, funcnest_max; +extern int evalnest, evalnest_max; +extern int sourcenest, sourcenest_max; +extern int stdin_redir; +extern int line_number_for_err_trap; + +extern char *the_printed_command_except_trap; + +extern char *this_command_name; +extern SHELL_VAR *this_shell_function; + +/* Functions declared in execute_cmd.c, used by many other files */ + +extern struct fd_bitmap *new_fd_bitmap PARAMS((int)); +extern void dispose_fd_bitmap PARAMS((struct fd_bitmap *)); +extern void close_fd_bitmap PARAMS((struct fd_bitmap *)); +extern int executing_line_number PARAMS((void)); +extern int execute_command PARAMS((COMMAND *)); +extern int execute_command_internal PARAMS((COMMAND *, int, int, int, struct fd_bitmap *)); +extern int shell_execve PARAMS((char *, char **, char **)); +extern void setup_async_signals PARAMS((void)); +extern void async_redirect_stdin PARAMS((void)); + +extern void undo_partial_redirects PARAMS((void)); +extern void dispose_partial_redirects PARAMS((void)); +extern void dispose_exec_redirects PARAMS((void)); + +extern int execute_shell_function PARAMS((SHELL_VAR *, WORD_LIST *)); + +extern struct coproc *getcoprocbypid PARAMS((pid_t)); +extern struct coproc *getcoprocbyname PARAMS((const char *)); + +extern void coproc_init PARAMS((struct coproc *)); +extern struct coproc *coproc_alloc PARAMS((char *, pid_t)); +extern void coproc_dispose PARAMS((struct coproc *)); +extern void coproc_flush PARAMS((void)); +extern void coproc_close PARAMS((struct coproc *)); +extern void coproc_closeall PARAMS((void)); +extern void coproc_reap PARAMS((void)); +extern pid_t coproc_active PARAMS((void)); + +extern void coproc_rclose PARAMS((struct coproc *, int)); +extern void coproc_wclose PARAMS((struct coproc *, int)); +extern void coproc_fdclose PARAMS((struct coproc *, int)); + +extern void coproc_checkfd PARAMS((struct coproc *, int)); +extern void coproc_fdchk PARAMS((int)); + +extern void coproc_pidchk PARAMS((pid_t, int)); + +extern void coproc_fdsave PARAMS((struct coproc *)); +extern void coproc_fdrestore PARAMS((struct coproc *)); + +extern void coproc_setvars PARAMS((struct coproc *)); +extern void coproc_unsetvars PARAMS((struct coproc *)); + +#if defined (PROCESS_SUBSTITUTION) +extern void close_all_files PARAMS((void)); +#endif + +#if defined (ARRAY_VARS) +extern void restore_funcarray_state PARAMS((struct func_array_state *)); +#endif + +#endif /* _EXECUTE_CMD_H_ */ diff --git a/third_party/bash/expr.c b/third_party/bash/expr.c new file mode 100644 index 000000000..5079bd476 --- /dev/null +++ b/third_party/bash/expr.c @@ -0,0 +1,1693 @@ +/* expr.c -- arithmetic expression evaluation. */ + +/* Copyright (C) 1990-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* + All arithmetic is done as intmax_t integers with no checking for overflow + (though division by 0 is caught and flagged as an error). + + The following operators are handled, grouped into a set of levels in + order of decreasing precedence. + + "id++", "id--" [post-increment and post-decrement] + "-", "+" [(unary operators)] + "++id", "--id" [pre-increment and pre-decrement] + "!", "~" + "**" [(exponentiation)] + "*", "/", "%" + "+", "-" + "<<", ">>" + "<=", ">=", "<", ">" + "==", "!=" + "&" + "^" + "|" + "&&" + "||" + "expr ? expr : expr" + "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=" + , [comma] + + (Note that most of these operators have special meaning to bash, and an + entire expression should be quoted, e.g. "a=$a+1" or "a=a+1" to ensure + that it is passed intact to the evaluator when using `let'. When using + the $[] or $(( )) forms, the text between the `[' and `]' or `((' and `))' + is treated as if in double quotes.) + + Sub-expressions within parentheses have a precedence level greater than + all of the above levels and are evaluated first. Within a single prece- + dence group, evaluation is left-to-right, except for the arithmetic + assignment operator (`='), which is evaluated right-to-left (as in C). + + The expression evaluator returns the value of the expression (assignment + statements have as a value what is returned by the RHS). The `let' + builtin, on the other hand, returns 0 if the last expression evaluates to + a non-zero, and 1 otherwise. + + Implementation is a recursive-descent parser. + + Chet Ramey + chet@po.cwru.edu +*/ + +#include "config.h" + +#include +#include "bashansi.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "chartypes.h" +#include "bashintl.h" + +#include "shell.h" +#include "arrayfunc.h" +#include "execute_cmd.h" +#include "flags.h" +#include "subst.h" +#include "typemax.h" /* INTMAX_MAX, INTMAX_MIN */ + +/* Because of the $((...)) construct, expressions may include newlines. + Here is a macro which accepts newlines, tabs and spaces as whitespace. */ +#define cr_whitespace(c) (whitespace(c) || ((c) == '\n')) + +/* Size be which the expression stack grows when necessary. */ +#define EXPR_STACK_GROW_SIZE 10 + +/* Maximum amount of recursion allowed. This prevents a non-integer + variable such as "num=num+2" from infinitely adding to itself when + "let num=num+2" is given. */ +#define MAX_EXPR_RECURSION_LEVEL 1024 + +/* The Tokens. Singing "The Lion Sleeps Tonight". */ + +#define EQEQ 1 /* "==" */ +#define NEQ 2 /* "!=" */ +#define LEQ 3 /* "<=" */ +#define GEQ 4 /* ">=" */ +#define STR 5 /* string */ +#define NUM 6 /* number */ +#define LAND 7 /* "&&" Logical AND */ +#define LOR 8 /* "||" Logical OR */ +#define LSH 9 /* "<<" Left SHift */ +#define RSH 10 /* ">>" Right SHift */ +#define OP_ASSIGN 11 /* op= expassign as in Posix.2 */ +#define COND 12 /* exp1 ? exp2 : exp3 */ +#define POWER 13 /* exp1**exp2 */ +#define PREINC 14 /* ++var */ +#define PREDEC 15 /* --var */ +#define POSTINC 16 /* var++ */ +#define POSTDEC 17 /* var-- */ +#define EQ '=' +#define GT '>' +#define LT '<' +#define PLUS '+' +#define MINUS '-' +#define MUL '*' +#define DIV '/' +#define MOD '%' +#define NOT '!' +#define LPAR '(' +#define RPAR ')' +#define BAND '&' /* Bitwise AND */ +#define BOR '|' /* Bitwise OR. */ +#define BXOR '^' /* Bitwise eXclusive OR. */ +#define BNOT '~' /* Bitwise NOT; Two's complement. */ +#define QUES '?' +#define COL ':' +#define COMMA ',' + +/* This should be the function corresponding to the operator with the + lowest precedence. */ +#define EXP_LOWEST expcomma + +#ifndef MAX_INT_LEN +# define MAX_INT_LEN 32 +#endif + +struct lvalue +{ + char *tokstr; /* possibly-rewritten lvalue if not NULL */ + intmax_t tokval; /* expression evaluated value */ + SHELL_VAR *tokvar; /* variable described by array or var reference */ + intmax_t ind; /* array index if not -1 */ +}; + +/* A structure defining a single expression context. */ +typedef struct { + int curtok, lasttok; + char *expression, *tp, *lasttp; + intmax_t tokval; + char *tokstr; + int noeval; + struct lvalue lval; +} EXPR_CONTEXT; + +static char *expression; /* The current expression */ +static char *tp; /* token lexical position */ +static char *lasttp; /* pointer to last token position */ +static int curtok; /* the current token */ +static int lasttok; /* the previous token */ +static int assigntok; /* the OP in OP= */ +static char *tokstr; /* current token string */ +static intmax_t tokval; /* current token value */ +static int noeval; /* set to 1 if no assignment to be done */ +static procenv_t evalbuf; + +/* set to 1 if the expression has already been run through word expansion */ +static int already_expanded; + +static struct lvalue curlval = {0, 0, 0, -1}; +static struct lvalue lastlval = {0, 0, 0, -1}; + +static int _is_arithop PARAMS((int)); +static void readtok PARAMS((void)); /* lexical analyzer */ + +static void init_lvalue PARAMS((struct lvalue *)); +static struct lvalue *alloc_lvalue PARAMS((void)); +static void free_lvalue PARAMS((struct lvalue *)); + +static intmax_t expr_streval PARAMS((char *, int, struct lvalue *)); +static intmax_t strlong PARAMS((char *)); +static void evalerror PARAMS((const char *)); + +static void pushexp PARAMS((void)); +static void popexp PARAMS((void)); +static void expr_unwind PARAMS((void)); +static void expr_bind_variable PARAMS((char *, char *)); +#if defined (ARRAY_VARS) +static void expr_bind_array_element PARAMS((char *, arrayind_t, char *)); +#endif + +static intmax_t subexpr PARAMS((char *)); + +static intmax_t expcomma PARAMS((void)); +static intmax_t expassign PARAMS((void)); +static intmax_t expcond PARAMS((void)); +static intmax_t explor PARAMS((void)); +static intmax_t expland PARAMS((void)); +static intmax_t expbor PARAMS((void)); +static intmax_t expbxor PARAMS((void)); +static intmax_t expband PARAMS((void)); +static intmax_t exp5 PARAMS((void)); +static intmax_t exp4 PARAMS((void)); +static intmax_t expshift PARAMS((void)); +static intmax_t exp3 PARAMS((void)); +static intmax_t expmuldiv PARAMS((void)); +static intmax_t exppower PARAMS((void)); +static intmax_t exp1 PARAMS((void)); +static intmax_t exp0 PARAMS((void)); + +/* Global var which contains the stack of expression contexts. */ +static EXPR_CONTEXT **expr_stack; +static int expr_depth; /* Location in the stack. */ +static int expr_stack_size; /* Number of slots already allocated. */ + +#if defined (ARRAY_VARS) +extern const char * const bash_badsub_errmsg; +#endif + +#define SAVETOK(X) \ + do { \ + (X)->curtok = curtok; \ + (X)->lasttok = lasttok; \ + (X)->tp = tp; \ + (X)->lasttp = lasttp; \ + (X)->tokval = tokval; \ + (X)->tokstr = tokstr; \ + (X)->noeval = noeval; \ + (X)->lval = curlval; \ + } while (0) + +#define RESTORETOK(X) \ + do { \ + curtok = (X)->curtok; \ + lasttok = (X)->lasttok; \ + tp = (X)->tp; \ + lasttp = (X)->lasttp; \ + tokval = (X)->tokval; \ + tokstr = (X)->tokstr; \ + noeval = (X)->noeval; \ + curlval = (X)->lval; \ + } while (0) + +/* Push and save away the contents of the globals describing the + current expression context. */ +static void +pushexp () +{ + EXPR_CONTEXT *context; + + if (expr_depth >= MAX_EXPR_RECURSION_LEVEL) + evalerror (_("expression recursion level exceeded")); + + if (expr_depth >= expr_stack_size) + { + expr_stack_size += EXPR_STACK_GROW_SIZE; + expr_stack = (EXPR_CONTEXT **)xrealloc (expr_stack, expr_stack_size * sizeof (EXPR_CONTEXT *)); + } + + context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT)); + + context->expression = expression; + SAVETOK(context); + + expr_stack[expr_depth++] = context; +} + +/* Pop the the contents of the expression context stack into the + globals describing the current expression context. */ +static void +popexp () +{ + EXPR_CONTEXT *context; + + if (expr_depth <= 0) + { + /* See the comment at the top of evalexp() for an explanation of why + this is done. */ + expression = lasttp = 0; + evalerror (_("recursion stack underflow")); + } + + context = expr_stack[--expr_depth]; + + expression = context->expression; + RESTORETOK (context); + + free (context); +} + +static void +expr_unwind () +{ + while (--expr_depth > 0) + { + if (expr_stack[expr_depth]->tokstr) + free (expr_stack[expr_depth]->tokstr); + + if (expr_stack[expr_depth]->expression) + free (expr_stack[expr_depth]->expression); + + free (expr_stack[expr_depth]); + } + if (expr_depth == 0) + free (expr_stack[expr_depth]); /* free the allocated EXPR_CONTEXT */ + + noeval = 0; /* XXX */ +} + +static void +expr_bind_variable (lhs, rhs) + char *lhs, *rhs; +{ + SHELL_VAR *v; + int aflags; + + if (lhs == 0 || *lhs == 0) + return; /* XXX */ + +#if defined (ARRAY_VARS) + aflags = (assoc_expand_once && already_expanded) ? ASS_NOEXPAND : 0; + aflags |= ASS_ALLOWALLSUB; /* allow assoc[@]=value */ +#else + aflags = 0; +#endif + v = bind_int_variable (lhs, rhs, aflags); + if (v && (readonly_p (v) || noassign_p (v))) + sh_longjmp (evalbuf, 1); /* variable assignment error */ + stupidly_hack_special_variables (lhs); +} + +#if defined (ARRAY_VARS) +/* This is similar to the logic in arrayfunc.c:valid_array_reference when + you pass VA_NOEXPAND. */ +static int +expr_skipsubscript (vp, cp) + char *vp, *cp; +{ + int flags, isassoc; + SHELL_VAR *entry; + + isassoc = 0; + entry = 0; + if (assoc_expand_once & already_expanded) + { + *cp = '\0'; + isassoc = legal_identifier (vp) && (entry = find_variable (vp)) && assoc_p (entry); + *cp = '['; /* ] */ + } + flags = (isassoc && assoc_expand_once && already_expanded) ? VA_NOEXPAND : 0; + return (skipsubscript (cp, 0, flags)); +} + +/* Rewrite tok, which is of the form vname[expression], to vname[ind], where + IND is the already-calculated value of expression. */ +static void +expr_bind_array_element (tok, ind, rhs) + char *tok; + arrayind_t ind; + char *rhs; +{ + char *lhs, *vname; + size_t llen; + char ibuf[INT_STRLEN_BOUND (arrayind_t) + 1], *istr; + + istr = fmtumax (ind, 10, ibuf, sizeof (ibuf), 0); + vname = array_variable_name (tok, 0, (char **)NULL, (int *)NULL); + + llen = strlen (vname) + sizeof (ibuf) + 3; + lhs = xmalloc (llen); + + sprintf (lhs, "%s[%s]", vname, istr); /* XXX */ + +/*itrace("expr_bind_array_element: %s=%s", lhs, rhs);*/ + expr_bind_variable (lhs, rhs); + free (vname); + free (lhs); +} +#endif /* ARRAY_VARS */ + +/* Evaluate EXPR, and return the arithmetic result. If VALIDP is + non-null, a zero is stored into the location to which it points + if the expression is invalid, non-zero otherwise. If a non-zero + value is returned in *VALIDP, the return value of evalexp() may + be used. + + The `while' loop after the longjmp is caught relies on the above + implementation of pushexp and popexp leaving in expr_stack[0] the + values that the variables had when the program started. That is, + the first things saved are the initial values of the variables that + were assigned at program startup or by the compiler. Therefore, it is + safe to let the loop terminate when expr_depth == 0, without freeing up + any of the expr_depth[0] stuff. */ +intmax_t +evalexp (expr, flags, validp) + char *expr; + int flags; + int *validp; +{ + intmax_t val; + int c; + procenv_t oevalbuf; + + val = 0; + noeval = 0; + already_expanded = (flags&EXP_EXPANDED); + + FASTCOPY (evalbuf, oevalbuf, sizeof (evalbuf)); + + c = setjmp_nosigs (evalbuf); + + if (c) + { + FREE (tokstr); + FREE (expression); + tokstr = expression = (char *)NULL; + + expr_unwind (); + expr_depth = 0; /* XXX - make sure */ + + /* We copy in case we've called evalexp recursively */ + FASTCOPY (oevalbuf, evalbuf, sizeof (evalbuf)); + + if (validp) + *validp = 0; + return (0); + } + + val = subexpr (expr); + + if (validp) + *validp = 1; + + FASTCOPY (oevalbuf, evalbuf, sizeof (evalbuf)); + + return (val); +} + +static intmax_t +subexpr (expr) + char *expr; +{ + intmax_t val; + char *p; + + for (p = expr; p && *p && cr_whitespace (*p); p++) + ; + + if (p == NULL || *p == '\0') + return (0); + + pushexp (); + expression = savestring (expr); + tp = expression; + + curtok = lasttok = 0; + tokstr = (char *)NULL; + tokval = 0; + init_lvalue (&curlval); + lastlval = curlval; + + readtok (); + + val = EXP_LOWEST (); + + /*TAG:bash-5.3 make it clear that these are arithmetic syntax errors */ + if (curtok != 0) + evalerror (_("syntax error in expression")); + + FREE (tokstr); + FREE (expression); + + popexp (); + + return val; +} + +static intmax_t +expcomma () +{ + register intmax_t value; + + value = expassign (); + while (curtok == COMMA) + { + readtok (); + value = expassign (); + } + + return value; +} + +static intmax_t +expassign () +{ + register intmax_t value; + char *lhs, *rhs; + arrayind_t lind; +#if defined (HAVE_IMAXDIV) + imaxdiv_t idiv; +#endif + + value = expcond (); + if (curtok == EQ || curtok == OP_ASSIGN) + { + int special, op; + intmax_t lvalue; + + special = curtok == OP_ASSIGN; + + if (lasttok != STR) + evalerror (_("attempted assignment to non-variable")); + + if (special) + { + op = assigntok; /* a OP= b */ + lvalue = value; + } + + if (tokstr == 0) + evalerror (_("syntax error in variable assignment")); + + /* XXX - watch out for pointer aliasing issues here */ + lhs = savestring (tokstr); + /* save ind in case rhs is string var and evaluation overwrites it */ + lind = curlval.ind; + readtok (); + value = expassign (); + + if (special) + { + if ((op == DIV || op == MOD) && value == 0) + { + if (noeval == 0) + evalerror (_("division by 0")); + else + value = 1; + } + + switch (op) + { + case MUL: + /* Handle INTMAX_MIN and INTMAX_MAX * -1 specially here? */ + lvalue *= value; + break; + case DIV: + case MOD: + if (lvalue == INTMAX_MIN && value == -1) + lvalue = (op == DIV) ? INTMAX_MIN : 0; + else +#if HAVE_IMAXDIV + { + idiv = imaxdiv (lvalue, value); + lvalue = (op == DIV) ? idiv.quot : idiv.rem; + } +#else + lvalue = (op == DIV) ? lvalue / value : lvalue % value; +#endif + break; + case PLUS: + lvalue += value; + break; + case MINUS: + lvalue -= value; + break; + case LSH: + lvalue <<= value; + break; + case RSH: + lvalue >>= value; + break; + case BAND: + lvalue &= value; + break; + case BOR: + lvalue |= value; + break; + case BXOR: + lvalue ^= value; + break; + default: + free (lhs); + evalerror (_("bug: bad expassign token")); + break; + } + value = lvalue; + } + + rhs = itos (value); + if (noeval == 0) + { +#if defined (ARRAY_VARS) + if (lind != -1) + expr_bind_array_element (lhs, lind, rhs); + else +#endif + expr_bind_variable (lhs, rhs); + } + if (curlval.tokstr && curlval.tokstr == tokstr) + init_lvalue (&curlval); + + free (rhs); + free (lhs); + FREE (tokstr); + tokstr = (char *)NULL; /* For freeing on errors. */ + } + + return (value); +} + +/* Conditional expression (expr?expr:expr) */ +static intmax_t +expcond () +{ + intmax_t cval, val1, val2, rval; + int set_noeval; + + set_noeval = 0; + rval = cval = explor (); + if (curtok == QUES) /* found conditional expr */ + { + if (cval == 0) + { + set_noeval = 1; + noeval++; + } + + readtok (); + if (curtok == 0 || curtok == COL) + evalerror (_("expression expected")); + + val1 = EXP_LOWEST (); + + if (set_noeval) + noeval--; + if (curtok != COL) + evalerror (_("`:' expected for conditional expression")); + + set_noeval = 0; + if (cval) + { + set_noeval = 1; + noeval++; + } + + readtok (); + if (curtok == 0) + evalerror (_("expression expected")); + val2 = expcond (); + + if (set_noeval) + noeval--; + rval = cval ? val1 : val2; + lasttok = COND; + } + return rval; +} + +/* Logical OR. */ +static intmax_t +explor () +{ + register intmax_t val1, val2; + int set_noeval; + + val1 = expland (); + + while (curtok == LOR) + { + set_noeval = 0; + if (val1 != 0) + { + noeval++; + set_noeval = 1; + } + readtok (); + val2 = expland (); + if (set_noeval) + noeval--; + val1 = val1 || val2; + lasttok = LOR; + } + + return (val1); +} + +/* Logical AND. */ +static intmax_t +expland () +{ + register intmax_t val1, val2; + int set_noeval; + + val1 = expbor (); + + while (curtok == LAND) + { + set_noeval = 0; + if (val1 == 0) + { + set_noeval = 1; + noeval++; + } + readtok (); + val2 = expbor (); + if (set_noeval) + noeval--; + val1 = val1 && val2; + lasttok = LAND; + } + + return (val1); +} + +/* Bitwise OR. */ +static intmax_t +expbor () +{ + register intmax_t val1, val2; + + val1 = expbxor (); + + while (curtok == BOR) + { + readtok (); + val2 = expbxor (); + val1 = val1 | val2; + lasttok = NUM; + } + + return (val1); +} + +/* Bitwise XOR. */ +static intmax_t +expbxor () +{ + register intmax_t val1, val2; + + val1 = expband (); + + while (curtok == BXOR) + { + readtok (); + val2 = expband (); + val1 = val1 ^ val2; + lasttok = NUM; + } + + return (val1); +} + +/* Bitwise AND. */ +static intmax_t +expband () +{ + register intmax_t val1, val2; + + val1 = exp5 (); + + while (curtok == BAND) + { + readtok (); + val2 = exp5 (); + val1 = val1 & val2; + lasttok = NUM; + } + + return (val1); +} + +static intmax_t +exp5 () +{ + register intmax_t val1, val2; + + val1 = exp4 (); + + while ((curtok == EQEQ) || (curtok == NEQ)) + { + int op = curtok; + + readtok (); + val2 = exp4 (); + if (op == EQEQ) + val1 = (val1 == val2); + else if (op == NEQ) + val1 = (val1 != val2); + lasttok = NUM; + } + return (val1); +} + +static intmax_t +exp4 () +{ + register intmax_t val1, val2; + + val1 = expshift (); + while ((curtok == LEQ) || + (curtok == GEQ) || + (curtok == LT) || + (curtok == GT)) + { + int op = curtok; + + readtok (); + val2 = expshift (); + + if (op == LEQ) + val1 = val1 <= val2; + else if (op == GEQ) + val1 = val1 >= val2; + else if (op == LT) + val1 = val1 < val2; + else /* (op == GT) */ + val1 = val1 > val2; + lasttok = NUM; + } + return (val1); +} + +/* Left and right shifts. */ +static intmax_t +expshift () +{ + register intmax_t val1, val2; + + val1 = exp3 (); + + while ((curtok == LSH) || (curtok == RSH)) + { + int op = curtok; + + readtok (); + val2 = exp3 (); + + if (op == LSH) + val1 = val1 << val2; + else + val1 = val1 >> val2; + lasttok = NUM; + } + + return (val1); +} + +static intmax_t +exp3 () +{ + register intmax_t val1, val2; + + val1 = expmuldiv (); + + while ((curtok == PLUS) || (curtok == MINUS)) + { + int op = curtok; + + readtok (); + val2 = expmuldiv (); + + if (op == PLUS) + val1 += val2; + else if (op == MINUS) + val1 -= val2; + lasttok = NUM; + } + return (val1); +} + +static intmax_t +expmuldiv () +{ + register intmax_t val1, val2; +#if defined (HAVE_IMAXDIV) + imaxdiv_t idiv; +#endif + + val1 = exppower (); + + while ((curtok == MUL) || + (curtok == DIV) || + (curtok == MOD)) + { + int op = curtok; + char *stp, *sltp; + + stp = tp; + readtok (); + + val2 = exppower (); + + /* Handle division by 0 and twos-complement arithmetic overflow */ + if (((op == DIV) || (op == MOD)) && (val2 == 0)) + { + if (noeval == 0) + { + sltp = lasttp; + lasttp = stp; + while (lasttp && *lasttp && whitespace (*lasttp)) + lasttp++; + evalerror (_("division by 0")); + lasttp = sltp; + } + else + val2 = 1; + } + else if (op == MOD && val1 == INTMAX_MIN && val2 == -1) + { + val1 = 0; + continue; + } + else if (op == DIV && val1 == INTMAX_MIN && val2 == -1) + val2 = 1; + + if (op == MUL) + val1 *= val2; + else if (op == DIV || op == MOD) +#if defined (HAVE_IMAXDIV) + { + idiv = imaxdiv (val1, val2); + val1 = (op == DIV) ? idiv.quot : idiv.rem; + } +#else + val1 = (op == DIV) ? val1 / val2 : val1 % val2; +#endif + lasttok = NUM; + } + return (val1); +} + +static intmax_t +ipow (base, exp) + intmax_t base, exp; +{ + intmax_t result; + + result = 1; + while (exp) + { + if (exp & 1) + result *= base; + exp >>= 1; + base *= base; + } + return result; +} + +static intmax_t +exppower () +{ + register intmax_t val1, val2, c; + + val1 = exp1 (); + while (curtok == POWER) + { + readtok (); + val2 = exppower (); /* exponentiation is right-associative */ + lasttok = NUM; + if (val2 == 0) + return (1); + if (val2 < 0) + evalerror (_("exponent less than 0")); + val1 = ipow (val1, val2); + } + return (val1); +} + +static intmax_t +exp1 () +{ + register intmax_t val; + + if (curtok == NOT) + { + readtok (); + val = !exp1 (); + lasttok = NUM; + } + else if (curtok == BNOT) + { + readtok (); + val = ~exp1 (); + lasttok = NUM; + } + else if (curtok == MINUS) + { + readtok (); + val = - exp1 (); + lasttok = NUM; + } + else if (curtok == PLUS) + { + readtok (); + val = exp1 (); + lasttok = NUM; + } + else + val = exp0 (); + + return (val); +} + +static intmax_t +exp0 () +{ + register intmax_t val = 0, v2; + char *vincdec; + int stok; + EXPR_CONTEXT ec; + + /* XXX - might need additional logic here to decide whether or not + pre-increment or pre-decrement is legal at this point. */ + if (curtok == PREINC || curtok == PREDEC) + { + stok = lasttok = curtok; + readtok (); + if (curtok != STR) + /* readtok() catches this */ + evalerror (_("identifier expected after pre-increment or pre-decrement")); + + v2 = tokval + ((stok == PREINC) ? 1 : -1); + vincdec = itos (v2); + if (noeval == 0) + { +#if defined (ARRAY_VARS) + if (curlval.ind != -1) + expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); + else +#endif + if (tokstr) + expr_bind_variable (tokstr, vincdec); + } + free (vincdec); + val = v2; + + curtok = NUM; /* make sure --x=7 is flagged as an error */ + readtok (); + } + else if (curtok == LPAR) + { + /* XXX - save curlval here? Or entire expression context? */ + readtok (); + val = EXP_LOWEST (); + + if (curtok != RPAR) /* ( */ + evalerror (_("missing `)'")); + + /* Skip over closing paren. */ + readtok (); + } + else if ((curtok == NUM) || (curtok == STR)) + { + val = tokval; + if (curtok == STR) + { + SAVETOK (&ec); + tokstr = (char *)NULL; /* keep it from being freed */ + noeval = 1; + readtok (); + stok = curtok; + + /* post-increment or post-decrement */ + if (stok == POSTINC || stok == POSTDEC) + { + /* restore certain portions of EC */ + tokstr = ec.tokstr; + noeval = ec.noeval; + curlval = ec.lval; + lasttok = STR; /* ec.curtok */ + + v2 = val + ((stok == POSTINC) ? 1 : -1); + vincdec = itos (v2); + if (noeval == 0) + { +#if defined (ARRAY_VARS) + if (curlval.ind != -1) + expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); + else +#endif + expr_bind_variable (tokstr, vincdec); + } + free (vincdec); + curtok = NUM; /* make sure x++=7 is flagged as an error */ + } + else + { + /* XXX - watch out for pointer aliasing issues here */ + if (stok == STR) /* free new tokstr before old one is restored */ + FREE (tokstr); + RESTORETOK (&ec); + } + } + + readtok (); + } + else + evalerror (_("syntax error: operand expected")); + + return (val); +} + +static void +init_lvalue (lv) + struct lvalue *lv; +{ + lv->tokstr = 0; + lv->tokvar = 0; + lv->tokval = lv->ind = -1; +} + +static struct lvalue * +alloc_lvalue () +{ + struct lvalue *lv; + + lv = xmalloc (sizeof (struct lvalue)); + init_lvalue (lv); + return (lv); +} + +static void +free_lvalue (lv) + struct lvalue *lv; +{ + free (lv); /* should be inlined */ +} + +static intmax_t +expr_streval (tok, e, lvalue) + char *tok; + int e; + struct lvalue *lvalue; +{ + SHELL_VAR *v; + char *value; + intmax_t tval; + int initial_depth; +#if defined (ARRAY_VARS) + arrayind_t ind; + int tflag, aflag; + array_eltstate_t es; +#endif + +/*itrace("expr_streval: %s: noeval = %d expanded=%d", tok, noeval, already_expanded);*/ + /* If we are suppressing evaluation, just short-circuit here instead of + going through the rest of the evaluator. */ + if (noeval) + return (0); + + initial_depth = expr_depth; + +#if defined (ARRAY_VARS) + tflag = (assoc_expand_once && already_expanded) ? AV_NOEXPAND : 0; /* for a start */ +#endif + + /* [[[[[ */ +#if defined (ARRAY_VARS) + aflag = tflag; /* use a different variable for now */ + v = (e == ']') ? array_variable_part (tok, tflag, (char **)0, (int *)0) : find_variable (tok); +#else + v = find_variable (tok); +#endif + if (v == 0 && e != ']') + v = find_variable_last_nameref (tok, 0); + + if ((v == 0 || invisible_p (v)) && unbound_vars_is_error) + { +#if defined (ARRAY_VARS) + value = (e == ']') ? array_variable_name (tok, tflag, (char **)0, (int *)0) : tok; +#else + value = tok; +#endif + + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (value); + +#if defined (ARRAY_VARS) + if (e == ']') + FREE (value); /* array_variable_name returns new memory */ +#endif + + if (no_longjmp_on_fatal_error && interactive_shell) + sh_longjmp (evalbuf, 1); + + if (interactive_shell) + { + expr_unwind (); + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + else + jump_to_top_level (FORCE_EOF); + } + +#if defined (ARRAY_VARS) + init_eltstate (&es); + es.ind = -1; + /* If the second argument to get_array_value doesn't include AV_ALLOWALL, + we don't allow references like array[@]. In this case, get_array_value + is just like get_variable_value in that it does not return newly-allocated + memory or quote the results. AFLAG is set above and is either AV_NOEXPAND + or 0. */ + value = (e == ']') ? get_array_value (tok, aflag, &es) : get_variable_value (v); + ind = es.ind; + flush_eltstate (&es); +#else + value = get_variable_value (v); +#endif + + if (expr_depth < initial_depth) + { + if (no_longjmp_on_fatal_error && interactive_shell) + sh_longjmp (evalbuf, 1); + return (0); + } + + tval = (value && *value) ? subexpr (value) : 0; + + if (lvalue) + { + lvalue->tokstr = tok; /* XXX */ + lvalue->tokval = tval; + lvalue->tokvar = v; /* XXX */ +#if defined (ARRAY_VARS) + lvalue->ind = ind; +#else + lvalue->ind = -1; +#endif + } + + return (tval); +} + +static int +_is_multiop (c) + int c; +{ + switch (c) + { + case EQEQ: + case NEQ: + case LEQ: + case GEQ: + case LAND: + case LOR: + case LSH: + case RSH: + case OP_ASSIGN: + case COND: + case POWER: + case PREINC: + case PREDEC: + case POSTINC: + case POSTDEC: + return 1; + default: + return 0; + } +} + +static int +_is_arithop (c) + int c; +{ + switch (c) + { + case EQ: + case GT: + case LT: + case PLUS: + case MINUS: + case MUL: + case DIV: + case MOD: + case NOT: + case LPAR: + case RPAR: + case BAND: + case BOR: + case BXOR: + case BNOT: + return 1; /* operator tokens */ + case QUES: + case COL: + case COMMA: + return 1; /* questionable */ + default: + return 0; /* anything else is invalid */ + } +} + +/* Lexical analyzer/token reader for the expression evaluator. Reads the + next token and puts its value into curtok, while advancing past it. + Updates value of tp. May also set tokval (for number) or tokstr (for + string). */ +static void +readtok () +{ + register char *cp, *xp; + register unsigned char c, c1; + register int e; + struct lvalue lval; + + /* Skip leading whitespace. */ + cp = tp; + c = e = 0; + while (cp && (c = *cp) && (cr_whitespace (c))) + cp++; + + if (c) + cp++; + + if (c == '\0') + { + lasttok = curtok; + curtok = 0; + tp = cp; + return; + } + lasttp = tp = cp - 1; + + if (legal_variable_starter (c)) + { + /* variable names not preceded with a dollar sign are shell variables. */ + char *savecp; + EXPR_CONTEXT ec; + int peektok; + + while (legal_variable_char (c)) + c = *cp++; + + c = *--cp; + +#if defined (ARRAY_VARS) + if (c == '[') + { + e = expr_skipsubscript (tp, cp); /* XXX - was skipsubscript */ + if (cp[e] == ']') + { + cp += e + 1; + c = *cp; + e = ']'; + } + else + evalerror (bash_badsub_errmsg); + } +#endif /* ARRAY_VARS */ + + *cp = '\0'; + /* XXX - watch out for pointer aliasing issues here */ + if (curlval.tokstr && curlval.tokstr == tokstr) + init_lvalue (&curlval); + + FREE (tokstr); + tokstr = savestring (tp); + *cp = c; + + /* XXX - make peektok part of saved token state? */ + SAVETOK (&ec); + tokstr = (char *)NULL; /* keep it from being freed */ + tp = savecp = cp; + noeval = 1; + curtok = STR; + readtok (); + peektok = curtok; + if (peektok == STR) /* free new tokstr before old one is restored */ + FREE (tokstr); + RESTORETOK (&ec); + cp = savecp; + + /* The tests for PREINC and PREDEC aren't strictly correct, but they + preserve old behavior if a construct like --x=9 is given. */ + if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ) + { + lastlval = curlval; + tokval = expr_streval (tokstr, e, &curlval); + } + else + tokval = 0; + + lasttok = curtok; + curtok = STR; + } + else if (DIGIT(c)) + { + while (ISALNUM (c) || c == '#' || c == '@' || c == '_') + c = *cp++; + + c = *--cp; + *cp = '\0'; + + tokval = strlong (tp); + *cp = c; + lasttok = curtok; + curtok = NUM; + } + else + { + c1 = *cp++; + if ((c == EQ) && (c1 == EQ)) + c = EQEQ; + else if ((c == NOT) && (c1 == EQ)) + c = NEQ; + else if ((c == GT) && (c1 == EQ)) + c = GEQ; + else if ((c == LT) && (c1 == EQ)) + c = LEQ; + else if ((c == LT) && (c1 == LT)) + { + if (*cp == '=') /* a <<= b */ + { + assigntok = LSH; + c = OP_ASSIGN; + cp++; + } + else + c = LSH; + } + else if ((c == GT) && (c1 == GT)) + { + if (*cp == '=') + { + assigntok = RSH; /* a >>= b */ + c = OP_ASSIGN; + cp++; + } + else + c = RSH; + } + else if ((c == BAND) && (c1 == BAND)) + c = LAND; + else if ((c == BOR) && (c1 == BOR)) + c = LOR; + else if ((c == '*') && (c1 == '*')) + c = POWER; + else if ((c == '-' || c == '+') && c1 == c && curtok == STR) + c = (c == '-') ? POSTDEC : POSTINC; +#if STRICT_ARITH_PARSING + else if ((c == '-' || c == '+') && c1 == c && curtok == NUM) +#else + else if ((c == '-' || c == '+') && c1 == c && curtok == NUM && (lasttok == PREINC || lasttok == PREDEC)) +#endif + { + /* This catches something like --FOO++ */ + /* TAG:bash-5.3 add gettext calls here or make this a separate function */ + if (c == '-') + evalerror ("--: assignment requires lvalue"); + else + evalerror ("++: assignment requires lvalue"); + } + else if ((c == '-' || c == '+') && c1 == c) + { + /* Quickly scan forward to see if this is followed by optional + whitespace and an identifier. */ + xp = cp; + while (xp && *xp && cr_whitespace (*xp)) + xp++; + if (legal_variable_starter ((unsigned char)*xp)) + c = (c == '-') ? PREDEC : PREINC; + else + /* Could force parsing as preinc or predec and throw an error */ +#if STRICT_ARITH_PARSING + { + /* Posix says unary plus and minus have higher priority than + preinc and predec. */ + /* This catches something like --4++ */ + if (c == '-') + evalerror ("--: assignment requires lvalue"); + else + evalerror ("++: assignment requires lvalue"); + } +#else + cp--; /* not preinc or predec, so unget the character */ +#endif + } + else if (c1 == EQ && member (c, "*/%+-&^|")) + { + assigntok = c; /* a OP= b */ + c = OP_ASSIGN; + } + else if (_is_arithop (c) == 0) + { + cp--; + /* use curtok, since it hasn't been copied to lasttok yet */ + if (curtok == 0 || _is_arithop (curtok) || _is_multiop (curtok)) + evalerror (_("syntax error: operand expected")); + else + evalerror (_("syntax error: invalid arithmetic operator")); + } + else + cp--; /* `unget' the character */ + + /* Should check here to make sure that the current character is one + of the recognized operators and flag an error if not. Could create + a character map the first time through and check it on subsequent + calls. */ + lasttok = curtok; + curtok = c; + } + tp = cp; +} + +static void +evalerror (msg) + const char *msg; +{ + char *name, *t; + + name = this_command_name; + for (t = expression; t && whitespace (*t); t++) + ; + internal_error (_("%s%s%s: %s (error token is \"%s\")"), + name ? name : "", name ? ": " : "", + t ? t : "", msg, (lasttp && *lasttp) ? lasttp : ""); + sh_longjmp (evalbuf, 1); +} + +/* Convert a string to an intmax_t integer, with an arbitrary base. + 0nnn -> base 8 + 0[Xx]nn -> base 16 + Anything else: [base#]number (this is implemented to match ksh93) + + Base may be >=2 and <=64. If base is <= 36, the numbers are drawn + from [0-9][a-zA-Z], and lowercase and uppercase letters may be used + interchangeably. If base is > 36 and <= 64, the numbers are drawn + from [0-9][a-z][A-Z]_@ (a = 10, z = 35, A = 36, Z = 61, @ = 62, _ = 63 -- + you get the picture). */ + +#define VALID_NUMCHAR(c) (ISALNUM(c) || ((c) == '_') || ((c) == '@')) + +static intmax_t +strlong (num) + char *num; +{ + register char *s; + register unsigned char c; + int base, foundbase; + intmax_t val, pval; + + s = num; + + base = 10; + foundbase = 0; + if (*s == '0') + { + s++; + + if (*s == '\0') + return 0; + + /* Base 16? */ + if (*s == 'x' || *s == 'X') + { + base = 16; + s++; +#if STRICT_ARITH_PARSING + if (*s == 0) + evalerror (_("invalid number")); +#endif + } + else + base = 8; + foundbase++; + } + + val = 0; + for (c = *s++; c; c = *s++) + { + if (c == '#') + { + if (foundbase) + evalerror (_("invalid number")); + + /* Illegal base specifications raise an evaluation error. */ + if (val < 2 || val > 64) + evalerror (_("invalid arithmetic base")); + + base = val; + val = 0; + foundbase++; + + /* Make sure a base# is followed by a character that can compose a + valid integer constant. Jeremy Townshend */ + if (VALID_NUMCHAR (*s) == 0) + evalerror (_("invalid integer constant")); + } + else if (VALID_NUMCHAR (c)) + { + if (DIGIT(c)) + c = TODIGIT(c); + else if (c >= 'a' && c <= 'z') + c -= 'a' - 10; + else if (c >= 'A' && c <= 'Z') + c -= 'A' - ((base <= 36) ? 10 : 36); + else if (c == '@') + c = 62; + else if (c == '_') + c = 63; + + if (c >= base) + evalerror (_("value too great for base")); + +#ifdef CHECK_OVERFLOW + pval = val; + val = (val * base) + c; + if (val < 0 || val < pval) /* overflow */ + return INTMAX_MAX; +#else + val = (val * base) + c; +#endif + } + else + break; + } + + return (val); +} + +#if defined (EXPR_TEST) +void * +xmalloc (n) + int n; +{ + return (malloc (n)); +} + +void * +xrealloc (s, n) + char *s; + int n; +{ + return (realloc (s, n)); +} + +SHELL_VAR *find_variable () { return 0;} +SHELL_VAR *bind_variable () { return 0; } + +char *get_string_value () { return 0; } + +procenv_t top_level; + +main (argc, argv) + int argc; + char **argv; +{ + register int i; + intmax_t v; + int expok; + + if (setjmp (top_level)) + exit (0); + + for (i = 1; i < argc; i++) + { + v = evalexp (argv[i], 0, &expok); + if (expok == 0) + fprintf (stderr, _("%s: expression error\n"), argv[i]); + else + printf ("'%s' -> %ld\n", argv[i], v); + } + exit (0); +} + +int +builtin_error (format, arg1, arg2, arg3, arg4, arg5) + char *format; +{ + fprintf (stderr, "expr: "); + fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); + fprintf (stderr, "\n"); + return 0; +} + +char * +itos (n) + intmax_t n; +{ + return ("42"); +} + +#endif /* EXPR_TEST */ diff --git a/third_party/bash/externs.h b/third_party/bash/externs.h new file mode 100644 index 000000000..931dba9cd --- /dev/null +++ b/third_party/bash/externs.h @@ -0,0 +1,554 @@ +/* externs.h -- extern function declarations which do not appear in their + own header file. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* Make sure that this is included *after* config.h! */ + +#if !defined (_EXTERNS_H_) +# define _EXTERNS_H_ + +#include "stdc.h" + +/* Functions from expr.c. */ +#define EXP_EXPANDED 0x01 + +extern intmax_t evalexp PARAMS((char *, int, int *)); + +/* Functions from print_cmd.c. */ +#define FUNC_MULTILINE 0x01 +#define FUNC_EXTERNAL 0x02 + +extern char *make_command_string PARAMS((COMMAND *)); +extern char *print_comsub PARAMS((COMMAND *)); +extern char *named_function_string PARAMS((char *, COMMAND *, int)); + +extern void print_command PARAMS((COMMAND *)); +extern void print_simple_command PARAMS((SIMPLE_COM *)); +extern void print_word_list PARAMS((WORD_LIST *, char *)); + +/* debugger support */ +extern void print_for_command_head PARAMS((FOR_COM *)); +#if defined (SELECT_COMMAND) +extern void print_select_command_head PARAMS((SELECT_COM *)); +#endif +extern void print_case_command_head PARAMS((CASE_COM *)); +#if defined (DPAREN_ARITHMETIC) +extern void print_arith_command PARAMS((WORD_LIST *)); +#endif +#if defined (COND_COMMAND) +extern void print_cond_command PARAMS((COND_COM *)); +#endif + +/* set -x support */ +extern void xtrace_init PARAMS((void)); +#ifdef NEED_XTRACE_SET_DECL +extern void xtrace_set PARAMS((int, FILE *)); +#endif +extern void xtrace_fdchk PARAMS((int)); +extern void xtrace_reset PARAMS((void)); +extern char *indirection_level_string PARAMS((void)); +extern void xtrace_print_assignment PARAMS((char *, char *, int, int)); +extern void xtrace_print_word_list PARAMS((WORD_LIST *, int)); +extern void xtrace_print_for_command_head PARAMS((FOR_COM *)); +#if defined (SELECT_COMMAND) +extern void xtrace_print_select_command_head PARAMS((SELECT_COM *)); +#endif +extern void xtrace_print_case_command_head PARAMS((CASE_COM *)); +#if defined (DPAREN_ARITHMETIC) +extern void xtrace_print_arith_cmd PARAMS((WORD_LIST *)); +#endif +#if defined (COND_COMMAND) +extern void xtrace_print_cond_term PARAMS((int, int, WORD_DESC *, char *, char *)); +#endif + +/* Functions from shell.c. */ +extern void exit_shell PARAMS((int)) __attribute__((__noreturn__)); +extern void sh_exit PARAMS((int)) __attribute__((__noreturn__)); +extern void subshell_exit PARAMS((int)) __attribute__((__noreturn__)); +extern void set_exit_status PARAMS((int)); +extern void disable_priv_mode PARAMS((void)); +extern void unbind_args PARAMS((void)); + +#if defined (RESTRICTED_SHELL) +extern int shell_is_restricted PARAMS((char *)); +extern int maybe_make_restricted PARAMS((char *)); +#endif + +extern void unset_bash_input PARAMS((int)); +extern void get_current_user_info PARAMS((void)); + +/* Functions from eval.c. */ +extern int reader_loop PARAMS((void)); +extern int pretty_print_loop PARAMS((void)); +extern int parse_command PARAMS((void)); +extern int read_command PARAMS((void)); + +/* Functions from braces.c. */ +#if defined (BRACE_EXPANSION) +extern char **brace_expand PARAMS((char *)); +#endif + +/* Miscellaneous functions from parse.y */ +extern int yyparse PARAMS((void)); +extern int return_EOF PARAMS((void)); +extern void push_token PARAMS((int)); +extern char *xparse_dolparen PARAMS((char *, char *, int *, int)); +extern COMMAND *parse_string_to_command PARAMS((char *, int)); +extern void reset_parser PARAMS((void)); +extern void reset_readahead_token PARAMS((void)); +extern WORD_LIST *parse_string_to_word_list PARAMS((char *, int, const char *)); + +extern int parser_will_prompt PARAMS((void)); +extern int parser_in_command_position PARAMS((void)); + +extern void free_pushed_string_input PARAMS((void)); + +extern int parser_expanding_alias PARAMS((void)); +extern void parser_save_alias PARAMS((void)); +extern void parser_restore_alias PARAMS((void)); + +extern void clear_shell_input_line PARAMS((void)); + +extern char *decode_prompt_string PARAMS((char *)); + +extern int get_current_prompt_level PARAMS((void)); +extern void set_current_prompt_level PARAMS((int)); + +#if defined (HISTORY) +extern char *history_delimiting_chars PARAMS((const char *)); +#endif + +/* Declarations for functions defined in locale.c */ +extern void set_default_locale PARAMS((void)); +extern void set_default_locale_vars PARAMS((void)); +extern int set_locale_var PARAMS((char *, char *)); +extern int set_lang PARAMS((char *, char *)); +extern void set_default_lang PARAMS((void)); +extern char *get_locale_var PARAMS((char *)); +extern char *localetrans PARAMS((char *, int, int *)); +extern char *mk_msgstr PARAMS((char *, int *)); +extern char *locale_expand PARAMS((char *, int, int, int, int *)); +#ifndef locale_decpoint +extern int locale_decpoint PARAMS((void)); +#endif + +/* Declarations for functions defined in list.c. */ +extern void list_walk PARAMS((GENERIC_LIST *, sh_glist_func_t *)); +extern void wlist_walk PARAMS((WORD_LIST *, sh_icpfunc_t *)); +extern GENERIC_LIST *list_reverse (); +extern int list_length (); +extern GENERIC_LIST *list_append (); +extern GENERIC_LIST *list_remove (); + +/* Declarations for functions defined in stringlib.c */ +extern int find_string_in_alist PARAMS((char *, STRING_INT_ALIST *, int)); +extern char *find_token_in_alist PARAMS((int, STRING_INT_ALIST *, int)); +extern int find_index_in_alist PARAMS((char *, STRING_INT_ALIST *, int)); + +extern char *substring PARAMS((const char *, int, int)); +extern char *strsub PARAMS((char *, char *, char *, int)); +extern char *strcreplace PARAMS((char *, int, const char *, int)); +extern void strip_leading PARAMS((char *)); +extern void strip_trailing PARAMS((char *, int, int)); +extern void xbcopy PARAMS((char *, char *, int)); + +/* Functions from version.c. */ +extern char *shell_version_string PARAMS((void)); +extern void show_shell_version PARAMS((int)); + +/* Functions from the bash library, lib/sh/libsh.a. These should really + go into a separate include file. */ + +/* declarations for functions defined in lib/sh/casemod.c */ +extern char *sh_modcase PARAMS((const char *, char *, int)); + +/* Defines for flags argument to sh_modcase. These need to agree with what's + in lib/sh/casemode.c */ +#define CASE_LOWER 0x0001 +#define CASE_UPPER 0x0002 +#define CASE_CAPITALIZE 0x0004 +#define CASE_UNCAP 0x0008 +#define CASE_TOGGLE 0x0010 +#define CASE_TOGGLEALL 0x0020 +#define CASE_UPFIRST 0x0040 +#define CASE_LOWFIRST 0x0080 + +#define CASE_USEWORDS 0x1000 + +/* declarations for functions defined in lib/sh/clktck.c */ +extern long get_clk_tck PARAMS((void)); + +/* declarations for functions defined in lib/sh/clock.c */ +extern void clock_t_to_secs (); +extern void print_clock_t (); + +/* Declarations for functions defined in lib/sh/dprintf.c */ +#if !defined (HAVE_DPRINTF) +extern void dprintf PARAMS((int, const char *, ...)) __attribute__((__format__ (printf, 2, 3))); +#endif + +/* Declarations for functions defined in lib/sh/fmtulong.c */ +#define FL_PREFIX 0x01 /* add 0x, 0X, or 0 prefix as appropriate */ +#define FL_ADDBASE 0x02 /* add base# prefix to converted value */ +#define FL_HEXUPPER 0x04 /* use uppercase when converting to hex */ +#define FL_UNSIGNED 0x08 /* don't add any sign */ + +extern char *fmtulong PARAMS((unsigned long int, int, char *, size_t, int)); + +/* Declarations for functions defined in lib/sh/fmtulong.c */ +#if defined (HAVE_LONG_LONG_INT) +extern char *fmtullong PARAMS((unsigned long long int, int, char *, size_t, int)); +#endif + +/* Declarations for functions defined in lib/sh/fmtumax.c */ +extern char *fmtumax PARAMS((uintmax_t, int, char *, size_t, int)); + +/* Declarations for functions defined in lib/sh/fnxform.c */ +extern char *fnx_fromfs PARAMS((char *, size_t)); +extern char *fnx_tofs PARAMS((char *, size_t)); + +/* Declarations for functions defined in lib/sh/fpurge.c */ + +#if defined NEED_FPURGE_DECL +#if !HAVE_DECL_FPURGE + +#if HAVE_FPURGE +# define fpurge _bash_fpurge +#endif +extern int fpurge PARAMS((FILE *stream)); + +#endif /* HAVE_DECL_FPURGE */ +#endif /* NEED_FPURGE_DECL */ + +/* Declarations for functions defined in lib/sh/getcwd.c */ +#if !defined (HAVE_GETCWD) +extern char *getcwd PARAMS((char *, size_t)); +#endif + +/* Declarations for functions defined in lib/sh/input_avail.c */ +extern int input_avail PARAMS((int)); + +/* Declarations for functions defined in lib/sh/itos.c */ +extern char *inttostr PARAMS((intmax_t, char *, size_t)); +extern char *itos PARAMS((intmax_t)); +extern char *mitos PARAMS((intmax_t)); +extern char *uinttostr PARAMS((uintmax_t, char *, size_t)); +extern char *uitos PARAMS((uintmax_t)); + +/* declarations for functions defined in lib/sh/makepath.c */ +#define MP_DOTILDE 0x01 +#define MP_DOCWD 0x02 +#define MP_RMDOT 0x04 +#define MP_IGNDOT 0x08 + +extern char *sh_makepath PARAMS((const char *, const char *, int)); + +/* declarations for functions defined in lib/sh/mbscasecmp.c */ +#if !defined (HAVE_MBSCASECMP) +extern char *mbscasecmp PARAMS((const char *, const char *)); +#endif + +/* declarations for functions defined in lib/sh/mbschr.c */ +#if !defined (HAVE_MBSCHR) +extern char *mbschr PARAMS((const char *, int)); +#endif + +/* declarations for functions defined in lib/sh/mbscmp.c */ +#if !defined (HAVE_MBSCMP) +extern char *mbscmp PARAMS((const char *, const char *)); +#endif + +/* declarations for functions defined in lib/sh/netconn.c */ +extern int isnetconn PARAMS((int)); + +/* declarations for functions defined in lib/sh/netopen.c */ +extern int netopen PARAMS((char *)); + +/* Declarations for functions defined in lib/sh/oslib.c */ + +#if !defined (HAVE_DUP2) || defined (DUP2_BROKEN) +extern int dup2 PARAMS((int, int)); +#endif + +#if !defined (HAVE_GETDTABLESIZE) +extern int getdtablesize PARAMS((void)); +#endif /* !HAVE_GETDTABLESIZE */ + +#if !defined (HAVE_GETHOSTNAME) +extern int gethostname PARAMS((char *, int)); +#endif /* !HAVE_GETHOSTNAME */ + +extern int getmaxgroups PARAMS((void)); +extern long getmaxchild PARAMS((void)); + +/* declarations for functions defined in lib/sh/pathcanon.c */ +#define PATH_CHECKDOTDOT 0x0001 +#define PATH_CHECKEXISTS 0x0002 +#define PATH_HARDPATH 0x0004 +#define PATH_NOALLOC 0x0008 + +extern char *sh_canonpath PARAMS((char *, int)); + +/* declarations for functions defined in lib/sh/pathphys.c */ +extern char *sh_physpath PARAMS((char *, int)); +extern char *sh_realpath PARAMS((const char *, char *)); + +/* declarations for functions defined in lib/sh/random.c */ +extern int brand PARAMS((void)); +extern void sbrand PARAMS((unsigned long)); /* set bash random number generator. */ +extern void seedrand PARAMS((void)); /* seed generator randomly */ +extern void seedrand32 PARAMS((void)); +extern u_bits32_t get_urandom32 PARAMS((void)); + +/* declarations for functions defined in lib/sh/setlinebuf.c */ +#ifdef NEED_SH_SETLINEBUF_DECL +extern int sh_setlinebuf PARAMS((FILE *)); +#endif + +/* declarations for functions defined in lib/sh/shaccess.c */ +extern int sh_eaccess PARAMS((const char *, int)); + +/* declarations for functions defined in lib/sh/shmatch.c */ +extern int sh_regmatch PARAMS((const char *, const char *, int)); + +/* defines for flags argument to sh_regmatch. */ +#define SHMAT_SUBEXP 0x001 /* save subexpressions in SH_REMATCH */ +#define SHMAT_PWARN 0x002 /* print a warning message on invalid regexp */ + +/* declarations for functions defined in lib/sh/shmbchar.c */ +extern size_t mbstrlen PARAMS((const char *)); +extern char *mbsmbchar PARAMS((const char *)); +extern int sh_mbsnlen PARAMS((const char *, size_t, int)); + +/* declarations for functions defined in lib/sh/shquote.c */ +extern char *sh_single_quote PARAMS((const char *)); +extern char *sh_double_quote PARAMS((const char *)); +extern char *sh_mkdoublequoted PARAMS((const char *, int, int)); +extern char *sh_un_double_quote PARAMS((char *)); +extern char *sh_backslash_quote PARAMS((char *, const char *, int)); +extern char *sh_backslash_quote_for_double_quotes PARAMS((char *, int)); +extern char *sh_quote_reusable PARAMS((char *, int)); +extern int sh_contains_shell_metas PARAMS((const char *)); +extern int sh_contains_quotes PARAMS((const char *)); + +/* declarations for functions defined in lib/sh/spell.c */ +extern int spname PARAMS((char *, char *)); +extern char *dirspell PARAMS((char *)); + +/* declarations for functions defined in lib/sh/strcasecmp.c */ +#if !defined (HAVE_STRCASECMP) +extern int strncasecmp PARAMS((const char *, const char *, size_t)); +extern int strcasecmp PARAMS((const char *, const char *)); +#endif /* HAVE_STRCASECMP */ + +/* declarations for functions defined in lib/sh/strcasestr.c */ +#if ! HAVE_STRCASESTR +extern char *strcasestr PARAMS((const char *, const char *)); +#endif + +/* declarations for functions defined in lib/sh/strchrnul.c */ +#if ! HAVE_STRCHRNUL +extern char *strchrnul PARAMS((const char *, int)); +#endif + +/* declarations for functions defined in lib/sh/strerror.c */ +#if !defined (HAVE_STRERROR) && !defined (strerror) +extern char *strerror PARAMS((int)); +#endif + +/* declarations for functions defined in lib/sh/strftime.c */ +#if !defined (HAVE_STRFTIME) && defined (NEED_STRFTIME_DECL) +extern size_t strftime PARAMS((char *, size_t, const char *, const struct tm *)); +#endif + +/* declarations for functions and structures defined in lib/sh/stringlist.c */ + +/* This is a general-purpose argv-style array struct. */ +typedef struct _list_of_strings { + char **list; + int list_size; + int list_len; +} STRINGLIST; + +typedef int sh_strlist_map_func_t PARAMS((char *)); + +extern STRINGLIST *strlist_create PARAMS((int)); +extern STRINGLIST *strlist_resize PARAMS((STRINGLIST *, int)); +extern void strlist_flush PARAMS((STRINGLIST *)); +extern void strlist_dispose PARAMS((STRINGLIST *)); +extern int strlist_remove PARAMS((STRINGLIST *, char *)); +extern STRINGLIST *strlist_copy PARAMS((STRINGLIST *)); +extern STRINGLIST *strlist_merge PARAMS((STRINGLIST *, STRINGLIST *)); +extern STRINGLIST *strlist_append PARAMS((STRINGLIST *, STRINGLIST *)); +extern STRINGLIST *strlist_prefix_suffix PARAMS((STRINGLIST *, char *, char *)); +extern void strlist_print PARAMS((STRINGLIST *, char *)); +extern void strlist_walk PARAMS((STRINGLIST *, sh_strlist_map_func_t *)); +extern void strlist_sort PARAMS((STRINGLIST *)); + +/* declarations for functions defined in lib/sh/stringvec.c */ + +extern char **strvec_create PARAMS((int)); +extern char **strvec_resize PARAMS((char **, int)); +extern char **strvec_mcreate PARAMS((int)); +extern char **strvec_mresize PARAMS((char **, int)); +extern void strvec_flush PARAMS((char **)); +extern void strvec_dispose PARAMS((char **)); +extern int strvec_remove PARAMS((char **, char *)); +extern int strvec_len PARAMS((char **)); +extern int strvec_search PARAMS((char **, char *)); +extern char **strvec_copy PARAMS((char **)); +extern int strvec_posixcmp PARAMS((char **, char **)); +extern int strvec_strcmp PARAMS((char **, char **)); +extern void strvec_sort PARAMS((char **, int)); + +extern char **strvec_from_word_list PARAMS((WORD_LIST *, int, int, int *)); +extern WORD_LIST *strvec_to_word_list PARAMS((char **, int, int)); + +/* declarations for functions defined in lib/sh/strnlen.c */ +#if !defined (HAVE_STRNLEN) +extern size_t strnlen PARAMS((const char *, size_t)); +#endif + +/* declarations for functions defined in lib/sh/strpbrk.c */ +#if !defined (HAVE_STRPBRK) +extern char *strpbrk PARAMS((const char *, const char *)); +#endif + +/* declarations for functions defined in lib/sh/strtod.c */ +#if !defined (HAVE_STRTOD) +extern double strtod PARAMS((const char *, char **)); +#endif + +/* declarations for functions defined in lib/sh/strtol.c */ +#if !HAVE_DECL_STRTOL +extern long strtol PARAMS((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strtoll.c */ +#if defined (HAVE_LONG_LONG_INT) && !HAVE_DECL_STRTOLL +extern long long strtoll PARAMS((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strtoul.c */ +#if !HAVE_DECL_STRTOUL +extern unsigned long strtoul PARAMS((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strtoull.c */ +#if defined (HAVE_UNSIGNED_LONG_LONG_INT) && !HAVE_DECL_STRTOULL +extern unsigned long long strtoull PARAMS((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strimax.c */ +#if !HAVE_DECL_STRTOIMAX +extern intmax_t strtoimax PARAMS((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strumax.c */ +#if !HAVE_DECL_STRTOUMAX +extern uintmax_t strtoumax PARAMS((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strtrans.c */ +extern char *ansicstr PARAMS((char *, int, int, int *, int *)); +extern char *ansic_quote PARAMS((char *, int, int *)); +extern int ansic_shouldquote PARAMS((const char *)); +extern char *ansiexpand PARAMS((char *, int, int, int *)); + +/* declarations for functions defined in lib/sh/strvis.c */ +extern int sh_charvis PARAMS((const char *, size_t *, size_t, char *, size_t *)); +extern char *sh_strvis PARAMS((const char *)); + +/* declarations for functions defined in lib/sh/timeval.c. No prototypes + so we don't have to count on having a definition of struct timeval in + scope when this file is included. */ +extern void timeval_to_secs (); +extern void print_timeval (); + +/* declarations for functions defined in lib/sh/tmpfile.c */ +#define MT_USETMPDIR 0x0001 +#define MT_READWRITE 0x0002 +#define MT_USERANDOM 0x0004 +#define MT_TEMPLATE 0x0008 + +extern char *sh_mktmpname PARAMS((char *, int)); +extern int sh_mktmpfd PARAMS((char *, int, char **)); +/* extern FILE *sh_mktmpfp PARAMS((char *, int, char **)); */ +extern char *sh_mktmpdir PARAMS((char *, int)); + +/* declarations for functions defined in lib/sh/uconvert.c */ +extern int uconvert PARAMS((char *, long *, long *, char **)); + +/* declarations for functions defined in lib/sh/ufuncs.c */ +extern unsigned int falarm PARAMS((unsigned int, unsigned int)); +extern unsigned int fsleep PARAMS((unsigned int, unsigned int)); + +/* declarations for functions defined in lib/sh/unicode.c */ +extern int u32cconv PARAMS((unsigned long, char *)); +extern void u32reset PARAMS((void)); + +/* declarations for functions defined in lib/sh/utf8.c */ +extern char *utf8_mbschr PARAMS((const char *, int)); +extern int utf8_mbscmp PARAMS((const char *, const char *)); +extern char *utf8_mbsmbchar PARAMS((const char *)); +extern int utf8_mbsnlen PARAMS((const char *, size_t, int)); +extern int utf8_mblen PARAMS((const char *, size_t)); +extern size_t utf8_mbstrlen PARAMS((const char *)); + +/* declarations for functions defined in lib/sh/wcsnwidth.c */ +#if defined (HANDLE_MULTIBYTE) +extern int wcsnwidth PARAMS((const wchar_t *, size_t, int)); +#endif + +/* declarations for functions defined in lib/sh/winsize.c */ +extern void get_new_window_size PARAMS((int, int *, int *)); + +/* declarations for functions defined in lib/sh/zcatfd.c */ +extern int zcatfd PARAMS((int, int, char *)); + +/* declarations for functions defined in lib/sh/zgetline.c */ +extern ssize_t zgetline PARAMS((int, char **, size_t *, int, int)); + +/* declarations for functions defined in lib/sh/zmapfd.c */ +extern int zmapfd PARAMS((int, char **, char *)); + +/* declarations for functions defined in lib/sh/zread.c */ +extern ssize_t zread PARAMS((int, char *, size_t)); +extern ssize_t zreadretry PARAMS((int, char *, size_t)); +extern ssize_t zreadintr PARAMS((int, char *, size_t)); +extern ssize_t zreadc PARAMS((int, char *)); +extern ssize_t zreadcintr PARAMS((int, char *)); +extern ssize_t zreadn PARAMS((int, char *, size_t)); +extern void zreset PARAMS((void)); +extern void zsyncfd PARAMS((int)); + +/* declarations for functions defined in lib/sh/zwrite.c */ +extern int zwrite PARAMS((int, char *, size_t)); + +/* declarations for functions defined in lib/glob/gmisc.c */ +extern int match_pattern_char PARAMS((char *, char *, int)); +extern int umatchlen PARAMS((char *, size_t)); + +#if defined (HANDLE_MULTIBYTE) +extern int match_pattern_wchar PARAMS((wchar_t *, wchar_t *, int)); +extern int wmatchlen PARAMS((wchar_t *, size_t)); +#endif + +#endif /* _EXTERNS_H_ */ diff --git a/third_party/bash/filecntl.h b/third_party/bash/filecntl.h new file mode 100644 index 000000000..9bcbab8d5 --- /dev/null +++ b/third_party/bash/filecntl.h @@ -0,0 +1,53 @@ +/* filecntl.h - Definitions to set file descriptors to close-on-exec. */ + +/* Copyright (C) 1993 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_FILECNTL_H_) +#define _FILECNTL_H_ + +#include + +/* Definitions to set file descriptors to close-on-exec, the Posix way. */ +#if !defined (FD_CLOEXEC) +#define FD_CLOEXEC 1 +#endif + +#define FD_NCLOEXEC 0 + +#define SET_CLOSE_ON_EXEC(fd) (fcntl ((fd), F_SETFD, FD_CLOEXEC)) +#define SET_OPEN_ON_EXEC(fd) (fcntl ((fd), F_SETFD, FD_NCLOEXEC)) + +/* How to open a file in non-blocking mode, the Posix.1 way. */ +#if !defined (O_NONBLOCK) +# if defined (O_NDELAY) +# define O_NONBLOCK O_NDELAY +# else +# define O_NONBLOCK 0 +# endif +#endif + +/* Make sure O_BINARY and O_TEXT are defined to avoid Windows-specific code. */ +#if !defined (O_BINARY) +# define O_BINARY 0 +#endif +#if !defined (O_TEXT) +# define O_TEXT 0 +#endif + +#endif /* ! _FILECNTL_H_ */ diff --git a/third_party/bash/findcmd.c b/third_party/bash/findcmd.c new file mode 100644 index 000000000..7c3017820 --- /dev/null +++ b/third_party/bash/findcmd.c @@ -0,0 +1,696 @@ +/* findcmd.c -- Functions to search for commands by name. */ + +/* Copyright (C) 1997-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include +#include "chartypes.h" +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "filecntl.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif +#include + +#include "bashansi.h" + +#include "memalloc.h" +#include "shell.h" +#include "execute_cmd.h" +#include "flags.h" +#include "hashlib.h" +#include "pathexp.h" +#include "hashcmd.h" +#include "findcmd.h" /* matching prototypes and declarations */ + +#include "strmatch.h" + +#if !defined (errno) +extern int errno; +#endif + +/* Static functions defined and used in this file. */ +static char *_find_user_command_internal PARAMS((const char *, int)); +static char *find_user_command_internal PARAMS((const char *, int)); +static char *find_user_command_in_path PARAMS((const char *, char *, int, int *)); +static char *find_in_path_element PARAMS((const char *, char *, int, int, struct stat *, int *)); +static char *find_absolute_program PARAMS((const char *, int)); + +static char *get_next_path_element PARAMS((char *, int *)); + +/* The file name which we would try to execute, except that it isn't + possible to execute it. This is the first file that matches the + name that we are looking for while we are searching $PATH for a + suitable one to execute. If we cannot find a suitable executable + file, then we use this one. */ +static char *file_to_lose_on; + +/* Non-zero if we should stat every command found in the hash table to + make sure it still exists. */ +int check_hashed_filenames = CHECKHASH_DEFAULT; + +/* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command () + encounters a `.' as the directory pathname while scanning the + list of possible pathnames; i.e., if `.' comes before the directory + containing the file of interest. */ +int dot_found_in_search = 0; + +/* Set up EXECIGNORE; a blacklist of patterns that executable files should not + match. */ +static struct ignorevar execignore = +{ + "EXECIGNORE", + NULL, + 0, + NULL, + NULL +}; + +void +setup_exec_ignore (varname) + char *varname; +{ + setup_ignore_patterns (&execignore); +} + +static int +exec_name_should_ignore (name) + const char *name; +{ + struct ign *p; + + for (p = execignore.ignores; p && p->val; p++) + if (strmatch (p->val, (char *)name, FNMATCH_EXTFLAG|FNM_CASEFOLD) != FNM_NOMATCH) + return 1; + return 0; +} + +/* Return some flags based on information about this file. + The EXISTS bit is non-zero if the file is found. + The EXECABLE bit is non-zero the file is executable. + Zero is returned if the file is not found. */ +int +file_status (name) + const char *name; +{ + struct stat finfo; + int r; + + /* Determine whether this file exists or not. */ + if (stat (name, &finfo) < 0) + return (0); + + /* If the file is a directory, then it is not "executable" in the + sense of the shell. */ + if (S_ISDIR (finfo.st_mode)) + return (FS_EXISTS|FS_DIRECTORY); + + r = FS_EXISTS; + +#if defined (HAVE_EACCESS) + /* Use eaccess(2) if we have it to take things like ACLs and other + file access mechanisms into account. eaccess uses the effective + user and group IDs, not the real ones. We could use sh_eaccess, + but we don't want any special treatment for /dev/fd. */ + if (exec_name_should_ignore (name) == 0 && eaccess (name, X_OK) == 0) + r |= FS_EXECABLE; + if (eaccess (name, R_OK) == 0) + r |= FS_READABLE; + + return r; +#elif defined (AFS) + /* We have to use access(2) to determine access because AFS does not + support Unix file system semantics. This may produce wrong + answers for non-AFS files when ruid != euid. I hate AFS. */ + if (exec_name_should_ignore (name) == 0 && access (name, X_OK) == 0) + r |= FS_EXECABLE; + if (access (name, R_OK) == 0) + r |= FS_READABLE; + + return r; +#else /* !HAVE_EACCESS && !AFS */ + + /* Find out if the file is actually executable. By definition, the + only other criteria is that the file has an execute bit set that + we can use. The same with whether or not a file is readable. */ + + /* Root only requires execute permission for any of owner, group or + others to be able to exec a file, and can read any file. */ + if (current_user.euid == (uid_t)0) + { + r |= FS_READABLE; + if (exec_name_should_ignore (name) == 0 && (finfo.st_mode & S_IXUGO)) + r |= FS_EXECABLE; + return r; + } + + /* If we are the owner of the file, the owner bits apply. */ + if (current_user.euid == finfo.st_uid) + { + if (exec_name_should_ignore (name) == 0 && (finfo.st_mode & S_IXUSR)) + r |= FS_EXECABLE; + if (finfo.st_mode & S_IRUSR) + r |= FS_READABLE; + } + + /* If we are in the owning group, the group permissions apply. */ + else if (group_member (finfo.st_gid)) + { + if (exec_name_should_ignore (name) == 0 && (finfo.st_mode & S_IXGRP)) + r |= FS_EXECABLE; + if (finfo.st_mode & S_IRGRP) + r |= FS_READABLE; + } + + /* Else we check whether `others' have permission to execute the file */ + else + { + if (exec_name_should_ignore (name) == 0 && finfo.st_mode & S_IXOTH) + r |= FS_EXECABLE; + if (finfo.st_mode & S_IROTH) + r |= FS_READABLE; + } + + return r; +#endif /* !AFS */ +} + +/* Return non-zero if FILE exists and is executable. + Note that this function is the definition of what an + executable file is; do not change this unless YOU know + what an executable file is. */ +int +executable_file (file) + const char *file; +{ + int s; + + s = file_status (file); +#if defined (EISDIR) + if (s & FS_DIRECTORY) + errno = EISDIR; /* let's see if we can improve error messages */ +#endif + return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0)); +} + +int +is_directory (file) + const char *file; +{ + return (file_status (file) & FS_DIRECTORY); +} + +int +executable_or_directory (file) + const char *file; +{ + int s; + + s = file_status (file); + return ((s & FS_EXECABLE) || (s & FS_DIRECTORY)); +} + +/* Locate the executable file referenced by NAME, searching along + the contents of the shell PATH variable. Return a new string + which is the full pathname to the file, or NULL if the file + couldn't be found. If a file is found that isn't executable, + and that is the only match, then return that. */ +char * +find_user_command (name) + const char *name; +{ + return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS)); +} + +/* Locate the file referenced by NAME, searching along the contents + of the shell PATH variable. Return a new string which is the full + pathname to the file, or NULL if the file couldn't be found. This + returns the first readable file found; designed to be used to look + for shell scripts or files to source. */ +char * +find_path_file (name) + const char *name; +{ + return (find_user_command_internal (name, FS_READABLE)); +} + +static char * +_find_user_command_internal (name, flags) + const char *name; + int flags; +{ + char *path_list, *cmd; + SHELL_VAR *var; + + /* Search for the value of PATH in both the temporary environments and + in the regular list of variables. */ + if (var = find_variable_tempenv ("PATH")) /* XXX could be array? */ + path_list = value_cell (var); + else + path_list = (char *)NULL; + + if (path_list == 0 || *path_list == '\0') + return (savestring (name)); + + cmd = find_user_command_in_path (name, path_list, flags, (int *)0); + + return (cmd); +} + +static char * +find_user_command_internal (name, flags) + const char *name; + int flags; +{ +#ifdef __WIN32__ + char *res, *dotexe; + + dotexe = (char *)xmalloc (strlen (name) + 5); + strcpy (dotexe, name); + strcat (dotexe, ".exe"); + res = _find_user_command_internal (dotexe, flags); + free (dotexe); + if (res == 0) + res = _find_user_command_internal (name, flags); + return res; +#else + return (_find_user_command_internal (name, flags)); +#endif +} + +/* Return the next element from PATH_LIST, a colon separated list of + paths. PATH_INDEX_POINTER is the address of an index into PATH_LIST; + the index is modified by this function. + Return the next element of PATH_LIST or NULL if there are no more. */ +static char * +get_next_path_element (path_list, path_index_pointer) + char *path_list; + int *path_index_pointer; +{ + char *path; + + path = extract_colon_unit (path_list, path_index_pointer); + + if (path == 0) + return (path); + + if (*path == '\0') + { + free (path); + path = savestring ("."); + } + + return (path); +} + +/* Look for PATHNAME in $PATH. Returns either the hashed command + corresponding to PATHNAME or the first instance of PATHNAME found + in $PATH. If (FLAGS&CMDSRCH_HASH) is non-zero, insert the instance of + PATHNAME found in $PATH into the command hash table. + If (FLAGS&CMDSRCH_STDPATH) is non-zero, we are running in a `command -p' + environment and should use the Posix standard path. + Returns a newly-allocated string. */ +char * +search_for_command (pathname, flags) + const char *pathname; + int flags; +{ + char *hashed_file, *command, *path_list; + int temp_path, st; + SHELL_VAR *path; + + hashed_file = command = (char *)NULL; + + /* If PATH is in the temporary environment for this command, don't use the + hash table to search for the full pathname. */ + path = find_variable_tempenv ("PATH"); + temp_path = path && tempvar_p (path); + + /* Don't waste time trying to find hashed data for a pathname + that is already completely specified or if we're using a command- + specific value for PATH. */ + if (temp_path == 0 && (flags & CMDSRCH_STDPATH) == 0 && absolute_program (pathname) == 0) + hashed_file = phash_search (pathname); + + /* If a command found in the hash table no longer exists, we need to + look for it in $PATH. Thank you Posix.2. This forces us to stat + every command found in the hash table. */ + + if (hashed_file && (posixly_correct || check_hashed_filenames)) + { + st = file_status (hashed_file); + if ((st & (FS_EXISTS|FS_EXECABLE)) != (FS_EXISTS|FS_EXECABLE)) + { + phash_remove (pathname); + free (hashed_file); + hashed_file = (char *)NULL; + } + } + + if (hashed_file) + command = hashed_file; + else if (absolute_program (pathname)) + /* A command containing a slash is not looked up in PATH or saved in + the hash table. */ + command = savestring (pathname); + else + { + if (flags & CMDSRCH_STDPATH) + path_list = conf_standard_path (); + else if (temp_path || path) + path_list = value_cell (path); + else + path_list = 0; + + command = find_user_command_in_path (pathname, path_list, FS_EXEC_PREFERRED|FS_NODIRS, &st); + + if (command && hashing_enabled && temp_path == 0 && (flags & CMDSRCH_HASH)) + { + /* If we found the full pathname the same as the command name, the + command probably doesn't exist. Don't put it into the hash + table unless it's an executable file in the current directory. */ + if (STREQ (command, pathname)) + { + if (st & FS_EXECABLE) + phash_insert ((char *)pathname, command, dot_found_in_search, 1); + } + /* If we're in posix mode, don't add files without the execute bit + to the hash table. */ + else if (posixly_correct || check_hashed_filenames) + { + if (st & FS_EXECABLE) + phash_insert ((char *)pathname, command, dot_found_in_search, 1); + } + else + phash_insert ((char *)pathname, command, dot_found_in_search, 1); + } + + if (flags & CMDSRCH_STDPATH) + free (path_list); + } + + return (command); +} + +char * +user_command_matches (name, flags, state) + const char *name; + int flags, state; +{ + register int i; + int path_index, name_len; + char *path_list, *path_element, *match; + struct stat dotinfo; + static char **match_list = NULL; + static int match_list_size = 0; + static int match_index = 0; + + if (state == 0) + { + /* Create the list of matches. */ + if (match_list == 0) + { + match_list_size = 5; + match_list = strvec_create (match_list_size); + } + + /* Clear out the old match list. */ + for (i = 0; i < match_list_size; i++) + match_list[i] = 0; + + /* We haven't found any files yet. */ + match_index = 0; + + if (absolute_program (name)) + { + match_list[0] = find_absolute_program (name, flags); + match_list[1] = (char *)NULL; + path_list = (char *)NULL; + } + else + { + name_len = strlen (name); + file_to_lose_on = (char *)NULL; + dot_found_in_search = 0; + if (stat (".", &dotinfo) < 0) + dotinfo.st_dev = dotinfo.st_ino = 0; /* so same_file won't match */ + path_list = get_string_value ("PATH"); + path_index = 0; + } + + while (path_list && path_list[path_index]) + { + path_element = get_next_path_element (path_list, &path_index); + + if (path_element == 0) + break; + + match = find_in_path_element (name, path_element, flags, name_len, &dotinfo, (int *)0); + free (path_element); + + if (match == 0) + continue; + + if (match_index + 1 == match_list_size) + { + match_list_size += 10; + match_list = strvec_resize (match_list, (match_list_size + 1)); + } + + match_list[match_index++] = match; + match_list[match_index] = (char *)NULL; + FREE (file_to_lose_on); + file_to_lose_on = (char *)NULL; + } + + /* We haven't returned any strings yet. */ + match_index = 0; + } + + match = match_list[match_index]; + + if (match) + match_index++; + + return (match); +} + +static char * +find_absolute_program (name, flags) + const char *name; + int flags; +{ + int st; + + st = file_status (name); + + /* If the file doesn't exist, quit now. */ + if ((st & FS_EXISTS) == 0) + return ((char *)NULL); + + /* If we only care about whether the file exists or not, return + this filename. Otherwise, maybe we care about whether this + file is executable. If it is, and that is what we want, return it. */ + if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE))) + return (savestring (name)); + + return (NULL); +} + +static char * +find_in_path_element (name, path, flags, name_len, dotinfop, rflagsp) + const char *name; + char *path; + int flags, name_len; + struct stat *dotinfop; + int *rflagsp; +{ + int status; + char *full_path, *xpath; + + xpath = (posixly_correct == 0 && *path == '~') ? bash_tilde_expand (path, 0) : path; + + /* Remember the location of "." in the path, in all its forms + (as long as they begin with a `.', e.g. `./.') */ + /* We could also do this or something similar for all relative pathnames + found while searching PATH. */ + if (dot_found_in_search == 0 && *xpath == '.') + dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL); + + full_path = sh_makepath (xpath, name, 0); + + status = file_status (full_path); + + if (xpath != path) + free (xpath); + + if (rflagsp) + *rflagsp = status; + + if ((status & FS_EXISTS) == 0) + { + free (full_path); + return ((char *)NULL); + } + + /* The file exists. If the caller simply wants the first file, here it is. */ + if (flags & FS_EXISTS) + return (full_path); + + /* If we have a readable file, and the caller wants a readable file, this + is it. */ + if ((flags & FS_READABLE) && (status & FS_READABLE)) + return (full_path); + + /* If the file is executable, then it satisfies the cases of + EXEC_ONLY and EXEC_PREFERRED. Return this file unconditionally. */ + if ((status & FS_EXECABLE) && (flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) && + (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0))) + { + FREE (file_to_lose_on); + file_to_lose_on = (char *)NULL; + return (full_path); + } + + /* The file is not executable, but it does exist. If we prefer + an executable, then remember this one if it is the first one + we have found. */ + if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0 && exec_name_should_ignore (full_path) == 0) + file_to_lose_on = savestring (full_path); + + /* If we want only executable files, or we don't want directories and + this file is a directory, or we want a readable file and this file + isn't readable, fail. */ + if ((flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) || + ((flags & FS_NODIRS) && (status & FS_DIRECTORY)) || + ((flags & FS_READABLE) && (status & FS_READABLE) == 0)) + { + free (full_path); + return ((char *)NULL); + } + else + return (full_path); +} + +/* This does the dirty work for find_user_command_internal () and + user_command_matches (). + NAME is the name of the file to search for. + PATH_LIST is a colon separated list of directories to search. + FLAGS contains bit fields which control the files which are eligible. + Some values are: + FS_EXEC_ONLY: The file must be an executable to be found. + FS_EXEC_PREFERRED: If we can't find an executable, then the + the first file matching NAME will do. + FS_EXISTS: The first file found will do. + FS_NODIRS: Don't find any directories. +*/ +static char * +find_user_command_in_path (name, path_list, flags, rflagsp) + const char *name; + char *path_list; + int flags, *rflagsp; +{ + char *full_path, *path; + int path_index, name_len, rflags; + struct stat dotinfo; + + /* We haven't started looking, so we certainly haven't seen + a `.' as the directory path yet. */ + dot_found_in_search = 0; + + if (rflagsp) + *rflagsp = 0; + + if (absolute_program (name)) + { + full_path = find_absolute_program (name, flags); + return (full_path); + } + + if (path_list == 0 || *path_list == '\0') + return (savestring (name)); /* XXX */ + + file_to_lose_on = (char *)NULL; + name_len = strlen (name); + if (stat (".", &dotinfo) < 0) + dotinfo.st_dev = dotinfo.st_ino = 0; + path_index = 0; + + while (path_list[path_index]) + { + /* Allow the user to interrupt out of a lengthy path search. */ + QUIT; + + path = get_next_path_element (path_list, &path_index); + if (path == 0) + break; + + /* Side effects: sets dot_found_in_search, possibly sets + file_to_lose_on. */ + full_path = find_in_path_element (name, path, flags, name_len, &dotinfo, &rflags); + free (path); + + /* We use the file status flag bits to check whether full_path is a + directory, which we reject here. */ + if (full_path && (rflags & FS_DIRECTORY)) + { + free (full_path); + continue; + } + + if (full_path) + { + if (rflagsp) + *rflagsp = rflags; + FREE (file_to_lose_on); + return (full_path); + } + } + + /* We didn't find exactly what the user was looking for. Return + the contents of FILE_TO_LOSE_ON which is NULL when the search + required an executable, or non-NULL if a file was found and the + search would accept a non-executable as a last resort. If the + caller specified FS_NODIRS, and file_to_lose_on is a directory, + return NULL. */ + if (file_to_lose_on && (flags & FS_NODIRS) && file_isdir (file_to_lose_on)) + { + free (file_to_lose_on); + file_to_lose_on = (char *)NULL; + } + + return (file_to_lose_on); +} + +/* External interface to find a command given a $PATH. Separate from + find_user_command_in_path to allow future customization. */ +char * +find_in_path (name, path_list, flags) + const char *name; + char *path_list; + int flags; +{ + return (find_user_command_in_path (name, path_list, flags, (int *)0)); +} diff --git a/third_party/bash/findcmd.h b/third_party/bash/findcmd.h new file mode 100644 index 000000000..bf457814e --- /dev/null +++ b/third_party/bash/findcmd.h @@ -0,0 +1,47 @@ +/* findcmd.h - functions from findcmd.c. */ + +/* Copyright (C) 1997-2015,2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_FINDCMD_H_) +#define _FINDCMD_H_ + +#include "stdc.h" + +/* Flags for search_for_command */ +#define CMDSRCH_HASH 0x01 +#define CMDSRCH_STDPATH 0x02 +#define CMDSRCH_TEMPENV 0x04 + +extern int file_status PARAMS((const char *)); +extern int executable_file PARAMS((const char *)); +extern int is_directory PARAMS((const char *)); +extern int executable_or_directory PARAMS((const char *)); +extern char *find_user_command PARAMS((const char *)); +extern char *find_in_path PARAMS((const char *, char *, int)); +extern char *find_path_file PARAMS((const char *)); +extern char *search_for_command PARAMS((const char *, int)); +extern char *user_command_matches PARAMS((const char *, int, int)); +extern void setup_exec_ignore PARAMS((char *)); + +extern int dot_found_in_search; + +/* variables managed via shopt */ +extern int check_hashed_filenames; + +#endif /* _FINDCMD_H_ */ diff --git a/third_party/bash/flags.c b/third_party/bash/flags.c new file mode 100644 index 000000000..30f6c13d4 --- /dev/null +++ b/third_party/bash/flags.c @@ -0,0 +1,385 @@ +/* flags.c -- Everything about flags except the `set' command. That + is in builtins.c */ + +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "shell.h" +#include "execute_cmd.h" +#include "flags.h" + +#if defined (BANG_HISTORY) +# include "bashhist.h" +#endif + +#if defined (JOB_CONTROL) +extern int set_job_control PARAMS((int)); +#endif + +/* **************************************************************** */ +/* */ +/* The Standard sh Flags. */ +/* */ +/* **************************************************************** */ + +/* Non-zero means automatically mark variables which are modified or created + as auto export variables. */ +int mark_modified_vars = 0; + +/* Non-zero causes asynchronous job notification. Otherwise, job state + notification only takes place just before a primary prompt is printed. */ +int asynchronous_notification = 0; + +/* Non-zero means exit immediately if a command exits with a non-zero + exit status. The first is what controls set -e; the second is what + bash uses internally. */ +int errexit_flag = 0; +int exit_immediately_on_error = 0; + +/* Non-zero means disable filename globbing. */ +int disallow_filename_globbing = 0; + +/* Non-zero means that all keyword arguments are placed into the environment + for a command, not just those that appear on the line before the command + name. */ +int place_keywords_in_env = 0; + +/* Non-zero means read commands, but don't execute them. This is useful + for debugging shell scripts that should do something hairy and possibly + destructive. */ +int read_but_dont_execute = 0; + +/* Non-zero means end of file is after one command. */ +int just_one_command = 0; + +/* Non-zero means don't overwrite existing files while doing redirections. */ +int noclobber = 0; + +/* Non-zero means trying to get the value of $i where $i is undefined + causes an error, instead of a null substitution. */ +int unbound_vars_is_error = 0; + +/* Non-zero means type out input lines after you read them. */ +int echo_input_at_read = 0; +int verbose_flag = 0; + +/* Non-zero means type out the command definition after reading, but + before executing. */ +int echo_command_at_execute = 0; + +/* Non-zero means turn on the job control features. */ +int jobs_m_flag = 0; + +/* Non-zero means this shell is interactive, even if running under a + pipe. */ +int forced_interactive = 0; + +/* By default, follow the symbolic links as if they were real directories + while hacking the `cd' command. This means that `cd ..' moves up in + the string of symbolic links that make up the current directory, instead + of the absolute directory. The shell variable `nolinks' also controls + this flag. */ +int no_symbolic_links = 0; + +/* **************************************************************** */ +/* */ +/* Non-Standard Flags Follow Here. */ +/* */ +/* **************************************************************** */ + +#if 0 +/* Non-zero means do lexical scoping in the body of a FOR command. */ +int lexical_scoping = 0; +#endif + +/* Non-zero means look up and remember command names in a hash table, */ +int hashing_enabled = 1; + +#if defined (BANG_HISTORY) +/* Non-zero means that we are doing history expansion. The default. + This means !22 gets the 22nd line of history. */ +int history_expansion = HISTEXPAND_DEFAULT; +int histexp_flag = 0; +#endif /* BANG_HISTORY */ + +/* Non-zero means that we allow comments to appear in interactive commands. */ +int interactive_comments = 1; + +#if defined (RESTRICTED_SHELL) +/* Non-zero means that this shell is `restricted'. A restricted shell + disallows: changing directories, command or path names containing `/', + unsetting or resetting the values of $PATH and $SHELL, and any type of + output redirection. */ +int restricted = 0; /* currently restricted */ +int restricted_shell = 0; /* shell was started in restricted mode. */ +#endif /* RESTRICTED_SHELL */ + +/* Non-zero means that this shell is running in `privileged' mode. This + is required if the shell is to run setuid. If the `-p' option is + not supplied at startup, and the real and effective uids or gids + differ, disable_priv_mode is called to relinquish setuid status. */ +int privileged_mode = 0; + +#if defined (BRACE_EXPANSION) +/* Zero means to disable brace expansion: foo{a,b} -> fooa foob */ +int brace_expansion = 1; +#endif + +/* Non-zero means that shell functions inherit the DEBUG trap. */ +int function_trace_mode = 0; + +/* Non-zero means that shell functions inherit the ERR trap. */ +int error_trace_mode = 0; + +/* Non-zero means that the rightmost non-zero exit status in a pipeline + is the exit status of the entire pipeline. If each processes exits + with a 0 status, the status of the pipeline is 0. */ +int pipefail_opt = 0; + +/* **************************************************************** */ +/* */ +/* The Flags ALIST. */ +/* */ +/* **************************************************************** */ + +const struct flags_alist shell_flags[] = { + /* Standard sh flags. */ + { 'a', &mark_modified_vars }, +#if defined (JOB_CONTROL) + { 'b', &asynchronous_notification }, +#endif /* JOB_CONTROL */ + { 'e', &errexit_flag }, + { 'f', &disallow_filename_globbing }, + { 'h', &hashing_enabled }, + { 'i', &forced_interactive }, + { 'k', &place_keywords_in_env }, +#if defined (JOB_CONTROL) + { 'm', &jobs_m_flag }, +#endif /* JOB_CONTROL */ + { 'n', &read_but_dont_execute }, + { 'p', &privileged_mode }, +#if defined (RESTRICTED_SHELL) + { 'r', &restricted }, +#endif /* RESTRICTED_SHELL */ + { 't', &just_one_command }, + { 'u', &unbound_vars_is_error }, + { 'v', &verbose_flag }, + { 'x', &echo_command_at_execute }, + + /* New flags that control non-standard things. */ +#if 0 + { 'l', &lexical_scoping }, +#endif +#if defined (BRACE_EXPANSION) + { 'B', &brace_expansion }, +#endif + { 'C', &noclobber }, + { 'E', &error_trace_mode }, +#if defined (BANG_HISTORY) + { 'H', &histexp_flag }, +#endif /* BANG_HISTORY */ + { 'P', &no_symbolic_links }, + { 'T', &function_trace_mode }, + {0, (int *)NULL} +}; + +#define NUM_SHELL_FLAGS (sizeof (shell_flags) / sizeof (struct flags_alist)) + +char optflags[NUM_SHELL_FLAGS+4] = { '+' }; + +int * +find_flag (name) + int name; +{ + int i; + for (i = 0; shell_flags[i].name; i++) + { + if (shell_flags[i].name == name) + return (shell_flags[i].value); + } + return (FLAG_UNKNOWN); +} + +/* Change the state of a flag, and return it's original value, or return + FLAG_ERROR if there is no flag FLAG. ON_OR_OFF must be either + FLAG_ON or FLAG_OFF. */ +int +change_flag (flag, on_or_off) + int flag; + int on_or_off; +{ + int *value, old_value; + +#if defined (RESTRICTED_SHELL) + /* Don't allow "set +r" in a shell which is `restricted'. */ + if (restricted && flag == 'r' && on_or_off == FLAG_OFF) + return (FLAG_ERROR); +#endif /* RESTRICTED_SHELL */ + + value = find_flag (flag); + + if ((value == (int *)FLAG_UNKNOWN) || (on_or_off != FLAG_ON && on_or_off != FLAG_OFF)) + return (FLAG_ERROR); + + old_value = *value; + *value = (on_or_off == FLAG_ON) ? 1 : 0; + + /* Special cases for a few flags. */ + switch (flag) + { +#if defined (BANG_HISTORY) + case 'H': + history_expansion = histexp_flag; + if (on_or_off == FLAG_ON) + bash_initialize_history (); + break; +#endif + +#if defined (JOB_CONTROL) + case 'm': + set_job_control (on_or_off == FLAG_ON); + break; +#endif /* JOB_CONTROL */ + + case 'e': + if (builtin_ignoring_errexit == 0) + exit_immediately_on_error = errexit_flag; + break; + + case 'n': + if (interactive_shell) + read_but_dont_execute = 0; + break; + + case 'p': + if (on_or_off == FLAG_OFF) + disable_priv_mode (); + break; + +#if defined (RESTRICTED_SHELL) + case 'r': + if (on_or_off == FLAG_ON && shell_initialized) + maybe_make_restricted (shell_name); + break; +#endif + + case 'v': + echo_input_at_read = verbose_flag; + break; + } + + return (old_value); +} + +/* Return a string which is the names of all the currently + set shell flags. */ +char * +which_set_flags () +{ + char *temp; + int i, string_index; + + temp = (char *)xmalloc (1 + NUM_SHELL_FLAGS + read_from_stdin + want_pending_command); + for (i = string_index = 0; shell_flags[i].name; i++) + if (*(shell_flags[i].value)) + temp[string_index++] = shell_flags[i].name; + + if (want_pending_command) + temp[string_index++] = 'c'; + if (read_from_stdin) + temp[string_index++] = 's'; + + temp[string_index] = '\0'; + return (temp); +} + +char * +get_current_flags () +{ + char *temp; + int i; + + temp = (char *)xmalloc (1 + NUM_SHELL_FLAGS); + for (i = 0; shell_flags[i].name; i++) + temp[i] = *(shell_flags[i].value); + temp[i] = '\0'; + return (temp); +} + +void +set_current_flags (bitmap) + const char *bitmap; +{ + int i; + + if (bitmap == 0) + return; + for (i = 0; shell_flags[i].name; i++) + *(shell_flags[i].value) = bitmap[i]; +} + +void +reset_shell_flags () +{ + mark_modified_vars = disallow_filename_globbing = 0; + place_keywords_in_env = read_but_dont_execute = just_one_command = 0; + noclobber = unbound_vars_is_error = 0; + echo_command_at_execute = jobs_m_flag = forced_interactive = 0; + no_symbolic_links = 0; + privileged_mode = pipefail_opt = 0; + + error_trace_mode = function_trace_mode = 0; + + exit_immediately_on_error = errexit_flag = 0; + echo_input_at_read = verbose_flag = 0; + + hashing_enabled = interactive_comments = 1; + +#if defined (JOB_CONTROL) + asynchronous_notification = 0; +#endif + +#if defined (BANG_HISTORY) + histexp_flag = 0; +#endif + +#if defined (BRACE_EXPANSION) + brace_expansion = 1; +#endif + +#if defined (RESTRICTED_SHELL) + restricted = 0; +#endif +} + +void +initialize_flags () +{ + register int i; + + for (i = 0; shell_flags[i].name; i++) + optflags[i+1] = shell_flags[i].name; + optflags[++i] = 'o'; + optflags[++i] = ';'; + optflags[i+1] = '\0'; +} diff --git a/third_party/bash/flags.h b/third_party/bash/flags.h new file mode 100644 index 000000000..a3b5daa94 --- /dev/null +++ b/third_party/bash/flags.h @@ -0,0 +1,87 @@ +/* flags.h -- a list of all the flags that the shell knows about. You add + a flag to this program by adding the name here, and in flags.c. */ + +/* Copyright (C) 1993-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_FLAGS_H_) +#define _FLAGS_H_ + +#include "stdc.h" + +/* Welcome to the world of Un*x, where everything is slightly backwards. */ +#define FLAG_ON '-' +#define FLAG_OFF '+' + +#define FLAG_ERROR -1 +#define FLAG_UNKNOWN (int *)0 + +/* The thing that we build the array of flags out of. */ +struct flags_alist { + char name; + int *value; +}; + +extern const struct flags_alist shell_flags[]; +extern char optflags[]; + +extern int + mark_modified_vars, errexit_flag, exit_immediately_on_error, + disallow_filename_globbing, + place_keywords_in_env, read_but_dont_execute, + just_one_command, unbound_vars_is_error, echo_input_at_read, verbose_flag, + echo_command_at_execute, noclobber, + hashing_enabled, forced_interactive, privileged_mode, jobs_m_flag, + asynchronous_notification, interactive_comments, no_symbolic_links, + function_trace_mode, error_trace_mode, pipefail_opt; + +/* -c, -s invocation options -- not really flags, but they show up in $- */ +extern int want_pending_command, read_from_stdin; + +#if 0 +extern int lexical_scoping; +#endif + +#if defined (BRACE_EXPANSION) +extern int brace_expansion; +#endif + +#if defined (BANG_HISTORY) +extern int history_expansion; +extern int histexp_flag; +#endif /* BANG_HISTORY */ + +#if defined (RESTRICTED_SHELL) +extern int restricted; +extern int restricted_shell; +#endif /* RESTRICTED_SHELL */ + +extern int *find_flag PARAMS((int)); +extern int change_flag PARAMS((int, int)); +extern char *which_set_flags PARAMS((void)); +extern void reset_shell_flags PARAMS((void)); + +extern char *get_current_flags PARAMS((void)); +extern void set_current_flags PARAMS((const char *)); + +extern void initialize_flags PARAMS((void)); + +/* A macro for efficiency. */ +#define change_flag_char(flag, on_or_off) change_flag (flag, on_or_off) + +#endif /* _FLAGS_H_ */ diff --git a/third_party/bash/fmtullong.c b/third_party/bash/fmtullong.c new file mode 100644 index 000000000..7f385bd45 --- /dev/null +++ b/third_party/bash/fmtullong.c @@ -0,0 +1,31 @@ +/* fmtullong.c - convert `long long int' to string */ + +/* Copyright (C) 2001-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#ifdef HAVE_LONG_LONG_INT + +#define LONG long long +#define UNSIGNED_LONG unsigned long long +#define fmtulong fmtullong + +#include "fmtulong.c" + +#endif diff --git a/third_party/bash/fmtulong.c b/third_party/bash/fmtulong.c new file mode 100644 index 000000000..d3c7167f8 --- /dev/null +++ b/third_party/bash/fmtulong.c @@ -0,0 +1,191 @@ +/* fmtulong.c -- Convert unsigned long int to string. */ + +/* Copyright (C) 1998-2011 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_LIMITS_H) +# include +#endif + +#include "bashansi.h" +#ifdef HAVE_STDDEF_H +# include +#endif + +#ifdef HAVE_STDINT_H +# include +#endif +#ifdef HAVE_INTTYPES_H +# include +#endif +#include "chartypes.h" +#include + +#include "bashintl.h" + +#include "stdc.h" + +#include "typemax.h" + +#ifndef errno +extern int errno; +#endif + +#define x_digs "0123456789abcdef" +#define X_digs "0123456789ABCDEF" + +/* XXX -- assumes uppercase letters, lowercase letters, and digits are + contiguous */ +#define FMTCHAR(x) \ + ((x) < 10) ? (x) + '0' \ + : (((x) < 36) ? (x) - 10 + 'a' \ + : (((x) < 62) ? (x) - 36 + 'A' \ + : (((x) == 62) ? '@' : '_'))) + +#ifndef FL_PREFIX +# define FL_PREFIX 0x01 /* add 0x, 0X, or 0 prefix as appropriate */ +# define FL_ADDBASE 0x02 /* add base# prefix to converted value */ +# define FL_HEXUPPER 0x04 /* use uppercase when converting to hex */ +# define FL_UNSIGNED 0x08 /* don't add any sign */ +#endif + +#ifndef LONG +# define LONG long +# define UNSIGNED_LONG unsigned long +#endif + +/* `unsigned long' (or unsigned long long) to string conversion for a given + base. The caller passes the output buffer and the size. This should + check for buffer underflow, but currently does not. */ +char * +fmtulong (ui, base, buf, len, flags) + UNSIGNED_LONG ui; + int base; + char *buf; + size_t len; + int flags; +{ + char *p; + int sign; + LONG si; + + if (base == 0) + base = 10; + + if (base < 2 || base > 64) + { +#if 1 + /* XXX - truncation possible with long translation */ + strncpy (buf, _("invalid base"), len - 1); + buf[len-1] = '\0'; + errno = EINVAL; + return (p = buf); +#else + base = 10; +#endif + } + + sign = 0; + if ((flags & FL_UNSIGNED) == 0 && (LONG)ui < 0) + { + ui = -ui; + sign = '-'; + } + + p = buf + len - 2; + p[1] = '\0'; + + /* handle common cases explicitly */ + switch (base) + { + case 10: + if (ui < 10) + { + *p-- = TOCHAR (ui); + break; + } + /* Favor signed arithmetic over unsigned arithmetic; it is faster on + many machines. */ + if ((LONG)ui < 0) + { + *p-- = TOCHAR (ui % 10); + si = ui / 10; + } + else + si = ui; + do + *p-- = TOCHAR (si % 10); + while (si /= 10); + break; + + case 8: + do + *p-- = TOCHAR (ui & 7); + while (ui >>= 3); + break; + + case 16: + do + *p-- = (flags & FL_HEXUPPER) ? X_digs[ui & 15] : x_digs[ui & 15]; + while (ui >>= 4); + break; + + case 2: + do + *p-- = TOCHAR (ui & 1); + while (ui >>= 1); + break; + + default: + do + *p-- = FMTCHAR (ui % base); + while (ui /= base); + break; + } + + if ((flags & FL_PREFIX) && (base == 8 || base == 16)) + { + if (base == 16) + { + *p-- = (flags & FL_HEXUPPER) ? 'X' : 'x'; + *p-- = '0'; + } + else if (p[1] != '0') + *p-- = '0'; + } + else if ((flags & FL_ADDBASE) && base != 10) + { + *p-- = '#'; + *p-- = TOCHAR (base % 10); + if (base > 10) + *p-- = TOCHAR (base / 10); + } + + if (sign) + *p-- = '-'; + + return (p + 1); +} diff --git a/third_party/bash/fmtumax.c b/third_party/bash/fmtumax.c new file mode 100644 index 000000000..78a9aef4d --- /dev/null +++ b/third_party/bash/fmtumax.c @@ -0,0 +1,27 @@ +/* fmtumax.c -- Convert uintmax_t to string. */ + +/* Copyright (C) 2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#define LONG intmax_t +#define UNSIGNED_LONG uintmax_t +#define fmtulong fmtumax + +#include "fmtulong.c" diff --git a/third_party/bash/fnxform.c b/third_party/bash/fnxform.c new file mode 100644 index 000000000..0a9f0247d --- /dev/null +++ b/third_party/bash/fnxform.c @@ -0,0 +1,199 @@ +/* fnxform - use iconv(3) to transform strings to and from "filename" format */ + +/* Copyright (C) 2009-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" +#if defined (HAVE_UNISTD_H) +# include +#endif +#include "bashansi.h" +#include +#include "bashtypes.h" + +#include "stdc.h" +#include "bashintl.h" +#include "xmalloc.h" + +#if defined (HAVE_ICONV) +# include +#endif + +#if defined (HAVE_LOCALE_CHARSET) +extern const char *locale_charset PARAMS((void)); +#else +extern char *get_locale_var PARAMS((char *)); +#endif + +#if defined (HAVE_ICONV) +static iconv_t conv_fromfs = (iconv_t)-1; +static iconv_t conv_tofs = (iconv_t)-1; + +#define OUTLEN_MAX 4096 + +static char *outbuf = 0; +static size_t outlen = 0; + +static char *curencoding PARAMS((void)); +static void init_tofs PARAMS((void)); +static void init_fromfs PARAMS((void)); + +static char * +curencoding () +{ + char *loc; +#if defined (HAVE_LOCALE_CHARSET) + loc = (char *)locale_charset (); + return loc; +#else + char *dot, *mod; + + loc = get_locale_var ("LC_CTYPE"); + if (loc == 0 || *loc == 0) + return ""; + dot = strchr (loc, '.'); + if (dot == 0) + return loc; + mod = strchr (dot, '@'); + if (mod) + *mod = '\0'; + return ++dot; +#endif +} + +static void +init_tofs () +{ + char *cur; + + cur = curencoding (); + conv_tofs = iconv_open ("UTF-8-MAC", cur); +} + +static void +init_fromfs () +{ + char *cur; + + cur = curencoding (); + conv_fromfs = iconv_open (cur, "UTF-8-MAC"); +} + +char * +fnx_tofs (string, len) + char *string; + size_t len; +{ +#ifdef MACOSX + ICONV_CONST char *inbuf; + char *tempbuf; + size_t templen; + + if (conv_tofs == (iconv_t)-1) + init_tofs (); + if (conv_tofs == (iconv_t)-1) + return string; + + /* Free and reallocate outbuf if it's *too* big */ + if (outlen >= OUTLEN_MAX && len < OUTLEN_MAX - 8) + { + free (outbuf); + outbuf = 0; + outlen = 0; + } + + inbuf = string; + if (outbuf == 0 || outlen < len + 8) + { + outlen = len + 8; + outbuf = outbuf ? xrealloc (outbuf, outlen + 1) : xmalloc (outlen + 1); + } + tempbuf = outbuf; + templen = outlen; + + iconv (conv_tofs, NULL, NULL, NULL, NULL); + + if (iconv (conv_tofs, &inbuf, &len, &tempbuf, &templen) == (size_t)-1) + return string; + + *tempbuf = '\0'; + return outbuf; +#else + return string; +#endif +} + +char * +fnx_fromfs (string, len) + char *string; + size_t len; +{ +#ifdef MACOSX + ICONV_CONST char *inbuf; + char *tempbuf; + size_t templen; + + if (conv_fromfs == (iconv_t)-1) + init_fromfs (); + if (conv_fromfs == (iconv_t)-1) + return string; + + /* Free and reallocate outbuf if it's *too* big */ + if (outlen >= OUTLEN_MAX && len < OUTLEN_MAX - 8) + { + free (outbuf); + outbuf = 0; + outlen = 0; + } + + inbuf = string; + if (outbuf == 0 || outlen < (len + 8)) + { + outlen = len + 8; + outbuf = outbuf ? xrealloc (outbuf, outlen + 1) : xmalloc (outlen + 1); + } + tempbuf = outbuf; + templen = outlen; + + iconv (conv_fromfs, NULL, NULL, NULL, NULL); + + if (iconv (conv_fromfs, &inbuf, &len, &tempbuf, &templen) == (size_t)-1) + return string; + + *tempbuf = '\0'; + return outbuf; +#else + return string; +#endif +} + +#else +char * +fnx_tofs (string) + char *string; +{ + return string; +} + +char * +fnx_fromfs (string) + char *string; +{ + return string; +} +#endif diff --git a/third_party/bash/fpurge.c b/third_party/bash/fpurge.c new file mode 100644 index 000000000..37a5c0d34 --- /dev/null +++ b/third_party/bash/fpurge.c @@ -0,0 +1,232 @@ +/* fpurge - Flushing buffers of a FILE stream. */ + +/* Copyright (C) 2007-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "stdc.h" + +#include + +/* Specification. Same as in ../../externs.h. */ +#define NEED_FPURGE_DECL +#if HAVE_FPURGE +# define fpurge _bash_fpurge +#endif +extern int fpurge PARAMS((FILE *stream)); + +#if HAVE___FPURGE /* glibc >= 2.2, Haiku, Solaris >= 7 */ +# include +#endif +#include + +/* Inline contents of gnulib:stdio-impl.h */ + +/* Many stdio implementations have the same logic and therefore can share + the same implementation of stdio extension API, except that some fields + have different naming conventions, or their access requires some casts. */ + +/* BSD stdio derived implementations. */ + +#if defined __NetBSD__ /* NetBSD */ +/* Get __NetBSD_Version__. */ +# include +#endif + +#if defined __sferror || defined __DragonFly__ /* FreeBSD, NetBSD, OpenBSD, DragonFly, MacOS X, Cygwin */ + +# if defined __DragonFly__ /* DragonFly */ + /* See . */ +# define fp_ ((struct { struct __FILE_public pub; \ + struct { unsigned char *_base; int _size; } _bf; \ + void *cookie; \ + void *_close; \ + void *_read; \ + void *_seek; \ + void *_write; \ + struct { unsigned char *_base; int _size; } _ub; \ + int _ur; \ + unsigned char _ubuf[3]; \ + unsigned char _nbuf[1]; \ + struct { unsigned char *_base; int _size; } _lb; \ + int _blksize; \ + fpos_t _offset; \ + /* More fields, not relevant here. */ \ + } *) fp) + /* See . */ +# define _p pub._p +# define _flags pub._flags +# define _r pub._r +# define _w pub._w +# else +# define fp_ fp +# endif + +# if (defined __NetBSD__ && __NetBSD_Version__ >= 105270000) || defined __OpenBSD__ /* NetBSD >= 1.5ZA, OpenBSD */ + /* See + and */ + struct __sfileext + { + struct __sbuf _ub; /* ungetc buffer */ + /* More fields, not relevant here. */ + }; +# define fp_ub ((struct __sfileext *) fp->_ext._base)->_ub +# else /* FreeBSD, NetBSD <= 1.5Z, DragonFly, MacOS X, Cygwin */ +# define fp_ub fp_->_ub +# endif + +# define HASUB(fp) (fp_ub._base != NULL) + +#endif + +/* SystemV derived implementations. */ + +#if defined _IOERR + +# if defined __sun && defined _LP64 /* Solaris/{SPARC,AMD64} 64-bit */ +# define fp_ ((struct { unsigned char *_ptr; \ + unsigned char *_base; \ + unsigned char *_end; \ + long _cnt; \ + int _file; \ + unsigned int _flag; \ + } *) fp) +# else +# define fp_ fp +# endif + +# if defined _SCO_DS /* OpenServer */ +# define _cnt __cnt +# define _ptr __ptr +# define _base __base +# define _flag __flag +# endif + +#endif + +int +fpurge (FILE *fp) +{ +#if HAVE___FPURGE /* glibc >= 2.2, Haiku, Solaris >= 7 */ + + __fpurge (fp); + /* The __fpurge function does not have a return value. */ + return 0; + +#elif HAVE_FPURGE /* FreeBSD, NetBSD, OpenBSD, DragonFly, MacOS X, Cygwin 1.7 */ + + /* Call the system's fpurge function. */ +# undef fpurge +# if !HAVE_DECL_FPURGE + extern int fpurge (FILE *); +# endif + int result = fpurge (fp); +# if defined __sferror || defined __DragonFly__ /* FreeBSD, NetBSD, OpenBSD, DragonFly, MacOS X, Cygwin */ + if (result == 0) + /* Correct the invariants that fpurge broke. + on BSD systems says: + "The following always hold: if _flags & __SRD, _w is 0." + If this invariant is not fulfilled and the stream is read-write but + currently reading, subsequent putc or fputc calls will write directly + into the buffer, although they shouldn't be allowed to. */ + if ((fp_->_flags & __SRD) != 0) + fp_->_w = 0; +# endif + return result; + +#else + + /* Most systems provide FILE as a struct and the necessary bitmask in + , because they need it for implementing getc() and putc() as + fast macros. */ +# if defined _IO_ftrylockfile || __GNU_LIBRARY__ == 1 /* GNU libc, BeOS, Haiku, Linux libc5 */ + fp->_IO_read_end = fp->_IO_read_ptr; + fp->_IO_write_ptr = fp->_IO_write_base; + /* Avoid memory leak when there is an active ungetc buffer. */ + if (fp->_IO_save_base != NULL) + { + free (fp->_IO_save_base); + fp->_IO_save_base = NULL; + } + return 0; +# elif defined __sferror || defined __DragonFly__ /* FreeBSD, NetBSD, OpenBSD, DragonFly, MacOS X, Cygwin */ + fp_->_p = fp_->_bf._base; + fp_->_r = 0; + fp_->_w = ((fp_->_flags & (__SLBF | __SNBF | __SRD)) == 0 /* fully buffered and not currently reading? */ + ? fp_->_bf._size + : 0); + /* Avoid memory leak when there is an active ungetc buffer. */ + if (fp_ub._base != NULL) + { + if (fp_ub._base != fp_->_ubuf) + free (fp_ub._base); + fp_ub._base = NULL; + } + return 0; +# elif defined __EMX__ /* emx+gcc */ + fp->_ptr = fp->_buffer; + fp->_rcount = 0; + fp->_wcount = 0; + fp->_ungetc_count = 0; + return 0; +# elif defined _IOERR || defined __TANDEM /* AIX, HP-UX, IRIX, OSF/1, Solaris, OpenServer, mingw */ + fp->_ptr = fp->_base; + if (fp->_ptr != NULL) + fp->_cnt = 0; + return 0; +# elif defined __UCLIBC__ /* uClibc */ +# ifdef __STDIO_BUFFERS + if (fp->__modeflags & __FLAG_WRITING) + fp->__bufpos = fp->__bufstart; + else if (fp->__modeflags & (__FLAG_READONLY | __FLAG_READING)) + fp->__bufpos = fp->__bufread; +# endif + return 0; +# elif defined __QNX__ /* QNX */ + fp->_Rback = fp->_Back + sizeof (fp->_Back); + fp->_Rsave = NULL; + if (fp->_Mode & 0x2000 /* _MWRITE */) + /* fp->_Buf <= fp->_Next <= fp->_Wend */ + fp->_Next = fp->_Buf; + else + /* fp->_Buf <= fp->_Next <= fp->_Rend */ + fp->_Rend = fp->_Next; + return 0; +# elif defined __MINT__ /* Atari FreeMiNT */ + if (fp->__pushed_back) + { + fp->__bufp = fp->__pushback_bufp; + fp->__pushed_back = 0; + } + /* Preserve the current file position. */ + if (fp->__target != -1) + fp->__target += fp->__bufp - fp->__buffer; + fp->__bufp = fp->__buffer; + /* Nothing in the buffer, next getc is nontrivial. */ + fp->__get_limit = fp->__bufp; + /* Nothing in the buffer, next putc is nontrivial. */ + fp->__put_limit = fp->__buffer; + return 0; +# else +# warning "Please port gnulib fpurge.c to your platform! Look at the definitions of fflush, setvbuf and ungetc on your system, then report this to bug-gnulib." + return 0; +# endif + +#endif +} diff --git a/third_party/bash/general.c b/third_party/bash/general.c new file mode 100644 index 000000000..b9e43aa4c --- /dev/null +++ b/third_party/bash/general.c @@ -0,0 +1,1450 @@ +/* general.c -- Stuff that is used by all files. */ + +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#if defined (HAVE_SYS_PARAM_H) +# include +#endif +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "filecntl.h" +#include "bashansi.h" +#include +#include "chartypes.h" +#include + +#include "bashintl.h" + +#include "shell.h" +#include "parser.h" +#include "flags.h" +#include "findcmd.h" +#include "test.h" +#include "trap.h" +#include "pathexp.h" + +#include "common.h" + +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include /* mbschr */ +#endif + +#include "tilde.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#ifdef __CYGWIN__ +# include +#endif + +static char *bash_special_tilde_expansions PARAMS((char *)); +static int unquoted_tilde_word PARAMS((const char *)); +static void initialize_group_array PARAMS((void)); + +/* A standard error message to use when getcwd() returns NULL. */ +const char * const bash_getcwd_errstr = N_("getcwd: cannot access parent directories"); + +/* Do whatever is necessary to initialize `Posix mode'. This currently + modifies the following variables which are controlled via shopt: + interactive_comments + source_uses_path + expand_aliases + inherit_errexit + print_shift_error + posixglob + + and the following variables which cannot be user-modified: + + source_searches_cwd + + If we add to the first list, we need to change the table and functions + below */ + +static struct { + int *posix_mode_var; +} posix_vars[] = +{ + &interactive_comments, + &source_uses_path, + &expand_aliases, + &inherit_errexit, + &print_shift_error, + 0 +}; + +static char *saved_posix_vars = 0; + +void +posix_initialize (on) + int on; +{ + /* Things that should be turned on when posix mode is enabled. */ + if (on != 0) + { + interactive_comments = source_uses_path = expand_aliases = 1; + inherit_errexit = 1; + source_searches_cwd = 0; + print_shift_error = 1; + } + + /* Things that should be turned on when posix mode is disabled. */ + else if (saved_posix_vars) /* on == 0, restore saved settings */ + { + set_posix_options (saved_posix_vars); + free (saved_posix_vars); + saved_posix_vars = 0; + } + else /* on == 0, restore a default set of settings */ + { + source_searches_cwd = 1; + expand_aliases = interactive_shell; + print_shift_error = 0; + } +} + +int +num_posix_options () +{ + return ((sizeof (posix_vars) / sizeof (posix_vars[0])) - 1); +} + +char * +get_posix_options (bitmap) + char *bitmap; +{ + register int i; + + if (bitmap == 0) + bitmap = (char *)xmalloc (num_posix_options ()); /* no trailing NULL */ + for (i = 0; posix_vars[i].posix_mode_var; i++) + bitmap[i] = *(posix_vars[i].posix_mode_var); + return bitmap; +} + +#undef save_posix_options +void +save_posix_options () +{ + saved_posix_vars = get_posix_options (saved_posix_vars); +} + +void +set_posix_options (bitmap) + const char *bitmap; +{ + register int i; + + for (i = 0; posix_vars[i].posix_mode_var; i++) + *(posix_vars[i].posix_mode_var) = bitmap[i]; +} + +/* **************************************************************** */ +/* */ +/* Functions to convert to and from and display non-standard types */ +/* */ +/* **************************************************************** */ + +#if defined (RLIMTYPE) +RLIMTYPE +string_to_rlimtype (s) + char *s; +{ + RLIMTYPE ret; + int neg; + + ret = 0; + neg = 0; + while (s && *s && whitespace (*s)) + s++; + if (s && (*s == '-' || *s == '+')) + { + neg = *s == '-'; + s++; + } + for ( ; s && *s && DIGIT (*s); s++) + ret = (ret * 10) + TODIGIT (*s); + return (neg ? -ret : ret); +} + +void +print_rlimtype (n, addnl) + RLIMTYPE n; + int addnl; +{ + char s[INT_STRLEN_BOUND (RLIMTYPE) + 1], *p; + + p = s + sizeof(s); + *--p = '\0'; + + if (n < 0) + { + do + *--p = '0' - n % 10; + while ((n /= 10) != 0); + + *--p = '-'; + } + else + { + do + *--p = '0' + n % 10; + while ((n /= 10) != 0); + } + + printf ("%s%s", p, addnl ? "\n" : ""); +} +#endif /* RLIMTYPE */ + +/* **************************************************************** */ +/* */ +/* Input Validation Functions */ +/* */ +/* **************************************************************** */ + +/* Return non-zero if all of the characters in STRING are digits. */ +int +all_digits (string) + const char *string; +{ + register const char *s; + + for (s = string; *s; s++) + if (DIGIT (*s) == 0) + return (0); + + return (1); +} + +/* Return non-zero if the characters pointed to by STRING constitute a + valid number. Stuff the converted number into RESULT if RESULT is + not null. */ +int +legal_number (string, result) + const char *string; + intmax_t *result; +{ + intmax_t value; + char *ep; + + if (result) + *result = 0; + + if (string == 0) + return 0; + + errno = 0; + value = strtoimax (string, &ep, 10); + if (errno || ep == string) + return 0; /* errno is set on overflow or underflow */ + + /* Skip any trailing whitespace, since strtoimax does not. */ + while (whitespace (*ep)) + ep++; + + /* If *string is not '\0' but *ep is '\0' on return, the entire string + is valid. */ + if (*string && *ep == '\0') + { + if (result) + *result = value; + /* The SunOS4 implementation of strtol() will happily ignore + overflow conditions, so this cannot do overflow correctly + on those systems. */ + return 1; + } + + return (0); +} + +/* Return 1 if this token is a legal shell `identifier'; that is, it consists + solely of letters, digits, and underscores, and does not begin with a + digit. */ +int +legal_identifier (name) + const char *name; +{ + register const char *s; + unsigned char c; + + if (!name || !(c = *name) || (legal_variable_starter (c) == 0)) + return (0); + + for (s = name + 1; (c = *s) != 0; s++) + { + if (legal_variable_char (c) == 0) + return (0); + } + return (1); +} + +/* Return 1 if NAME is a valid value that can be assigned to a nameref + variable. FLAGS can be 2, in which case the name is going to be used + to create a variable. Other values are currently unused, but could + be used to allow values to be stored and indirectly referenced, but + not used in assignments. */ +int +valid_nameref_value (name, flags) + const char *name; + int flags; +{ + if (name == 0 || *name == 0) + return 0; + + /* valid identifier */ +#if defined (ARRAY_VARS) + if (legal_identifier (name) || (flags != 2 && valid_array_reference (name, 0))) +#else + if (legal_identifier (name)) +#endif + return 1; + + return 0; +} + +int +check_selfref (name, value, flags) + const char *name; + char *value; + int flags; +{ + char *t; + + if (STREQ (name, value)) + return 1; + +#if defined (ARRAY_VARS) + if (valid_array_reference (value, 0)) + { + t = array_variable_name (value, 0, (char **)NULL, (int *)NULL); + if (t && STREQ (name, t)) + { + free (t); + return 1; + } + free (t); + } +#endif + + return 0; /* not a self reference */ +} + +/* Make sure that WORD is a valid shell identifier, i.e. + does not contain a dollar sign, nor is quoted in any way. + If CHECK_WORD is non-zero, + the word is checked to ensure that it consists of only letters, + digits, and underscores, and does not consist of all digits. */ +int +check_identifier (word, check_word) + WORD_DESC *word; + int check_word; +{ + if (word->flags & (W_HASDOLLAR|W_QUOTED)) /* XXX - HASDOLLAR? */ + { + internal_error (_("`%s': not a valid identifier"), word->word); + return (0); + } + else if (check_word && (all_digits (word->word) || legal_identifier (word->word) == 0)) + { + internal_error (_("`%s': not a valid identifier"), word->word); + return (0); + } + else + return (1); +} + +/* Return 1 if STRING is a function name that the shell will import from + the environment. Currently we reject attempts to import shell functions + containing slashes, beginning with newlines or containing blanks. In + Posix mode, we require that STRING be a valid shell identifier. Not + used yet. */ +int +importable_function_name (string, len) + const char *string; + size_t len; +{ + if (absolute_program (string)) /* don't allow slash */ + return 0; + if (*string == '\n') /* can't start with a newline */ + return 0; + if (shellblank (*string) || shellblank(string[len-1])) + return 0; + return (posixly_correct ? legal_identifier (string) : 1); +} + +int +exportable_function_name (string) + const char *string; +{ + if (absolute_program (string)) + return 0; + if (mbschr (string, '=') != 0) + return 0; + return 1; +} + +/* Return 1 if STRING comprises a valid alias name. The shell accepts + essentially all characters except those which must be quoted to the + parser (which disqualifies them from alias expansion anyway) and `/'. */ +int +legal_alias_name (string, flags) + const char *string; + int flags; +{ + register const char *s; + + for (s = string; *s; s++) + if (shellbreak (*s) || shellxquote (*s) || shellexp (*s) || (*s == '/')) + return 0; + return 1; +} + +/* Returns non-zero if STRING is an assignment statement. The returned value + is the index of the `=' sign. If FLAGS&1 we are expecting a compound assignment + and require an array subscript before the `=' to denote an assignment + statement. */ +int +assignment (string, flags) + const char *string; + int flags; +{ + register unsigned char c; + register int newi, indx; + + c = string[indx = 0]; + +#if defined (ARRAY_VARS) + /* If parser_state includes PST_COMPASSIGN, FLAGS will include 1, so we are + parsing the contents of a compound assignment. If parser_state includes + PST_REPARSE, we are in the middle of an assignment statement and breaking + the words between the parens into words and assignment statements, but + we don't need to check for that right now. Within a compound assignment, + the subscript is required to make the word an assignment statement. If + we don't have a subscript, even if the word is a valid assignment + statement otherwise, we don't want to treat it as one. */ + if ((flags & 1) && c != '[') /* ] */ + return (0); + else if ((flags & 1) == 0 && legal_variable_starter (c) == 0) +#else + if (legal_variable_starter (c) == 0) +#endif + return (0); + + while (c = string[indx]) + { + /* The following is safe. Note that '=' at the start of a word + is not an assignment statement. */ + if (c == '=') + return (indx); + +#if defined (ARRAY_VARS) + if (c == '[') + { + newi = skipsubscript (string, indx, (flags & 2) ? 1 : 0); + /* XXX - why not check for blank subscripts here, if we do in + valid_array_reference? */ + if (string[newi++] != ']') + return (0); + if (string[newi] == '+' && string[newi+1] == '=') + return (newi + 1); + return ((string[newi] == '=') ? newi : 0); + } +#endif /* ARRAY_VARS */ + + /* Check for `+=' */ + if (c == '+' && string[indx+1] == '=') + return (indx + 1); + + /* Variable names in assignment statements may contain only letters, + digits, and `_'. */ + if (legal_variable_char (c) == 0) + return (0); + + indx++; + } + return (0); +} + +int +line_isblank (line) + const char *line; +{ + register int i; + + if (line == 0) + return 0; /* XXX */ + for (i = 0; line[i]; i++) + if (isblank ((unsigned char)line[i]) == 0) + break; + return (line[i] == '\0'); +} + +/* **************************************************************** */ +/* */ +/* Functions to manage files and file descriptors */ +/* */ +/* **************************************************************** */ + +/* A function to unset no-delay mode on a file descriptor. Used in shell.c + to unset it on the fd passed as stdin. Should be called on stdin if + readline gets an EAGAIN or EWOULDBLOCK when trying to read input. */ + +#if !defined (O_NDELAY) +# if defined (FNDELAY) +# define O_NDELAY FNDELAY +# endif +#endif /* O_NDELAY */ + +/* Make sure no-delay mode is not set on file descriptor FD. */ +int +sh_unset_nodelay_mode (fd) + int fd; +{ + int flags, bflags; + + if ((flags = fcntl (fd, F_GETFL, 0)) < 0) + return -1; + + bflags = 0; + + /* This is defined to O_NDELAY in filecntl.h if O_NONBLOCK is not present + and O_NDELAY is defined. */ +#ifdef O_NONBLOCK + bflags |= O_NONBLOCK; +#endif + +#ifdef O_NDELAY + bflags |= O_NDELAY; +#endif + + if (flags & bflags) + { + flags &= ~bflags; + return (fcntl (fd, F_SETFL, flags)); + } + + return 0; +} + +/* Just a wrapper for the define in include/filecntl.h */ +int +sh_setclexec (fd) + int fd; +{ + return (SET_CLOSE_ON_EXEC (fd)); +} + +/* Return 1 if file descriptor FD is valid; 0 otherwise. */ +int +sh_validfd (fd) + int fd; +{ + return (fcntl (fd, F_GETFD, 0) >= 0); +} + +int +fd_ispipe (fd) + int fd; +{ + errno = 0; + return ((lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE)); +} + +/* There is a bug in the NeXT 2.1 rlogind that causes opens + of /dev/tty to fail. */ + +#if defined (__BEOS__) +/* On BeOS, opening in non-blocking mode exposes a bug in BeOS, so turn it + into a no-op. This should probably go away in the future. */ +# undef O_NONBLOCK +# define O_NONBLOCK 0 +#endif /* __BEOS__ */ + +void +check_dev_tty () +{ + int tty_fd; + char *tty; + + tty_fd = open ("/dev/tty", O_RDWR|O_NONBLOCK); + + if (tty_fd < 0) + { + tty = (char *)ttyname (fileno (stdin)); + if (tty == 0) + return; + tty_fd = open (tty, O_RDWR|O_NONBLOCK); + } + if (tty_fd >= 0) + close (tty_fd); +} + +/* Return 1 if PATH1 and PATH2 are the same file. This is kind of + expensive. If non-NULL STP1 and STP2 point to stat structures + corresponding to PATH1 and PATH2, respectively. */ +int +same_file (path1, path2, stp1, stp2) + const char *path1, *path2; + struct stat *stp1, *stp2; +{ + struct stat st1, st2; + + if (stp1 == NULL) + { + if (stat (path1, &st1) != 0) + return (0); + stp1 = &st1; + } + + if (stp2 == NULL) + { + if (stat (path2, &st2) != 0) + return (0); + stp2 = &st2; + } + + return ((stp1->st_dev == stp2->st_dev) && (stp1->st_ino == stp2->st_ino)); +} + +/* Move FD to a number close to the maximum number of file descriptors + allowed in the shell process, to avoid the user stepping on it with + redirection and causing us extra work. If CHECK_NEW is non-zero, + we check whether or not the file descriptors are in use before + duplicating FD onto them. MAXFD says where to start checking the + file descriptors. If it's less than 20, we get the maximum value + available from getdtablesize(2). */ +int +move_to_high_fd (fd, check_new, maxfd) + int fd, check_new, maxfd; +{ + int script_fd, nfds, ignore; + + if (maxfd < 20) + { + nfds = getdtablesize (); + if (nfds <= 0) + nfds = 20; + if (nfds > HIGH_FD_MAX) + nfds = HIGH_FD_MAX; /* reasonable maximum */ + } + else + nfds = maxfd; + + for (nfds--; check_new && nfds > 3; nfds--) + if (fcntl (nfds, F_GETFD, &ignore) == -1) + break; + + if (nfds > 3 && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1) + { + if (check_new == 0 || fd != fileno (stderr)) /* don't close stderr */ + close (fd); + return (script_fd); + } + + /* OK, we didn't find one less than our artificial maximum; return the + original file descriptor. */ + return (fd); +} + +/* Return non-zero if the characters from SAMPLE are not all valid + characters to be found in the first line of a shell script. We + check up to the first newline, or SAMPLE_LEN, whichever comes first. + All of the characters must be printable or whitespace. */ + +int +check_binary_file (sample, sample_len) + const char *sample; + int sample_len; +{ + register int i; + unsigned char c; + + if (sample_len >= 4 && sample[0] == 0x7f && sample[1] == 'E' && sample[2] == 'L' && sample[3] == 'F') + return 1; + + /* Generally we check the first line for NULs. If the first line looks like + a `#!' interpreter specifier, we just look for NULs anywhere in the + buffer. */ + if (sample[0] == '#' && sample[1] == '!') + return (memchr (sample, '\0', sample_len) != NULL); + + for (i = 0; i < sample_len; i++) + { + c = sample[i]; + if (c == '\n') + return (0); + if (c == '\0') + return (1); + } + + return (0); +} + +/* **************************************************************** */ +/* */ +/* Functions to manipulate pipes */ +/* */ +/* **************************************************************** */ + +int +sh_openpipe (pv) + int *pv; +{ + int r; + + if ((r = pipe (pv)) < 0) + return r; + + pv[0] = move_to_high_fd (pv[0], 1, 64); + pv[1] = move_to_high_fd (pv[1], 1, 64); + + return 0; +} + +int +sh_closepipe (pv) + int *pv; +{ + if (pv[0] >= 0) + close (pv[0]); + + if (pv[1] >= 0) + close (pv[1]); + + pv[0] = pv[1] = -1; + return 0; +} + +/* **************************************************************** */ +/* */ +/* Functions to inspect pathnames */ +/* */ +/* **************************************************************** */ + +int +file_exists (fn) + const char *fn; +{ + struct stat sb; + + return (stat (fn, &sb) == 0); +} + +int +file_isdir (fn) + const char *fn; +{ + struct stat sb; + + return ((stat (fn, &sb) == 0) && S_ISDIR (sb.st_mode)); +} + +int +file_iswdir (fn) + const char *fn; +{ + return (file_isdir (fn) && sh_eaccess (fn, W_OK) == 0); +} + +/* Return 1 if STRING is "." or "..", optionally followed by a directory + separator */ +int +path_dot_or_dotdot (string) + const char *string; +{ + if (string == 0 || *string == '\0' || *string != '.') + return (0); + + /* string[0] == '.' */ + if (PATHSEP(string[1]) || (string[1] == '.' && PATHSEP(string[2]))) + return (1); + + return (0); +} + +/* Return 1 if STRING contains an absolute pathname, else 0. Used by `cd' + to decide whether or not to look up a directory name in $CDPATH. */ +int +absolute_pathname (string) + const char *string; +{ + if (string == 0 || *string == '\0') + return (0); + + if (ABSPATH(string)) + return (1); + + if (string[0] == '.' && PATHSEP(string[1])) /* . and ./ */ + return (1); + + if (string[0] == '.' && string[1] == '.' && PATHSEP(string[2])) /* .. and ../ */ + return (1); + + return (0); +} + +/* Return 1 if STRING is an absolute program name; it is absolute if it + contains any slashes. This is used to decide whether or not to look + up through $PATH. */ +int +absolute_program (string) + const char *string; +{ + return ((char *)mbschr (string, '/') != (char *)NULL); +} + +/* **************************************************************** */ +/* */ +/* Functions to manipulate pathnames */ +/* */ +/* **************************************************************** */ + +/* Turn STRING (a pathname) into an absolute pathname, assuming that + DOT_PATH contains the symbolic location of `.'. This always + returns a new string, even if STRING was an absolute pathname to + begin with. */ +char * +make_absolute (string, dot_path) + const char *string, *dot_path; +{ + char *result; + + if (dot_path == 0 || ABSPATH(string)) +#ifdef __CYGWIN__ + { + char pathbuf[PATH_MAX + 1]; + + /* WAS cygwin_conv_to_full_posix_path (string, pathbuf); */ + cygwin_conv_path (CCP_WIN_A_TO_POSIX, string, pathbuf, PATH_MAX); + result = savestring (pathbuf); + } +#else + result = savestring (string); +#endif + else + result = sh_makepath (dot_path, string, 0); + + return (result); +} + +/* Return the `basename' of the pathname in STRING (the stuff after the + last '/'). If STRING is `/', just return it. */ +char * +base_pathname (string) + char *string; +{ + char *p; + +#if 0 + if (absolute_pathname (string) == 0) + return (string); +#endif + + if (string[0] == '/' && string[1] == 0) + return (string); + + p = (char *)strrchr (string, '/'); + return (p ? ++p : string); +} + +/* Return the full pathname of FILE. Easy. Filenames that begin + with a '/' are returned as themselves. Other filenames have + the current working directory prepended. A new string is + returned in either case. */ +char * +full_pathname (file) + char *file; +{ + char *ret; + + file = (*file == '~') ? bash_tilde_expand (file, 0) : savestring (file); + + if (ABSPATH(file)) + return (file); + + ret = sh_makepath ((char *)NULL, file, (MP_DOCWD|MP_RMDOT)); + free (file); + + return (ret); +} + +/* A slightly related function. Get the prettiest name of this + directory possible. */ +static char tdir[PATH_MAX]; + +/* Return a pretty pathname. If the first part of the pathname is + the same as $HOME, then replace that with `~'. */ +char * +polite_directory_format (name) + char *name; +{ + char *home; + int l; + + home = get_string_value ("HOME"); + l = home ? strlen (home) : 0; + if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/')) + { + strncpy (tdir + 1, name + l, sizeof(tdir) - 2); + tdir[0] = '~'; + tdir[sizeof(tdir) - 1] = '\0'; + return (tdir); + } + else + return (name); +} + +/* Trim NAME. If NAME begins with `~/', skip over tilde prefix. Trim to + keep any tilde prefix and PROMPT_DIRTRIM trailing directory components + and replace the intervening characters with `...' */ +char * +trim_pathname (name, maxlen) + char *name; + int maxlen; +{ + int nlen, ndirs; + intmax_t nskip; + char *nbeg, *nend, *ntail, *v; + + if (name == 0 || (nlen = strlen (name)) == 0) + return name; + nend = name + nlen; + + v = get_string_value ("PROMPT_DIRTRIM"); + if (v == 0 || *v == 0) + return name; + if (legal_number (v, &nskip) == 0 || nskip <= 0) + return name; + + /* Skip over tilde prefix */ + nbeg = name; + if (name[0] == '~') + for (nbeg = name; *nbeg; nbeg++) + if (*nbeg == '/') + { + nbeg++; + break; + } + if (*nbeg == 0) + return name; + + for (ndirs = 0, ntail = nbeg; *ntail; ntail++) + if (*ntail == '/') + ndirs++; + if (ndirs < nskip) + return name; + + for (ntail = (*nend == '/') ? nend : nend - 1; ntail > nbeg; ntail--) + { + if (*ntail == '/') + nskip--; + if (nskip == 0) + break; + } + if (ntail == nbeg) + return name; + + /* Now we want to return name[0..nbeg]+"..."+ntail, modifying name in place */ + nlen = ntail - nbeg; + if (nlen <= 3) + return name; + + *nbeg++ = '.'; + *nbeg++ = '.'; + *nbeg++ = '.'; + + nlen = nend - ntail; + memmove (nbeg, ntail, nlen); + nbeg[nlen] = '\0'; + + return name; +} + +/* Return a printable representation of FN without special characters. The + caller is responsible for freeing memory if this returns something other + than its argument. If FLAGS is non-zero, we are printing for portable + re-input and should single-quote filenames appropriately. */ +char * +printable_filename (fn, flags) + char *fn; + int flags; +{ + char *newf; + + if (ansic_shouldquote (fn)) + newf = ansic_quote (fn, 0, NULL); + else if (flags && sh_contains_shell_metas (fn)) + newf = sh_single_quote (fn); + else + newf = fn; + + return newf; +} + +/* Given a string containing units of information separated by colons, + return the next one pointed to by (P_INDEX), or NULL if there are no more. + Advance (P_INDEX) to the character after the colon. */ +char * +extract_colon_unit (string, p_index) + char *string; + int *p_index; +{ + int i, start, len; + char *value; + + if (string == 0) + return (string); + + len = strlen (string); + if (*p_index >= len) + return ((char *)NULL); + + i = *p_index; + + /* Each call to this routine leaves the index pointing at a colon if + there is more to the path. If I is > 0, then increment past the + `:'. If I is 0, then the path has a leading colon. Trailing colons + are handled OK by the `else' part of the if statement; an empty + string is returned in that case. */ + if (i && string[i] == ':') + i++; + + for (start = i; string[i] && string[i] != ':'; i++) + ; + + *p_index = i; + + if (i == start) + { + if (string[i]) + (*p_index)++; + /* Return "" in the case of a trailing `:'. */ + value = (char *)xmalloc (1); + value[0] = '\0'; + } + else + value = substring (string, start, i); + + return (value); +} + +/* **************************************************************** */ +/* */ +/* Tilde Initialization and Expansion */ +/* */ +/* **************************************************************** */ + +#if defined (PUSHD_AND_POPD) +extern char *get_dirstack_from_string PARAMS((char *)); +#endif + +static char **bash_tilde_prefixes; +static char **bash_tilde_prefixes2; +static char **bash_tilde_suffixes; +static char **bash_tilde_suffixes2; + +/* If tilde_expand hasn't been able to expand the text, perhaps it + is a special shell expansion. This function is installed as the + tilde_expansion_preexpansion_hook. It knows how to expand ~- and ~+. + If PUSHD_AND_POPD is defined, ~[+-]N expands to directories from the + directory stack. */ +static char * +bash_special_tilde_expansions (text) + char *text; +{ + char *result; + + result = (char *)NULL; + + if (text[0] == '+' && text[1] == '\0') + result = get_string_value ("PWD"); + else if (text[0] == '-' && text[1] == '\0') + result = get_string_value ("OLDPWD"); +#if defined (PUSHD_AND_POPD) + else if (DIGIT (*text) || ((*text == '+' || *text == '-') && DIGIT (text[1]))) + result = get_dirstack_from_string (text); +#endif + + return (result ? savestring (result) : (char *)NULL); +} + +/* Initialize the tilde expander. In Bash, we handle `~-' and `~+', as + well as handling special tilde prefixes; `:~" and `=~' are indications + that we should do tilde expansion. */ +void +tilde_initialize () +{ + static int times_called = 0; + + /* Tell the tilde expander that we want a crack first. */ + tilde_expansion_preexpansion_hook = bash_special_tilde_expansions; + + /* Tell the tilde expander about special strings which start a tilde + expansion, and the special strings that end one. Only do this once. + tilde_initialize () is called from within bashline_reinitialize (). */ + if (times_called++ == 0) + { + bash_tilde_prefixes = strvec_create (3); + bash_tilde_prefixes[0] = "=~"; + bash_tilde_prefixes[1] = ":~"; + bash_tilde_prefixes[2] = (char *)NULL; + + bash_tilde_prefixes2 = strvec_create (2); + bash_tilde_prefixes2[0] = ":~"; + bash_tilde_prefixes2[1] = (char *)NULL; + + tilde_additional_prefixes = bash_tilde_prefixes; + + bash_tilde_suffixes = strvec_create (3); + bash_tilde_suffixes[0] = ":"; + bash_tilde_suffixes[1] = "=~"; /* XXX - ?? */ + bash_tilde_suffixes[2] = (char *)NULL; + + tilde_additional_suffixes = bash_tilde_suffixes; + + bash_tilde_suffixes2 = strvec_create (2); + bash_tilde_suffixes2[0] = ":"; + bash_tilde_suffixes2[1] = (char *)NULL; + } +} + +/* POSIX.2, 3.6.1: A tilde-prefix consists of an unquoted tilde character + at the beginning of the word, followed by all of the characters preceding + the first unquoted slash in the word, or all the characters in the word + if there is no slash...If none of the characters in the tilde-prefix are + quoted, the characters in the tilde-prefix following the tilde shell be + treated as a possible login name. */ + +#define TILDE_END(c) ((c) == '\0' || (c) == '/' || (c) == ':') + +static int +unquoted_tilde_word (s) + const char *s; +{ + const char *r; + + for (r = s; TILDE_END(*r) == 0; r++) + { + switch (*r) + { + case '\\': + case '\'': + case '"': + return 0; + } + } + return 1; +} + +/* Find the end of the tilde-prefix starting at S, and return the tilde + prefix in newly-allocated memory. Return the length of the string in + *LENP. FLAGS tells whether or not we're in an assignment context -- + if so, `:' delimits the end of the tilde prefix as well. */ +char * +bash_tilde_find_word (s, flags, lenp) + const char *s; + int flags, *lenp; +{ + const char *r; + char *ret; + int l; + + for (r = s; *r && *r != '/'; r++) + { + /* Short-circuit immediately if we see a quote character. Even though + POSIX says that `the first unquoted slash' (or `:') terminates the + tilde-prefix, in practice, any quoted portion of the tilde prefix + will cause it to not be expanded. */ + if (*r == '\\' || *r == '\'' || *r == '"') + { + ret = savestring (s); + if (lenp) + *lenp = 0; + return ret; + } + else if (flags && *r == ':') + break; + } + l = r - s; + ret = xmalloc (l + 1); + strncpy (ret, s, l); + ret[l] = '\0'; + if (lenp) + *lenp = l; + return ret; +} + +/* Tilde-expand S by running it through the tilde expansion library. + ASSIGN_P is 1 if this is a variable assignment, so the alternate + tilde prefixes should be enabled (`=~' and `:~', see above). If + ASSIGN_P is 2, we are expanding the rhs of an assignment statement, + so `=~' is not valid. */ +char * +bash_tilde_expand (s, assign_p) + const char *s; + int assign_p; +{ + int r; + char *ret; + + tilde_additional_prefixes = assign_p == 0 ? (char **)0 + : (assign_p == 2 ? bash_tilde_prefixes2 : bash_tilde_prefixes); + if (assign_p == 2) + tilde_additional_suffixes = bash_tilde_suffixes2; + + r = (*s == '~') ? unquoted_tilde_word (s) : 1; + ret = r ? tilde_expand (s) : savestring (s); + + QUIT; + + return (ret); +} + +/* **************************************************************** */ +/* */ +/* Functions to manipulate and search the group list */ +/* */ +/* **************************************************************** */ + +static int ngroups, maxgroups; + +/* The set of groups that this user is a member of. */ +static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL; + +#if !defined (NOGROUP) +# define NOGROUP (gid_t) -1 +#endif + +static void +initialize_group_array () +{ + register int i; + + if (maxgroups == 0) + maxgroups = getmaxgroups (); + + ngroups = 0; + group_array = (GETGROUPS_T *)xrealloc (group_array, maxgroups * sizeof (GETGROUPS_T)); + +#if defined (HAVE_GETGROUPS) + ngroups = getgroups (maxgroups, group_array); +#endif + + /* If getgroups returns nothing, or the OS does not support getgroups(), + make sure the groups array includes at least the current gid. */ + if (ngroups == 0) + { + group_array[0] = current_user.gid; + ngroups = 1; + } + + /* If the primary group is not in the groups array, add it as group_array[0] + and shuffle everything else up 1, if there's room. */ + for (i = 0; i < ngroups; i++) + if (current_user.gid == (gid_t)group_array[i]) + break; + if (i == ngroups && ngroups < maxgroups) + { + for (i = ngroups; i > 0; i--) + group_array[i] = group_array[i - 1]; + group_array[0] = current_user.gid; + ngroups++; + } + + /* If the primary group is not group_array[0], swap group_array[0] and + whatever the current group is. The vast majority of systems should + not need this; a notable exception is Linux. */ + if (group_array[0] != current_user.gid) + { + for (i = 0; i < ngroups; i++) + if (group_array[i] == current_user.gid) + break; + if (i < ngroups) + { + group_array[i] = group_array[0]; + group_array[0] = current_user.gid; + } + } +} + +/* Return non-zero if GID is one that we have in our groups list. */ +int +#if defined (__STDC__) || defined ( _MINIX) +group_member (gid_t gid) +#else +group_member (gid) + gid_t gid; +#endif /* !__STDC__ && !_MINIX */ +{ +#if defined (HAVE_GETGROUPS) + register int i; +#endif + + /* Short-circuit if possible, maybe saving a call to getgroups(). */ + if (gid == current_user.gid || gid == current_user.egid) + return (1); + +#if defined (HAVE_GETGROUPS) + if (ngroups == 0) + initialize_group_array (); + + /* In case of error, the user loses. */ + if (ngroups <= 0) + return (0); + + /* Search through the list looking for GID. */ + for (i = 0; i < ngroups; i++) + if (gid == (gid_t)group_array[i]) + return (1); +#endif + + return (0); +} + +char ** +get_group_list (ngp) + int *ngp; +{ + static char **group_vector = (char **)NULL; + register int i; + + if (group_vector) + { + if (ngp) + *ngp = ngroups; + return group_vector; + } + + if (ngroups == 0) + initialize_group_array (); + + if (ngroups <= 0) + { + if (ngp) + *ngp = 0; + return (char **)NULL; + } + + group_vector = strvec_create (ngroups); + for (i = 0; i < ngroups; i++) + group_vector[i] = itos (group_array[i]); + + if (ngp) + *ngp = ngroups; + return group_vector; +} + +int * +get_group_array (ngp) + int *ngp; +{ + int i; + static int *group_iarray = (int *)NULL; + + if (group_iarray) + { + if (ngp) + *ngp = ngroups; + return (group_iarray); + } + + if (ngroups == 0) + initialize_group_array (); + + if (ngroups <= 0) + { + if (ngp) + *ngp = 0; + return (int *)NULL; + } + + group_iarray = (int *)xmalloc (ngroups * sizeof (int)); + for (i = 0; i < ngroups; i++) + group_iarray[i] = (int)group_array[i]; + + if (ngp) + *ngp = ngroups; + return group_iarray; +} + +/* **************************************************************** */ +/* */ +/* Miscellaneous functions */ +/* */ +/* **************************************************************** */ + +/* Return a value for PATH that is guaranteed to find all of the standard + utilities. This uses Posix.2 configuration variables, if present. It + uses a value defined in config.h as a last resort. */ +char * +conf_standard_path () +{ +#if defined (_CS_PATH) && defined (HAVE_CONFSTR) + char *p; + size_t len; + + len = (size_t)confstr (_CS_PATH, (char *)NULL, (size_t)0); + if (len > 0) + { + p = (char *)xmalloc (len + 2); + *p = '\0'; + confstr (_CS_PATH, p, len); + return (p); + } + else + return (savestring (STANDARD_UTILS_PATH)); +#else /* !_CS_PATH || !HAVE_CONFSTR */ +# if defined (CS_PATH) + return (savestring (CS_PATH)); +# else + return (savestring (STANDARD_UTILS_PATH)); +# endif /* !CS_PATH */ +#endif /* !_CS_PATH || !HAVE_CONFSTR */ +} + +int +default_columns () +{ + char *v; + int c; + + c = -1; + v = get_string_value ("COLUMNS"); + if (v && *v) + { + c = atoi (v); + if (c > 0) + return c; + } + + if (check_window_size) + get_new_window_size (0, (int *)0, &c); + + return (c > 0 ? c : 80); +} + + diff --git a/third_party/bash/general.h b/third_party/bash/general.h new file mode 100644 index 000000000..8064c50eb --- /dev/null +++ b/third_party/bash/general.h @@ -0,0 +1,372 @@ +/* general.h -- defines that everybody likes to use. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_GENERAL_H_) +#define _GENERAL_H_ + +#include "stdc.h" + +#include "bashtypes.h" +#include "chartypes.h" + +#if defined (HAVE_SYS_RESOURCE_H) && defined (RLIMTYPE) +# if defined (HAVE_SYS_TIME_H) +# include +# endif +# include +#endif + +#if defined (HAVE_STRING_H) +# include +#else +# include +#endif /* !HAVE_STRING_H */ + +#if defined (HAVE_LIMITS_H) +# include +#endif + +#include "xmalloc.h" + +/* NULL pointer type. */ +#if !defined (NULL) +# if defined (__STDC__) +# define NULL ((void *) 0) +# else +# define NULL 0x0 +# endif /* !__STDC__ */ +#endif /* !NULL */ + +/* Hardly used anymore */ +#define pointer_to_int(x) (int)((char *)x - (char *)0) + +#if defined (alpha) && defined (__GNUC__) && !defined (strchr) && !defined (__STDC__) +extern char *strchr (), *strrchr (); +#endif + +#if !defined (strcpy) && (defined (HAVE_DECL_STRCPY) && !HAVE_DECL_STRCPY) +extern char *strcpy PARAMS((char *, const char *)); +#endif + +#if !defined (savestring) +# define savestring(x) (char *)strcpy (xmalloc (1 + strlen (x)), (x)) +#endif + +#ifndef member +# define member(c, s) ((c) ? ((char *)mbschr ((s), (c)) != (char *)NULL) : 0) +#endif + +#ifndef whitespace +#define whitespace(c) (((c) == ' ') || ((c) == '\t')) +#endif + +#ifndef CHAR_MAX +# ifdef __CHAR_UNSIGNED__ +# define CHAR_MAX 0xff +# else +# define CHAR_MAX 0x7f +# endif +#endif + +#ifndef CHAR_BIT +# define CHAR_BIT 8 +#endif + +/* Nonzero if the integer type T is signed. */ +#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) + +/* The width in bits of the integer type or expression T. + Padding bits are not supported; this is checked at compile-time below. */ +#define TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT) + +/* Bound on length of the string representing an unsigned integer + value representable in B bits. log10 (2.0) < 146/485. The + smallest value of B where this bound is not tight is 2621. */ +#define INT_BITS_STRLEN_BOUND(b) (((b) * 146 + 484) / 485) + +/* Bound on length of the string representing an integer value of type T. + Subtract one for the sign bit if T is signed; + 302 / 1000 is log10 (2) rounded up; + add one for integer division truncation; + add one more for a minus sign if t is signed. */ +#define INT_STRLEN_BOUND(t) \ + ((TYPE_WIDTH (t) - TYPE_SIGNED (t)) * 302 / 1000 \ + + 1 + TYPE_SIGNED (t)) + +/* Updated version adapted from gnulib/intprops.h, not used right now. + Changes the approximation of log10(2) from 302/1000 to 146/485. */ +#if 0 +#define INT_STRLEN_BOUND(t) \ + (INT_BITS_STRLEN_BOUND (TYPE_WIDTH (t) - TYPE_SIGNED (t)) + TYPE_SIGNED(t)) +#endif + +/* Bound on buffer size needed to represent an integer type or expression T, + including the terminating null. */ +#define INT_BUFSIZE_BOUND(t) (INT_STRLEN_BOUND (t) + 1) + +/* Define exactly what a legal shell identifier consists of. */ +#define legal_variable_starter(c) (ISALPHA(c) || (c == '_')) +#define legal_variable_char(c) (ISALNUM(c) || c == '_') + +/* Definitions used in subst.c and by the `read' builtin for field + splitting. */ +#define spctabnl(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') + +/* All structs which contain a `next' field should have that field + as the first field in the struct. This means that functions + can be written to handle the general case for linked lists. */ +typedef struct g_list { + struct g_list *next; +} GENERIC_LIST; + +/* Here is a generic structure for associating character strings + with integers. It is used in the parser for shell tokenization. */ +typedef struct { + char *word; + int token; +} STRING_INT_ALIST; + +/* A macro to avoid making an unnecessary function call. */ +#define REVERSE_LIST(list, type) \ + ((list && list->next) ? (type)list_reverse ((GENERIC_LIST *)list) \ + : (type)(list)) + +#if __GNUC__ > 1 +# define FASTCOPY(s, d, n) __builtin_memcpy ((d), (s), (n)) +#else /* !__GNUC__ */ +# if !defined (HAVE_BCOPY) +# if !defined (HAVE_MEMMOVE) +# define FASTCOPY(s, d, n) memcpy ((d), (s), (n)) +# else +# define FASTCOPY(s, d, n) memmove ((d), (s), (n)) +# endif /* !HAVE_MEMMOVE */ +# else /* HAVE_BCOPY */ +# define FASTCOPY(s, d, n) bcopy ((s), (d), (n)) +# endif /* HAVE_BCOPY */ +#endif /* !__GNUC__ */ + +/* String comparisons that possibly save a function call each. */ +#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0) +#define STREQN(a, b, n) ((n == 0) ? (1) \ + : ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)) + +/* More convenience definitions that possibly save system or libc calls. */ +#define STRLEN(s) (((s) && (s)[0]) ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0) +#define FREE(s) do { if (s) free (s); } while (0) +#define MEMBER(c, s) (((c) && c == (s)[0] && !(s)[1]) || (member(c, s))) + +/* A fairly hairy macro to check whether an allocated string has more room, + and to resize it using xrealloc if it does not. + STR is the string (char *) + CIND is the current index into the string (int) + ROOM is the amount of additional room we need in the string (int) + CSIZE is the currently-allocated size of STR (int) + SINCR is how much to increment CSIZE before calling xrealloc (int) */ + +#define RESIZE_MALLOCED_BUFFER(str, cind, room, csize, sincr) \ + do { \ + if ((cind) + (room) >= csize) \ + { \ + while ((cind) + (room) >= csize) \ + csize += (sincr); \ + str = xrealloc (str, csize); \ + } \ + } while (0) + +/* Function pointers can be declared as (Function *)foo. */ +#if !defined (_FUNCTION_DEF) +# define _FUNCTION_DEF +typedef int Function (); +typedef void VFunction (); +typedef char *CPFunction (); /* no longer used */ +typedef char **CPPFunction (); /* no longer used */ +#endif /* _FUNCTION_DEF */ + +#ifndef SH_FUNCTION_TYPEDEF +# define SH_FUNCTION_TYPEDEF + +/* Shell function typedefs with prototypes */ +/* `Generic' function pointer typedefs */ + +typedef int sh_intfunc_t PARAMS((int)); +typedef int sh_ivoidfunc_t PARAMS((void)); +typedef int sh_icpfunc_t PARAMS((char *)); +typedef int sh_icppfunc_t PARAMS((char **)); +typedef int sh_iptrfunc_t PARAMS((PTR_T)); + +typedef void sh_voidfunc_t PARAMS((void)); +typedef void sh_vintfunc_t PARAMS((int)); +typedef void sh_vcpfunc_t PARAMS((char *)); +typedef void sh_vcppfunc_t PARAMS((char **)); +typedef void sh_vptrfunc_t PARAMS((PTR_T)); + +typedef int sh_wdesc_func_t PARAMS((WORD_DESC *)); +typedef int sh_wlist_func_t PARAMS((WORD_LIST *)); + +typedef int sh_glist_func_t PARAMS((GENERIC_LIST *)); + +typedef char *sh_string_func_t PARAMS((char *)); /* like savestring, et al. */ + +typedef int sh_msg_func_t PARAMS((const char *, ...)); /* printf(3)-like */ +typedef void sh_vmsg_func_t PARAMS((const char *, ...)); /* printf(3)-like */ + +/* Specific function pointer typedefs. Most of these could be done + with #defines. */ +typedef void sh_sv_func_t PARAMS((char *)); /* sh_vcpfunc_t */ +typedef void sh_free_func_t PARAMS((PTR_T)); /* sh_vptrfunc_t */ +typedef void sh_resetsig_func_t PARAMS((int)); /* sh_vintfunc_t */ + +typedef int sh_ignore_func_t PARAMS((const char *)); /* sh_icpfunc_t */ + +typedef int sh_assign_func_t PARAMS((const char *)); +typedef int sh_wassign_func_t PARAMS((WORD_DESC *, int)); + +typedef int sh_load_func_t PARAMS((char *)); +typedef void sh_unload_func_t PARAMS((char *)); + +typedef int sh_builtin_func_t PARAMS((WORD_LIST *)); /* sh_wlist_func_t */ + +#endif /* SH_FUNCTION_TYPEDEF */ + +#define NOW ((time_t) time ((time_t *) 0)) +#define GETTIME(tv) gettimeofday(&(tv), NULL) + +/* Some defines for calling file status functions. */ +#define FS_EXISTS 0x1 +#define FS_EXECABLE 0x2 +#define FS_EXEC_PREFERRED 0x4 +#define FS_EXEC_ONLY 0x8 +#define FS_DIRECTORY 0x10 +#define FS_NODIRS 0x20 +#define FS_READABLE 0x40 + +/* Default maximum for move_to_high_fd */ +#define HIGH_FD_MAX 256 + +/* The type of function passed as the fourth argument to qsort(3). */ +#ifdef __STDC__ +typedef int QSFUNC (const void *, const void *); +#else +typedef int QSFUNC (); +#endif + +/* Some useful definitions for Unix pathnames. Argument convention: + x == string, c == character */ + +#if !defined (__CYGWIN__) +# define ABSPATH(x) ((x)[0] == '/') +# define RELPATH(x) ((x)[0] != '/') +#else /* __CYGWIN__ */ +# define ABSPATH(x) (((x)[0] && ISALPHA((unsigned char)(x)[0]) && (x)[1] == ':') || ISDIRSEP((x)[0])) +# define RELPATH(x) (ABSPATH(x) == 0) +#endif /* __CYGWIN__ */ + +#define ROOTEDPATH(x) (ABSPATH(x)) + +#define DIRSEP '/' +#if !defined (__CYGWIN__) +# define ISDIRSEP(c) ((c) == '/') +#else +# define ISDIRSEP(c) ((c) == '/' || (c) == '\\') +#endif /* __CYGWIN__ */ +#define PATHSEP(c) (ISDIRSEP(c) || (c) == 0) + +#define DOT_OR_DOTDOT(s) (s[0] == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0))) + +#if defined (HANDLE_MULTIBYTE) +#define WDOT_OR_DOTDOT(w) (w[0] == L'.' && (w[1] == L'\0' || (w[1] == L'.' && w[2] == L'\0'))) +#endif + +#if 0 +/* Declarations for functions defined in xmalloc.c */ +extern PTR_T xmalloc PARAMS((size_t)); +extern PTR_T xrealloc PARAMS((void *, size_t)); +extern void xfree PARAMS((void *)); +#endif + +/* Declarations for functions defined in general.c */ +extern void posix_initialize PARAMS((int)); + +extern int num_posix_options PARAMS((void)); +extern char *get_posix_options PARAMS((char *)); +extern void set_posix_options PARAMS((const char *)); + +extern void save_posix_options PARAMS((void)); + +#if defined (RLIMTYPE) +extern RLIMTYPE string_to_rlimtype PARAMS((char *)); +extern void print_rlimtype PARAMS((RLIMTYPE, int)); +#endif + +extern int all_digits PARAMS((const char *)); +extern int legal_number PARAMS((const char *, intmax_t *)); +extern int legal_identifier PARAMS((const char *)); +extern int importable_function_name PARAMS((const char *, size_t)); +extern int exportable_function_name PARAMS((const char *)); +extern int check_identifier PARAMS((WORD_DESC *, int)); +extern int valid_nameref_value PARAMS((const char *, int)); +extern int check_selfref PARAMS((const char *, char *, int)); +extern int legal_alias_name PARAMS((const char *, int)); +extern int line_isblank PARAMS((const char *)); +extern int assignment PARAMS((const char *, int)); + +extern int sh_unset_nodelay_mode PARAMS((int)); +extern int sh_setclexec PARAMS((int)); +extern int sh_validfd PARAMS((int)); +extern int fd_ispipe PARAMS((int)); +extern void check_dev_tty PARAMS((void)); +extern int move_to_high_fd PARAMS((int, int, int)); +extern int check_binary_file PARAMS((const char *, int)); + +#ifdef _POSIXSTAT_H_ +extern int same_file PARAMS((const char *, const char *, struct stat *, struct stat *)); +#endif + +extern int sh_openpipe PARAMS((int *)); +extern int sh_closepipe PARAMS((int *)); + +extern int file_exists PARAMS((const char *)); +extern int file_isdir PARAMS((const char *)); +extern int file_iswdir PARAMS((const char *)); +extern int path_dot_or_dotdot PARAMS((const char *)); +extern int absolute_pathname PARAMS((const char *)); +extern int absolute_program PARAMS((const char *)); + +extern char *make_absolute PARAMS((const char *, const char *)); +extern char *base_pathname PARAMS((char *)); +extern char *full_pathname PARAMS((char *)); +extern char *polite_directory_format PARAMS((char *)); +extern char *trim_pathname PARAMS((char *, int)); +extern char *printable_filename PARAMS((char *, int)); + +extern char *extract_colon_unit PARAMS((char *, int *)); + +extern void tilde_initialize PARAMS((void)); +extern char *bash_tilde_find_word PARAMS((const char *, int, int *)); +extern char *bash_tilde_expand PARAMS((const char *, int)); + +extern int group_member PARAMS((gid_t)); +extern char **get_group_list PARAMS((int *)); +extern int *get_group_array PARAMS((int *)); + +extern char *conf_standard_path PARAMS((void)); +extern int default_columns PARAMS((void)); + +#endif /* _GENERAL_H_ */ diff --git a/third_party/bash/getenv.c b/third_party/bash/getenv.c new file mode 100644 index 000000000..1704a3a64 --- /dev/null +++ b/third_party/bash/getenv.c @@ -0,0 +1,233 @@ +/* getenv.c - get environment variable value from the shell's variable + list. */ + +/* Copyright (C) 1997-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (CAN_REDEFINE_GETENV) + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include +#include "shell.h" + +#ifndef errno +extern int errno; +#endif + +extern char **environ; + +/* We supply our own version of getenv () because we want library + routines to get the changed values of exported variables. */ + +/* The NeXT C library has getenv () defined and used in the same file. + This screws our scheme. However, Bash will run on the NeXT using + the C library getenv (), since right now the only environment variable + that we care about is HOME, and that is already defined. */ +static char *last_tempenv_value = (char *)NULL; + +char * +getenv (name) + const char *name; +{ + SHELL_VAR *var; + + if (name == 0 || *name == '\0') + return ((char *)NULL); + + var = find_tempenv_variable ((char *)name); + if (var) + { + FREE (last_tempenv_value); + + last_tempenv_value = value_cell (var) ? savestring (value_cell (var)) : (char *)NULL; + return (last_tempenv_value); + } + else if (shell_variables) + { + var = find_variable ((char *)name); + if (var && exported_p (var)) + return (value_cell (var)); + } + else if (environ) + { + register int i, len; + + /* In some cases, s5r3 invokes getenv() before main(); BSD systems + using gprof also exhibit this behavior. This means that + shell_variables will be 0 when this is invoked. We look up the + variable in the real environment in that case. */ + + for (i = 0, len = strlen (name); environ[i]; i++) + { + if ((STREQN (environ[i], name, len)) && (environ[i][len] == '=')) + return (environ[i] + len + 1); + } + } + + return ((char *)NULL); +} + +/* Some versions of Unix use _getenv instead. */ +char * +_getenv (name) + const char *name; +{ + return (getenv (name)); +} + +/* SUSv3 says argument is a `char *'; BSD implementations disagree */ +int +putenv (str) +#ifndef HAVE_STD_PUTENV + const char *str; +#else + char *str; +#endif +{ + SHELL_VAR *var; + char *name, *value; + int offset; + + if (str == 0 || *str == '\0') + { + errno = EINVAL; + return -1; + } + + offset = assignment (str, 0); + if (str[offset] != '=') + { + errno = EINVAL; + return -1; + } + name = savestring (str); + name[offset] = 0; + + value = name + offset + 1; + + /* XXX - should we worry about readonly here? */ + var = bind_variable (name, value, 0); + if (var == 0) + { + errno = EINVAL; + return -1; + } + + VUNSETATTR (var, att_invisible); + VSETATTR (var, att_exported); + + return 0; +} + +#if 0 +int +_putenv (name) +#ifndef HAVE_STD_PUTENV + const char *name; +#else + char *name; +#endif +{ + return putenv (name); +} +#endif + +int +setenv (name, value, rewrite) + const char *name; + const char *value; + int rewrite; +{ + SHELL_VAR *var; + char *v; + + if (name == 0 || *name == '\0' || strchr (name, '=') != 0) + { + errno = EINVAL; + return -1; + } + + var = 0; + v = (char *)value; /* some compilers need explicit cast */ + /* XXX - should we worry about readonly here? */ + if (rewrite == 0) + var = find_variable (name); + + if (var == 0) + var = bind_variable (name, v, 0); + + if (var == 0) + return -1; + + VUNSETATTR (var, att_invisible); + VSETATTR (var, att_exported); + + return 0; +} + +#if 0 +int +_setenv (name, value, rewrite) + const char *name; + const char *value; + int rewrite; +{ + return setenv (name, value, rewrite); +} +#endif + +/* SUSv3 says unsetenv returns int; existing implementations (BSD) disagree. */ + +#ifdef HAVE_STD_UNSETENV +#define UNSETENV_RETURN(N) return(N) +#define UNSETENV_RETTYPE int +#else +#define UNSETENV_RETURN(N) return +#define UNSETENV_RETTYPE void +#endif + +UNSETENV_RETTYPE +unsetenv (name) + const char *name; +{ + if (name == 0 || *name == '\0' || strchr (name, '=') != 0) + { + errno = EINVAL; + UNSETENV_RETURN(-1); + } + + /* XXX - should we just remove the export attribute here? */ +#if 1 + unbind_variable (name); +#else + SHELL_VAR *v; + + v = find_variable (name); + if (v) + VUNSETATTR (v, att_exported); +#endif + + UNSETENV_RETURN(0); +} +#endif /* CAN_REDEFINE_GETENV */ diff --git a/third_party/bash/getopt.c b/third_party/bash/getopt.c new file mode 100644 index 000000000..25f540cc5 --- /dev/null +++ b/third_party/bash/getopt.c @@ -0,0 +1,355 @@ +/* getopt.c - getopt for Bash. Used by the getopt builtin. */ + +/* Copyright (C) 1993-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "memalloc.h" +#include "bashintl.h" +#include "shell.h" +#include "getopt.h" + +/* For communication from `sh_getopt' to the caller. + When `sh_getopt' finds an option that takes an argument, + the argument value is returned here. */ +char *sh_optarg = 0; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `sh_getopt'. + + On entry to `sh_getopt', zero means this is the first call; initialize. + + When `sh_getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `sh_optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +/* XXX 1003.2 says this must be 1 before any call. */ +int sh_optind = 0; + +/* Index of the current argument. */ +static int sh_curopt; + +/* The next char to be scanned in the option-element + in which the last option character we returned was found. + This allows us to pick up the scan where we left off. + + If this is zero, or a null string, it means resume the scan + by advancing to the next ARGV-element. */ + +static char *nextchar; +static int sh_charindex; + +/* Callers store zero here to inhibit the error message + for unrecognized options. */ + +int sh_opterr = 1; + +/* Set to an option character which was unrecognized. + This must be initialized on some systems to avoid linking in the + system's own getopt implementation. */ + +int sh_optopt = '?'; + +/* Set to 1 when we see an invalid option; public so getopts can reset it. */ +int sh_badopt = 0; + +/* Scan elements of ARGV (whose length is ARGC) for option characters + given in OPTSTRING. + + If an element of ARGV starts with '-', and is not exactly "-" or "--", + then it is an option element. The characters of this element + (aside from the initial '-') are option characters. If `sh_getopt' + is called repeatedly, it returns successively each of the option characters + from each of the option elements. + + If `sh_getopt' finds another option character, it returns that character, + updating `sh_optind' and `nextchar' so that the next call to `sh_getopt' can + resume the scan with the following option character or ARGV-element. + + If there are no more option characters, `sh_getopt' returns `EOF'. + Then `sh_optind' is the index in ARGV of the first ARGV-element + that is not an option. + + OPTSTRING is a string containing the legitimate option characters. + If an option character is seen that is not listed in OPTSTRING, + return '?' after printing an error message. If you set `sh_opterr' to + zero, the error message is suppressed but we still return '?'. + + If a char in OPTSTRING is followed by a colon, that means it wants an arg, + so the following text in the same ARGV-element, or the text of the following + ARGV-element, is returned in `sh_optarg'. */ + +/* 1003.2 specifies the format of this message. */ +#define BADOPT(x) fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], x) +#define NEEDARG(x) fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], x) + +int +sh_getopt (argc, argv, optstring) + int argc; + char *const *argv; + const char *optstring; +{ + char c, *temp; + + sh_optarg = 0; + + if (sh_optind >= argc || sh_optind < 0) /* XXX was sh_optind > argc */ + { + sh_optind = argc; + return (EOF); + } + + /* Initialize the internal data when the first call is made. + Start processing options with ARGV-element 1 (since ARGV-element 0 + is the program name); the sequence of previously skipped + non-option ARGV-elements is empty. */ + + if (sh_optind == 0) + { + sh_optind = 1; + nextchar = (char *)NULL; + } + + if (nextchar == 0 || *nextchar == '\0') + { + /* If we have done all the ARGV-elements, stop the scan. */ + if (sh_optind >= argc) + return EOF; + + temp = argv[sh_optind]; + + /* Special ARGV-element `--' means premature end of options. + Skip it like a null option, and return EOF. */ + if (temp[0] == '-' && temp[1] == '-' && temp[2] == '\0') + { + sh_optind++; + return EOF; + } + + /* If we have come to a non-option, either stop the scan or describe + it to the caller and pass it by. This makes the pseudo-option + `-' mean the end of options, but does not skip over it. */ + if (temp[0] != '-' || temp[1] == '\0') + return EOF; + + /* We have found another option-ARGV-element. + Start decoding its characters. */ + nextchar = argv[sh_curopt = sh_optind] + 1; + sh_charindex = 1; + } + + /* Look at and handle the next option-character. */ + + c = *nextchar++; sh_charindex++; + temp = strchr (optstring, c); + + sh_optopt = c; + + /* Increment `sh_optind' when we start to process its last character. */ + if (nextchar == 0 || *nextchar == '\0') + { + sh_optind++; + nextchar = (char *)NULL; + } + + if (sh_badopt = (temp == NULL || c == ':')) + { + if (sh_opterr) + BADOPT (c); + + return '?'; + } + + if (temp[1] == ':') + { + if (nextchar && *nextchar) + { + /* This is an option that requires an argument. */ + sh_optarg = nextchar; + /* If we end this ARGV-element by taking the rest as an arg, + we must advance to the next element now. */ + sh_optind++; + } + else if (sh_optind == argc) + { + if (sh_opterr) + NEEDARG (c); + + sh_optopt = c; + sh_optarg = ""; /* Needed by getopts. */ + c = (optstring[0] == ':') ? ':' : '?'; + } + else + /* We already incremented `sh_optind' once; + increment it again when taking next ARGV-elt as argument. */ + sh_optarg = argv[sh_optind++]; + nextchar = (char *)NULL; + } + return c; +} + +void +sh_getopt_restore_state (argv) + char **argv; +{ + if (nextchar) + nextchar = argv[sh_curopt] + sh_charindex; +} + +sh_getopt_state_t * +sh_getopt_alloc_istate () +{ + sh_getopt_state_t *ret; + + ret = (sh_getopt_state_t *)xmalloc (sizeof (sh_getopt_state_t)); + return ret; +} + +void +sh_getopt_dispose_istate (gs) + sh_getopt_state_t *gs; +{ + free (gs); +} + +sh_getopt_state_t * +sh_getopt_save_istate () +{ + sh_getopt_state_t *ret; + + ret = sh_getopt_alloc_istate (); + + ret->gs_optarg = sh_optarg; + ret->gs_optind = sh_optind; + ret->gs_curopt = sh_curopt; + ret->gs_nextchar = nextchar; /* XXX */ + ret->gs_charindex = sh_charindex; + ret->gs_flags = 0; /* XXX for later use */ + + return ret; +} + +void +sh_getopt_restore_istate (state) + sh_getopt_state_t *state; +{ + sh_optarg = state->gs_optarg; + sh_optind = state->gs_optind; + sh_curopt = state->gs_curopt; + nextchar = state->gs_nextchar; /* XXX - probably not usable */ + sh_charindex = state->gs_charindex; + + sh_getopt_dispose_istate (state); +} + +#if 0 +void +sh_getopt_debug_restore_state (argv) + char **argv; +{ + if (nextchar && nextchar != argv[sh_curopt] + sh_charindex) + { + itrace("sh_getopt_debug_restore_state: resetting nextchar"); + nextchar = argv[sh_curopt] + sh_charindex; + } +} +#endif + +#ifdef TEST + +/* Compile with -DTEST to make an executable for use in testing + the above definition of `sh_getopt'. */ + +int +main (argc, argv) + int argc; + char **argv; +{ + int c; + int digit_sh_optind = 0; + + while (1) + { + int this_option_sh_optind = sh_optind ? sh_optind : 1; + + c = sh_getopt (argc, argv, "abc:d:0123456789"); + if (c == EOF) + break; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (digit_sh_optind != 0 && digit_sh_optind != this_option_sh_optind) + printf ("digits occur in two different argv-elements.\n"); + digit_sh_optind = this_option_sh_optind; + printf ("option %c\n", c); + break; + + case 'a': + printf ("option a\n"); + break; + + case 'b': + printf ("option b\n"); + break; + + case 'c': + printf ("option c with value `%s'\n", sh_optarg); + break; + + case '?': + break; + + default: + printf ("?? sh_getopt returned character code 0%o ??\n", c); + } + } + + if (sh_optind < argc) + { + printf ("non-option ARGV-elements: "); + while (sh_optind < argc) + printf ("%s ", argv[sh_optind++]); + printf ("\n"); + } + + exit (0); +} + +#endif /* TEST */ diff --git a/third_party/bash/getopt.h b/third_party/bash/getopt.h new file mode 100644 index 000000000..fd9785975 --- /dev/null +++ b/third_party/bash/getopt.h @@ -0,0 +1,82 @@ +/* getopt.h - declarations for getopt. */ + +/* Copyright (C) 1989-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* XXX THIS HAS BEEN MODIFIED FOR INCORPORATION INTO BASH XXX */ + +#ifndef _SH_GETOPT_H +#define _SH_GETOPT_H 1 + +#include "stdc.h" + +/* For communication from `getopt' to the caller. + When `getopt' finds an option that takes an argument, + the argument value is returned here. + Also, when `ordering' is RETURN_IN_ORDER, + each non-option ARGV-element is returned here. */ + +extern char *sh_optarg; + +/* Index in ARGV of the next element to be scanned. + This is used for communication to and from the caller + and for communication between successive calls to `getopt'. + + On entry to `getopt', zero means this is the first call; initialize. + + When `getopt' returns EOF, this is the index of the first of the + non-option elements that the caller should itself scan. + + Otherwise, `sh_optind' communicates from one call to the next + how much of ARGV has been scanned so far. */ + +extern int sh_optind; + +/* Callers store zero here to inhibit the error message `getopt' prints + for unrecognized options. */ + +extern int sh_opterr; + +/* Set to an option character which was unrecognized. */ + +extern int sh_optopt; + +/* Set to 1 when an unrecognized option is encountered. */ +extern int sh_badopt; + +extern int sh_getopt PARAMS((int, char *const *, const char *)); + +typedef struct sh_getopt_state +{ + char *gs_optarg; + int gs_optind; + int gs_curopt; + char *gs_nextchar; + int gs_charindex; + int gs_flags; +} sh_getopt_state_t; + +extern void sh_getopt_restore_state PARAMS((char **)); + +extern sh_getopt_state_t *sh_getopt_alloc_istate PARAMS((void)); +extern void sh_getopt_dispose_istate PARAMS((sh_getopt_state_t *)); + +extern sh_getopt_state_t *sh_getopt_save_istate PARAMS((void)); +extern void sh_getopt_restore_istate PARAMS((sh_getopt_state_t *)); + +#endif /* _SH_GETOPT_H */ diff --git a/third_party/bash/gettext.h b/third_party/bash/gettext.h new file mode 100644 index 000000000..97a1f36d6 --- /dev/null +++ b/third_party/bash/gettext.h @@ -0,0 +1,70 @@ +/* Convenience header for conditional use of GNU . + Copyright (C) 1995-1998, 2000-2002, 2008,2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne-Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _LIBGETTEXT_H +#define _LIBGETTEXT_H 1 + +/* NLS can be disabled through the configure --disable-nls option. */ +#if ENABLE_NLS + +/* Get declarations of GNU message catalog functions. */ +# include + +#else + +/* Solaris /usr/include/locale.h includes /usr/include/libintl.h, which + chokes if dcgettext is defined as a macro. So include it now, to make + later inclusions of a NOP. We don't include + as well because people using "gettext.h" will not include , + and also including would fail on SunOS 4, whereas + is OK. */ +#if defined(__sun) +# include +#endif + +/* Disabled NLS. + The casts to 'const char *' serve the purpose of producing warnings + for invalid uses of the value returned from these functions. + On pre-ANSI systems without 'const', the config.h file is supposed to + contain "#define const". */ +# define gettext(Msgid) ((const char *) (Msgid)) +# define dgettext(Domainname, Msgid) ((const char *) (Msgid)) +# define dcgettext(Domainname, Msgid, Category) ((const char *) (Msgid)) +# define ngettext(Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dngettext(Domainname, Msgid1, Msgid2, N) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \ + ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2)) +# define textdomain(Domainname) ((const char *) (Domainname)) +# define bindtextdomain(Domainname, Dirname) ((const char *) (Dirname)) +# define bind_textdomain_codeset(Domainname, Codeset) ((const char *) (Codeset)) + +#endif + +/* A pseudo function call that serves as a marker for the automated + extraction of messages, but does not call gettext(). The run-time + translation is done at a different place in the code. + The argument, String, should be a literal string. Concatenated strings + and other string expressions won't work. + The macro's expansion is not parenthesized, so that it is suitable as + initializer for static 'char[]' or 'const char[]' variables. */ +#define gettext_noop(String) String + +#endif /* _LIBGETTEXT_H */ diff --git a/third_party/bash/gettimeofday.c b/third_party/bash/gettimeofday.c new file mode 100644 index 000000000..b654c1547 --- /dev/null +++ b/third_party/bash/gettimeofday.c @@ -0,0 +1,35 @@ +/* gettimeofday.c - gettimeofday replacement using time() */ + +/* Copyright (C) 2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if !defined (HAVE_GETTIMEOFDAY) + +#include "posixtime.h" + +/* A version of gettimeofday that just sets tv_sec from time(3) */ +int +gettimeofday (struct timeval *tv, void *tz) +{ + tv->tv_sec = (time_t) time ((time_t *)0); + tv->tv_usec = 0; + return 0; +} +#endif diff --git a/third_party/bash/glob.c b/third_party/bash/glob.c new file mode 100644 index 000000000..fe80d41ec --- /dev/null +++ b/third_party/bash/glob.c @@ -0,0 +1,1609 @@ +/* glob.c -- file-name wildcard pattern matching for Bash. + + Copyright (C) 1985-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne-Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* To whomever it may concern: I have never seen the code which most + Unix programs use to perform this function. I wrote this from scratch + based on specifications for the pattern matching. --RMS. */ + +#include "config.h" + +#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) + #pragma alloca +#endif /* _AIX && RISC6000 && !__GNUC__ */ + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "posixdir.h" +#include "posixstat.h" +#include "shmbutil.h" +#include "xmalloc.h" + +#include "filecntl.h" +#if !defined (F_OK) +# define F_OK 0 +#endif + +#include "stdc.h" +#include "memalloc.h" + +#include + +#include "shell.h" +#include "general.h" + +#include "glob.h" +#include "strmatch.h" + +#if !defined (HAVE_BCOPY) && !defined (bcopy) +# define bcopy(s, d, n) ((void) memcpy ((d), (s), (n))) +#endif /* !HAVE_BCOPY && !bcopy */ + +#if !defined (NULL) +# if defined (__STDC__) +# define NULL ((void *) 0) +# else +# define NULL 0x0 +# endif /* __STDC__ */ +#endif /* !NULL */ + +#if !defined (FREE) +# define FREE(x) if (x) free (x) +#endif + +/* Don't try to alloca() more than this much memory for `struct globval' + in glob_vector() */ +#ifndef ALLOCA_MAX +# define ALLOCA_MAX 100000 +#endif + +struct globval + { + struct globval *next; + char *name; + }; + +extern void throw_to_top_level PARAMS((void)); +extern int sh_eaccess PARAMS((const char *, int)); +extern char *sh_makepath PARAMS((const char *, const char *, int)); +extern int signal_is_pending PARAMS((int)); +extern void run_pending_traps PARAMS((void)); + +extern int extended_glob; + +/* Global variable which controls whether or not * matches .*. + Non-zero means don't match .*. */ +int noglob_dot_filenames = 1; + +/* Global variable which controls whether or not filename globbing + is done without regard to case. */ +int glob_ignore_case = 0; + +/* Global variable controlling whether globbing ever returns . or .. + regardless of the pattern. If set to 1, no glob pattern will ever + match `.' or `..'. Disabled by default. */ +int glob_always_skip_dot_and_dotdot = 1; + +/* Global variable to return to signify an error in globbing. */ +char *glob_error_return; + +static struct globval finddirs_error_return; + +/* Some forward declarations. */ +static int skipname PARAMS((char *, char *, int)); +#if HANDLE_MULTIBYTE +static int mbskipname PARAMS((char *, char *, int)); +#endif +void udequote_pathname PARAMS((char *)); +#if HANDLE_MULTIBYTE +void wcdequote_pathname PARAMS((wchar_t *)); +static void wdequote_pathname PARAMS((char *)); +static void dequote_pathname PARAMS((char *)); +#else +# define dequote_pathname(p) udequote_pathname(p) +#endif +static int glob_testdir PARAMS((char *, int)); +static char **glob_dir_to_array PARAMS((char *, char **, int)); + +/* Make sure these names continue to agree with what's in smatch.c */ +extern char *glob_patscan PARAMS((char *, char *, int)); +extern wchar_t *glob_patscan_wc PARAMS((wchar_t *, wchar_t *, int)); + +/* And this from gmisc.c/gm_loop.c */ +extern int wextglob_pattern_p PARAMS((wchar_t *)); + +extern char *glob_dirscan PARAMS((char *, int)); + +/* Compile `glob_loop.inc' for single-byte characters. */ +#define GCHAR unsigned char +#define CHAR char +#define INT int +#define L(CS) CS +#define INTERNAL_GLOB_PATTERN_P internal_glob_pattern_p +#include "glob_loop.inc" + +/* Compile `glob_loop.inc' again for multibyte characters. */ +#if HANDLE_MULTIBYTE + +#define GCHAR wchar_t +#define CHAR wchar_t +#define INT wint_t +#define L(CS) L##CS +#define INTERNAL_GLOB_PATTERN_P internal_glob_wpattern_p +#include "glob_loop.inc" + +#endif /* HANDLE_MULTIBYTE */ + +/* And now a function that calls either the single-byte or multibyte version + of internal_glob_pattern_p. */ +int +glob_pattern_p (pattern) + const char *pattern; +{ +#if HANDLE_MULTIBYTE + size_t n; + wchar_t *wpattern; + int r; + + if (MB_CUR_MAX == 1 || mbsmbchar (pattern) == 0) + return (internal_glob_pattern_p ((unsigned char *)pattern)); + + /* Convert strings to wide chars, and call the multibyte version. */ + n = xdupmbstowcs (&wpattern, NULL, pattern); + if (n == (size_t)-1) + /* Oops. Invalid multibyte sequence. Try it as single-byte sequence. */ + return (internal_glob_pattern_p ((unsigned char *)pattern)); + + r = internal_glob_wpattern_p (wpattern); + free (wpattern); + + return r; +#else + return (internal_glob_pattern_p ((unsigned char *)pattern)); +#endif +} + +#if EXTENDED_GLOB + +#if defined (HANDLE_MULTIBYTE) +# define XSKIPNAME(p, d, f) mbskipname(p, d, f) +#else +# define XSKIPNAME(p, d, f) skipname(p, d, f) +#endif + +/* Return 1 if all subpatterns in the extended globbing pattern PAT indicate + that the name should be skipped. XXX - doesn't handle pattern negation, + not sure if it should */ +static int +extglob_skipname (pat, dname, flags) + char *pat, *dname; + int flags; +{ + char *pp, *pe, *t, *se; + int n, r, negate, wild, nullpat, xflags; + + negate = *pat == '!'; + wild = *pat == '*' || *pat == '?'; + pp = pat + 2; + se = pp + strlen (pp); /* end of pattern string */ + pe = glob_patscan (pp, se, 0); /* end of extglob pattern */ + + /* if pe == 0, this is an invalid extglob pattern */ + if (pe == 0) + return 0; + + xflags = flags | ( negate ? GX_NEGATE : 0); + + /* if pe != se we have more of the pattern at the end of the extglob + pattern. Check the easy case first ( */ + if (pe == se && *pe == 0 && pe[-1] == ')' && (t = strchr (pp, '|')) == 0) + { + pe[-1] = '\0'; + /* This is where we check whether the pattern is being negated and + match all files beginning with `.' if the pattern begins with a + literal `.'. */ + r = XSKIPNAME (pp, dname, xflags); /*(*/ + pe[-1] = ')'; + return r; + } + + /* Is the extglob pattern between the parens the null pattern? The null + pattern can match nothing, so should we check any remaining portion of + the pattern? */ + nullpat = pe >= (pat + 2) && pe[-2] == '(' && pe[-1] == ')'; + + /* check every subpattern */ + while (t = glob_patscan (pp, pe, '|')) + { + /* If T == PE and *T == 0 (&& PE[-1] == RPAREN), we have hit the end + of a pattern with no trailing characters. */ + n = t[-1]; /* ( */ + if (extglob_pattern_p (pp) && n == ')') /* nested extglob? */ + t[-1] = n; /* no-op for now */ + else + t[-1] = '\0'; + r = XSKIPNAME (pp, dname, xflags); + t[-1] = n; + if (r == 0) /* if any pattern says not skip, we don't skip */ + return r; + pp = t; + if (pp == pe) + break; + } + + /* glob_patscan might find end of string */ + if (pp == se) + return r; + + /* but if it doesn't then we didn't match a leading dot */ + if (wild && *pe) /* if we can match zero instances, check further */ + return (XSKIPNAME (pe, dname, flags)); + + return 1; +} +#endif + +/* Return 1 if DNAME should be skipped according to PAT. Mostly concerned + with matching leading `.'. */ +static int +skipname (pat, dname, flags) + char *pat; + char *dname; + int flags; +{ + int i; + +#if EXTENDED_GLOB + if (extglob_pattern_p (pat)) /* XXX */ + return (extglob_skipname (pat, dname, flags)); +#endif + + if (glob_always_skip_dot_and_dotdot && DOT_OR_DOTDOT (dname)) + return 1; + + /* If a leading dot need not be explicitly matched, and the pattern + doesn't start with a `.', don't match `.' or `..' */ + if (noglob_dot_filenames == 0 && pat[0] != '.' && + (pat[0] != '\\' || pat[1] != '.') && + DOT_OR_DOTDOT (dname)) + return 1; + +#if 0 + /* This is where we check whether the pattern is being negated and + match all files beginning with `.' if the pattern begins with a + literal `.'. This is the negation of the next clause. */ + else if ((flags & GX_NEGATE) && noglob_dot_filenames == 0 && + dname[0] == '.' && + (pat[0] == '.' || (pat[0] == '\\' && pat[1] == '.'))) + return 0; +#endif + + /* If a dot must be explicitly matched, check to see if they do. */ + else if (noglob_dot_filenames && dname[0] == '.' && + pat[0] != '.' && (pat[0] != '\\' || pat[1] != '.')) + return 1; + + return 0; +} + +#if HANDLE_MULTIBYTE + +static int +wskipname (pat, dname, flags) + wchar_t *pat, *dname; + int flags; +{ + int i; + + if (glob_always_skip_dot_and_dotdot && WDOT_OR_DOTDOT (dname)) + return 1; + + /* If a leading dot need not be explicitly matched, and the + pattern doesn't start with a `.', don't match `.' or `..' */ + if (noglob_dot_filenames == 0 && pat[0] != L'.' && + (pat[0] != L'\\' || pat[1] != L'.') && + WDOT_OR_DOTDOT (dname)) + return 1; + +#if 0 + /* This is where we check whether the pattern is being negated and + match all files beginning with `.' if the pattern begins with a + literal `.'. This is the negation of the next clause. */ + else if ((flags & GX_NEGATE) && noglob_dot_filenames == 0 && + dname[0] == L'.' && + (pat[0] == L'.' || (pat[0] == L'\\' && pat[1] == L'.'))) + return 0; +#endif + + /* If a leading dot must be explicitly matched, check to see if the + pattern and dirname both have one. */ + else if (noglob_dot_filenames && dname[0] == L'.' && + pat[0] != L'.' && (pat[0] != L'\\' || pat[1] != L'.')) + return 1; + + return 0; +} + +static int +wextglob_skipname (pat, dname, flags) + wchar_t *pat, *dname; + int flags; +{ +#if EXTENDED_GLOB + wchar_t *pp, *pe, *t, *se, n; + int r, negate, wild, nullpat, xflags; + + negate = *pat == L'!'; + wild = *pat == L'*' || *pat == L'?'; + pp = pat + 2; + se = pp + wcslen (pp); + pe = glob_patscan_wc (pp, se, 0); + + /* if pe == 0, this is an invalid extglob pattern */ + if (pe == 0) + return 0; + + xflags = flags | ( negate ? GX_NEGATE : 0); + + /* if pe != se we have more of the pattern at the end of the extglob + pattern. Check the easy case first ( */ + if (pe == se && *pe == L'\0' && pe[-1] == L')' && (t = wcschr (pp, L'|')) == 0) + { + pe[-1] = L'\0'; + r = wskipname (pp, dname, xflags); /*(*/ + pe[-1] = L')'; + return r; + } + + /* Is the extglob pattern between the parens the null pattern? The null + pattern can match nothing, so should we check any remaining portion of + the pattern? */ + nullpat = pe >= (pat + 2) && pe[-2] == L'(' && pe[-1] == L')'; + + /* check every subpattern */ + while (t = glob_patscan_wc (pp, pe, '|')) + { + n = t[-1]; /* ( */ + if (wextglob_pattern_p (pp) && n == L')') /* nested extglob? */ + t[-1] = n; /* no-op for now */ + else + t[-1] = L'\0'; + r = wskipname (pp, dname, xflags); + t[-1] = n; + if (r == 0) + return 0; + pp = t; + if (pp == pe) + break; + } + + /* glob_patscan_wc might find end of string */ + if (pp == se) + return r; + + /* but if it doesn't then we didn't match a leading dot */ + if (wild && *pe != L'\0') + return (wskipname (pe, dname, flags)); + + return 1; +#else + return (wskipname (pat, dname, flags)); +#endif +} + +/* Return 1 if DNAME should be skipped according to PAT. Handles multibyte + characters in PAT and DNAME. Mostly concerned with matching leading `.'. */ +static int +mbskipname (pat, dname, flags) + char *pat, *dname; + int flags; +{ + int ret, ext; + wchar_t *pat_wc, *dn_wc; + size_t pat_n, dn_n; + + if (mbsmbchar (dname) == 0 && mbsmbchar (pat) == 0) + return (skipname (pat, dname, flags)); + + ext = 0; +#if EXTENDED_GLOB + ext = extglob_pattern_p (pat); +#endif + + pat_wc = dn_wc = (wchar_t *)NULL; + + pat_n = xdupmbstowcs (&pat_wc, NULL, pat); + if (pat_n != (size_t)-1) + dn_n = xdupmbstowcs (&dn_wc, NULL, dname); + + ret = 0; + if (pat_n != (size_t)-1 && dn_n !=(size_t)-1) + ret = ext ? wextglob_skipname (pat_wc, dn_wc, flags) : wskipname (pat_wc, dn_wc, flags); + else + ret = skipname (pat, dname, flags); + + FREE (pat_wc); + FREE (dn_wc); + + return ret; +} +#endif /* HANDLE_MULTIBYTE */ + +/* Remove backslashes quoting characters in PATHNAME by modifying PATHNAME. */ +void +udequote_pathname (pathname) + char *pathname; +{ + register int i, j; + + for (i = j = 0; pathname && pathname[i]; ) + { + if (pathname[i] == '\\') + i++; + + pathname[j++] = pathname[i++]; + + if (pathname[i - 1] == 0) + break; + } + if (pathname) + pathname[j] = '\0'; +} + +#if HANDLE_MULTIBYTE +/* Remove backslashes quoting characters in PATHNAME by modifying PATHNAME. */ +void +wcdequote_pathname (wpathname) + wchar_t *wpathname; +{ + int i, j; + + for (i = j = 0; wpathname && wpathname[i]; ) + { + if (wpathname[i] == L'\\') + i++; + + wpathname[j++] = wpathname[i++]; + + if (wpathname[i - 1] == L'\0') + break; + } + if (wpathname) + wpathname[j] = L'\0'; +} + +static void +wdequote_pathname (pathname) + char *pathname; +{ + mbstate_t ps; + size_t len, n; + wchar_t *wpathname; + int i, j; + wchar_t *orig_wpathname; + + if (mbsmbchar (pathname) == 0) + { + udequote_pathname (pathname); + return; + } + + len = strlen (pathname); + /* Convert the strings into wide characters. */ + n = xdupmbstowcs (&wpathname, NULL, pathname); + if (n == (size_t) -1) + { + /* Something wrong. Fall back to single-byte */ + udequote_pathname (pathname); + return; + } + orig_wpathname = wpathname; + + wcdequote_pathname (wpathname); + + /* Convert the wide character string into unibyte character set. */ + memset (&ps, '\0', sizeof(mbstate_t)); + n = wcsrtombs(pathname, (const wchar_t **)&wpathname, len, &ps); + if (n == (size_t)-1 || (wpathname && *wpathname != 0)) /* what? now you tell me? */ + { + wpathname = orig_wpathname; + memset (&ps, '\0', sizeof(mbstate_t)); + n = xwcsrtombs (pathname, (const wchar_t **)&wpathname, len, &ps); + } + pathname[len] = '\0'; + + /* Can't just free wpathname here; wcsrtombs changes it in many cases. */ + free (orig_wpathname); +} + +static void +dequote_pathname (pathname) + char *pathname; +{ + if (MB_CUR_MAX > 1) + wdequote_pathname (pathname); + else + udequote_pathname (pathname); +} +#endif /* HANDLE_MULTIBYTE */ + +/* Test whether NAME exists. */ + +#if defined (HAVE_LSTAT) +# define GLOB_TESTNAME(name) (lstat (name, &finfo)) +#else /* !HAVE_LSTAT */ +# if !defined (AFS) +# define GLOB_TESTNAME(name) (sh_eaccess (name, F_OK)) +# else /* AFS */ +# define GLOB_TESTNAME(name) (access (name, F_OK)) +# endif /* AFS */ +#endif /* !HAVE_LSTAT */ + +/* Return 0 if DIR is a directory, -2 if DIR is a symlink, -1 otherwise. */ +static int +glob_testdir (dir, flags) + char *dir; + int flags; +{ + struct stat finfo; + int r; + +/*itrace("glob_testdir: testing %s" flags = %d, dir, flags);*/ +#if defined (HAVE_LSTAT) + r = (flags & GX_ALLDIRS) ? lstat (dir, &finfo) : stat (dir, &finfo); +#else + r = stat (dir, &finfo); +#endif + if (r < 0) + return (-1); + +#if defined (S_ISLNK) + if (S_ISLNK (finfo.st_mode)) + return (-2); +#endif + + if (S_ISDIR (finfo.st_mode) == 0) + return (-1); + + return (0); +} + +/* Recursively scan SDIR for directories matching PAT (PAT is always `**'). + FLAGS is simply passed down to the recursive call to glob_vector. Returns + a list of matching directory names. EP, if non-null, is set to the last + element of the returned list. NP, if non-null, is set to the number of + directories in the returned list. These two variables exist for the + convenience of the caller (always glob_vector). */ +static struct globval * +finddirs (pat, sdir, flags, ep, np) + char *pat; + char *sdir; + int flags; + struct globval **ep; + int *np; +{ + char **r, *n; + int ndirs; + struct globval *ret, *e, *g; + +/*itrace("finddirs: pat = `%s' sdir = `%s' flags = 0x%x", pat, sdir, flags);*/ + e = ret = 0; + r = glob_vector (pat, sdir, flags); + if (r == 0 || r[0] == 0) + { + if (np) + *np = 0; + if (ep) + *ep = 0; + if (r && r != &glob_error_return) + free (r); + return (struct globval *)0; + } + for (ndirs = 0; r[ndirs] != 0; ndirs++) + { + g = (struct globval *) malloc (sizeof (struct globval)); + if (g == 0) + { + while (ret) /* free list built so far */ + { + g = ret->next; + free (ret); + ret = g; + } + + free (r); + if (np) + *np = 0; + if (ep) + *ep = 0; + return (&finddirs_error_return); + } + if (e == 0) + e = g; + + g->next = ret; + ret = g; + + g->name = r[ndirs]; + } + + free (r); + if (ep) + *ep = e; + if (np) + *np = ndirs; + + return ret; +} + +/* Return a vector of names of files in directory DIR + whose names match glob pattern PAT. + The names are not in any particular order. + Wildcards at the beginning of PAT do not match an initial period. + + The vector is terminated by an element that is a null pointer. + + To free the space allocated, first free the vector's elements, + then free the vector. + + Return 0 if cannot get enough memory to hold the pointer + and the names. + + Return -1 if cannot access directory DIR. + Look in errno for more information. */ + +char ** +glob_vector (pat, dir, flags) + char *pat; + char *dir; + int flags; +{ + DIR *d; + register struct dirent *dp; + struct globval *lastlink, *e, *dirlist; + register struct globval *nextlink; + register char *nextname, *npat, *subdir; + unsigned int count; + int lose, skip, ndirs, isdir, sdlen, add_current, patlen; + register char **name_vector; + register unsigned int i; + int mflags; /* Flags passed to strmatch (). */ + int pflags; /* flags passed to sh_makepath () */ + int hasglob; /* return value from glob_pattern_p */ + int nalloca; + struct globval *firstmalloc, *tmplink; + char *convfn; + + lastlink = 0; + count = lose = skip = add_current = 0; + + firstmalloc = 0; + nalloca = 0; + + name_vector = NULL; + +/*itrace("glob_vector: pat = `%s' dir = `%s' flags = 0x%x", pat, dir, flags);*/ + /* If PAT is empty, skip the loop, but return one (empty) filename. */ + if (pat == 0 || *pat == '\0') + { + if (glob_testdir (dir, 0) < 0) + return ((char **) &glob_error_return); + + nextlink = (struct globval *)alloca (sizeof (struct globval)); + if (nextlink == NULL) + return ((char **) NULL); + + nextlink->next = (struct globval *)0; + nextname = (char *) malloc (1); + if (nextname == 0) + lose = 1; + else + { + lastlink = nextlink; + nextlink->name = nextname; + nextname[0] = '\0'; + count = 1; + } + + skip = 1; + } + + patlen = (pat && *pat) ? strlen (pat) : 0; + + /* If the filename pattern (PAT) does not contain any globbing characters, + or contains a pattern with only backslash escapes (hasglob == 2), + we can dispense with reading the directory, and just see if there is + a filename `DIR/PAT'. If there is, and we can access it, just make the + vector to return and bail immediately. */ + hasglob = 0; + if (skip == 0 && ((hasglob = glob_pattern_p (pat)) == 0 || hasglob == 2)) + { + int dirlen; + struct stat finfo; + + if (glob_testdir (dir, 0) < 0) + return ((char **) &glob_error_return); + + dirlen = strlen (dir); + nextname = (char *)malloc (dirlen + patlen + 2); + npat = (char *)malloc (patlen + 1); + if (nextname == 0 || npat == 0) + { + FREE (nextname); + FREE (npat); + lose = 1; + } + else + { + strcpy (npat, pat); + dequote_pathname (npat); + + strcpy (nextname, dir); + nextname[dirlen++] = '/'; + strcpy (nextname + dirlen, npat); + + if (GLOB_TESTNAME (nextname) >= 0) + { + free (nextname); + nextlink = (struct globval *)alloca (sizeof (struct globval)); + if (nextlink) + { + nextlink->next = (struct globval *)0; + lastlink = nextlink; + nextlink->name = npat; + count = 1; + } + else + { + free (npat); + lose = 1; + } + } + else + { + free (nextname); + free (npat); + } + } + + skip = 1; + } + + if (skip == 0) + { + /* Open the directory, punting immediately if we cannot. If opendir + is not robust (i.e., it opens non-directories successfully), test + that DIR is a directory and punt if it's not. */ +#if defined (OPENDIR_NOT_ROBUST) + if (glob_testdir (dir, 0) < 0) + return ((char **) &glob_error_return); +#endif + + d = opendir (dir); + if (d == NULL) + return ((char **) &glob_error_return); + + /* Compute the flags that will be passed to strmatch(). We don't + need to do this every time through the loop. */ + mflags = (noglob_dot_filenames ? FNM_PERIOD : FNM_DOTDOT) | FNM_PATHNAME; + +#ifdef FNM_CASEFOLD + if (glob_ignore_case) + mflags |= FNM_CASEFOLD; +#endif + + if (extended_glob) + mflags |= FNM_EXTMATCH; + + add_current = ((flags & (GX_ALLDIRS|GX_ADDCURDIR)) == (GX_ALLDIRS|GX_ADDCURDIR)); + + /* Scan the directory, finding all names that match For each name that matches, allocate a struct globval + on the stack and store the name in it. + Chain those structs together; lastlink is the front of the chain. */ + while (1) + { + /* Make globbing interruptible in the shell. */ + if (interrupt_state || terminating_signal) + { + lose = 1; + break; + } + else if (signal_is_pending (SIGINT)) /* XXX - make SIGINT traps responsive */ + { + lose = 1; + break; + } + + dp = readdir (d); + if (dp == NULL) + break; + + /* If this directory entry is not to be used, try again. */ + if (REAL_DIR_ENTRY (dp) == 0) + continue; + +#if 0 + if (dp->d_name == 0 || *dp->d_name == 0) + continue; +#endif + +#if HANDLE_MULTIBYTE + if (MB_CUR_MAX > 1 && mbskipname (pat, dp->d_name, flags)) + continue; + else +#endif + if (skipname (pat, dp->d_name, flags)) + continue; + + /* If we're only interested in directories, don't bother with files */ + if (flags & (GX_MATCHDIRS|GX_ALLDIRS)) + { + pflags = (flags & GX_ALLDIRS) ? MP_RMDOT : 0; + if (flags & GX_NULLDIR) + pflags |= MP_IGNDOT; + subdir = sh_makepath (dir, dp->d_name, pflags); + isdir = glob_testdir (subdir, flags); + if (isdir < 0 && (flags & GX_MATCHDIRS)) + { + free (subdir); + continue; + } + } + + if (flags & GX_ALLDIRS) + { + if (isdir == 0) + { + dirlist = finddirs (pat, subdir, (flags & ~GX_ADDCURDIR), &e, &ndirs); + if (dirlist == &finddirs_error_return) + { + free (subdir); + lose = 1; + break; + } + if (ndirs) /* add recursive directories to list */ + { + if (firstmalloc == 0) + firstmalloc = e; + e->next = lastlink; + lastlink = dirlist; + count += ndirs; + } + } + + /* XXX - should we even add this if it's not a directory? */ + nextlink = (struct globval *) malloc (sizeof (struct globval)); + if (firstmalloc == 0) + firstmalloc = nextlink; + sdlen = strlen (subdir); + nextname = (char *) malloc (sdlen + 1); + if (nextlink == 0 || nextname == 0) + { + if (firstmalloc && firstmalloc == nextlink) + firstmalloc = 0; + /* If we reset FIRSTMALLOC we can free this here. */ + FREE (nextlink); + FREE (nextname); + free (subdir); + lose = 1; + break; + } + nextlink->next = lastlink; + lastlink = nextlink; + nextlink->name = nextname; + bcopy (subdir, nextname, sdlen + 1); + free (subdir); + ++count; + continue; + } + else if (flags & GX_MATCHDIRS) + free (subdir); + + convfn = fnx_fromfs (dp->d_name, D_NAMLEN (dp)); + if (strmatch (pat, convfn, mflags) != FNM_NOMATCH) + { + if (nalloca < ALLOCA_MAX) + { + nextlink = (struct globval *) alloca (sizeof (struct globval)); + nalloca += sizeof (struct globval); + } + else + { + nextlink = (struct globval *) malloc (sizeof (struct globval)); + if (firstmalloc == 0) + firstmalloc = nextlink; + } + + nextname = (char *) malloc (D_NAMLEN (dp) + 1); + if (nextlink == 0 || nextname == 0) + { + /* We free NEXTLINK here, since it won't be added to the + LASTLINK chain. If we used malloc, and it returned non- + NULL, firstmalloc will be set to something valid. If it's + NEXTLINK, reset it before we free NEXTLINK to avoid + duplicate frees. If not, it will be taken care of by the + loop below with TMPLINK. */ + if (firstmalloc) + { + if (firstmalloc == nextlink) + firstmalloc = 0; + FREE (nextlink); + } + FREE (nextname); + lose = 1; + break; + } + nextlink->next = lastlink; + lastlink = nextlink; + nextlink->name = nextname; + bcopy (dp->d_name, nextname, D_NAMLEN (dp) + 1); + ++count; + } + } + + (void) closedir (d); + } + + /* compat: if GX_ADDCURDIR, add the passed directory also. Add an empty + directory name as a placeholder if GX_NULLDIR (in which case the passed + directory name is "."). */ + if (add_current && lose == 0) + { + sdlen = strlen (dir); + nextname = (char *)malloc (sdlen + 1); + nextlink = (struct globval *) malloc (sizeof (struct globval)); + if (nextlink == 0 || nextname == 0) + { + FREE (nextlink); + FREE (nextname); + lose = 1; + } + else + { + nextlink->name = nextname; + nextlink->next = lastlink; + lastlink = nextlink; + if (flags & GX_NULLDIR) + nextname[0] = '\0'; + else + bcopy (dir, nextname, sdlen + 1); + ++count; + } + } + + if (lose == 0) + { + name_vector = (char **) malloc ((count + 1) * sizeof (char *)); + lose |= name_vector == NULL; + } + + /* Have we run out of memory or been interrupted? */ + if (lose) + { + tmplink = 0; + + /* Here free the strings we have got. */ + while (lastlink) + { + /* Since we build the list in reverse order, the first N entries + will be allocated with malloc, if firstmalloc is set, from + lastlink to firstmalloc. */ + if (firstmalloc) + { + if (lastlink == firstmalloc) + firstmalloc = 0; + tmplink = lastlink; + } + else + tmplink = 0; + free (lastlink->name); + lastlink = lastlink->next; + FREE (tmplink); + } + + /* Don't call QUIT; here; let higher layers deal with it. */ + + return ((char **)NULL); + } + + /* Copy the name pointers from the linked list into the vector. */ + for (tmplink = lastlink, i = 0; i < count; ++i) + { + name_vector[i] = tmplink->name; + tmplink = tmplink->next; + } + + name_vector[count] = NULL; + + /* If we allocated some of the struct globvals, free them now. */ + if (firstmalloc) + { + tmplink = 0; + while (lastlink) + { + tmplink = lastlink; + if (lastlink == firstmalloc) + lastlink = firstmalloc = 0; + else + lastlink = lastlink->next; + free (tmplink); + } + } + + return (name_vector); +} + +/* Return a new array which is the concatenation of each string in ARRAY + to DIR. This function expects you to pass in an allocated ARRAY, and + it takes care of free()ing that array. Thus, you might think of this + function as side-effecting ARRAY. This should handle GX_MARKDIRS. */ +static char ** +glob_dir_to_array (dir, array, flags) + char *dir, **array; + int flags; +{ + register unsigned int i, l; + int add_slash; + char **result, *new; + struct stat sb; + + l = strlen (dir); + if (l == 0) + { + if (flags & GX_MARKDIRS) + for (i = 0; array[i]; i++) + { + if ((stat (array[i], &sb) == 0) && S_ISDIR (sb.st_mode)) + { + l = strlen (array[i]); + new = (char *)realloc (array[i], l + 2); + if (new == 0) + return NULL; + new[l] = '/'; + new[l+1] = '\0'; + array[i] = new; + } + } + return (array); + } + + add_slash = dir[l - 1] != '/'; + + i = 0; + while (array[i] != NULL) + ++i; + + result = (char **) malloc ((i + 1) * sizeof (char *)); + if (result == NULL) + return (NULL); + + for (i = 0; array[i] != NULL; i++) + { + /* 3 == 1 for NUL, 1 for slash at end of DIR, 1 for GX_MARKDIRS */ + result[i] = (char *) malloc (l + strlen (array[i]) + 3); + + if (result[i] == NULL) + { + int ind; + for (ind = 0; ind < i; ind++) + free (result[ind]); + free (result); + return (NULL); + } + + strcpy (result[i], dir); + if (add_slash) + result[i][l] = '/'; + if (array[i][0]) + { + strcpy (result[i] + l + add_slash, array[i]); + if (flags & GX_MARKDIRS) + { + if ((stat (result[i], &sb) == 0) && S_ISDIR (sb.st_mode)) + { + size_t rlen; + rlen = strlen (result[i]); + result[i][rlen] = '/'; + result[i][rlen+1] = '\0'; + } + } + } + else + result[i][l+add_slash] = '\0'; + } + result[i] = NULL; + + /* Free the input array. */ + for (i = 0; array[i] != NULL; i++) + free (array[i]); + free ((char *) array); + + return (result); +} + +/* Do globbing on PATHNAME. Return an array of pathnames that match, + marking the end of the array with a null-pointer as an element. + If no pathnames match, then the array is empty (first element is null). + If there isn't enough memory, then return NULL. + If a file system error occurs, return -1; `errno' has the error code. */ +char ** +glob_filename (pathname, flags) + char *pathname; + int flags; +{ + char **result, **new_result; + unsigned int result_size; + char *directory_name, *filename, *dname, *fn; + unsigned int directory_len; + int free_dirname; /* flag */ + int dflags, hasglob; + + result = (char **) malloc (sizeof (char *)); + result_size = 1; + if (result == NULL) + return (NULL); + + result[0] = NULL; + + directory_name = NULL; + + /* Find the filename. */ + filename = strrchr (pathname, '/'); +#if defined (EXTENDED_GLOB) + if (filename && extended_glob) + { + fn = glob_dirscan (pathname, '/'); +#if DEBUG_MATCHING + if (fn != filename) + fprintf (stderr, "glob_filename: glob_dirscan: fn (%s) != filename (%s)\n", fn ? fn : "(null)", filename); +#endif + filename = fn; + } +#endif + + if (filename == NULL) + { + filename = pathname; + directory_name = ""; + directory_len = 0; + free_dirname = 0; + } + else + { + directory_len = (filename - pathname) + 1; + directory_name = (char *) malloc (directory_len + 1); + + if (directory_name == 0) /* allocation failed? */ + { + free (result); + return (NULL); + } + + bcopy (pathname, directory_name, directory_len); + directory_name[directory_len] = '\0'; + ++filename; + free_dirname = 1; + } + + hasglob = 0; + /* If directory_name contains globbing characters, then we + have to expand the previous levels. Just recurse. + If glob_pattern_p returns != [0,1] we have a pattern that has backslash + quotes but no unquoted glob pattern characters. We dequote it below. */ + if (directory_len > 0 && (hasglob = glob_pattern_p (directory_name)) == 1) + { + char **directories, *d, *p; + register unsigned int i; + int all_starstar, last_starstar; + + all_starstar = last_starstar = 0; + d = directory_name; + dflags = flags & ~GX_MARKDIRS; + /* Collapse a sequence of ** patterns separated by one or more slashes + to a single ** terminated by a slash or NUL */ + if ((flags & GX_GLOBSTAR) && d[0] == '*' && d[1] == '*' && (d[2] == '/' || d[2] == '\0')) + { + p = d; + while (d[0] == '*' && d[1] == '*' && (d[2] == '/' || d[2] == '\0')) + { + p = d; + if (d[2]) + { + d += 3; + while (*d == '/') + d++; + if (*d == 0) + break; + } + } + if (*d == 0) + all_starstar = 1; + d = p; + dflags |= GX_ALLDIRS|GX_ADDCURDIR; + directory_len = strlen (d); + } + + /* If there is a non [star][star]/ component in directory_name, we + still need to collapse trailing sequences of [star][star]/ into + a single one and note that the directory name ends with [star][star], + so we can compensate if filename is [star][star] */ + if ((flags & GX_GLOBSTAR) && all_starstar == 0) + { + int dl, prev; + prev = dl = directory_len; + while (dl >= 4 && d[dl - 1] == '/' && + d[dl - 2] == '*' && + d[dl - 3] == '*' && + d[dl - 4] == '/') + prev = dl, dl -= 3; + if (dl != directory_len) + last_starstar = 1; + directory_len = prev; + } + + /* If the directory name ends in [star][star]/ but the filename is + [star][star], just remove the final [star][star] from the directory + so we don't have to scan everything twice. */ + if (last_starstar && directory_len > 4 && + filename[0] == '*' && filename[1] == '*' && filename[2] == 0) + { + directory_len -= 3; + } + + if (d[directory_len - 1] == '/') + d[directory_len - 1] = '\0'; + + directories = glob_filename (d, dflags|GX_RECURSE); + + if (free_dirname) + { + free (directory_name); + directory_name = NULL; + } + + if (directories == NULL) + goto memory_error; + else if (directories == (char **)&glob_error_return) + { + free ((char *) result); + return ((char **) &glob_error_return); + } + else if (*directories == NULL) + { + free ((char *) directories); + free ((char *) result); + return ((char **) &glob_error_return); + } + + /* If we have something like [star][star]/[star][star], it's no use to + glob **, then do it again, and throw half the results away. */ + if (all_starstar && filename[0] == '*' && filename[1] == '*' && filename[2] == 0) + { + free ((char *) directories); + free (directory_name); + directory_name = NULL; + directory_len = 0; + goto only_filename; + } + + /* We have successfully globbed the preceding directory name. + For each name in DIRECTORIES, call glob_vector on it and + FILENAME. Concatenate the results together. */ + for (i = 0; directories[i] != NULL; ++i) + { + char **temp_results; + int shouldbreak; + + shouldbreak = 0; + /* XXX -- we've recursively scanned any directories resulting from + a `**', so turn off the flag. We turn it on again below if + filename is `**' */ + /* Scan directory even on a NULL filename. That way, `*h/' + returns only directories ending in `h', instead of all + files ending in `h' with a `/' appended. */ + dname = directories[i]; + dflags = flags & ~(GX_MARKDIRS|GX_ALLDIRS|GX_ADDCURDIR); + /* last_starstar? */ + if ((flags & GX_GLOBSTAR) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0') + dflags |= GX_ALLDIRS|GX_ADDCURDIR; + if (dname[0] == '\0' && filename[0]) + { + dflags |= GX_NULLDIR; + dname = "."; /* treat null directory name and non-null filename as current directory */ + } + + /* Special handling for symlinks to directories with globstar on */ + if (all_starstar && (dflags & GX_NULLDIR) == 0) + { + int dlen; + + /* If we have a directory name that is not null (GX_NULLDIR above) + and is a symlink to a directory, we return the symlink if + we're not `descending' into it (filename[0] == 0) and return + glob_error_return (which causes the code below to skip the + name) otherwise. I should fold this into a test that does both + checks instead of calling stat twice. */ + if (glob_testdir (dname, flags|GX_ALLDIRS) == -2 && glob_testdir (dname, 0) == 0) + { + if (filename[0] != 0) + temp_results = (char **)&glob_error_return; /* skip */ + else + { + /* Construct array to pass to glob_dir_to_array */ + temp_results = (char **)malloc (2 * sizeof (char *)); + if (temp_results == NULL) + goto memory_error; + temp_results[0] = (char *)malloc (1); + if (temp_results[0] == 0) + { + free (temp_results); + goto memory_error; + } + **temp_results = '\0'; + temp_results[1] = NULL; + dflags |= GX_SYMLINK; /* mostly for debugging */ + } + } + else + temp_results = glob_vector (filename, dname, dflags); + } + else + temp_results = glob_vector (filename, dname, dflags); + + /* Handle error cases. */ + if (temp_results == NULL) + goto memory_error; + else if (temp_results == (char **)&glob_error_return) + /* This filename is probably not a directory. Ignore it. */ + ; + else + { + char **array; + register unsigned int l; + + /* If we're expanding **, we don't need to glue the directory + name to the results; we've already done it in glob_vector */ + if ((dflags & GX_ALLDIRS) && filename[0] == '*' && filename[1] == '*' && (filename[2] == '\0' || filename[2] == '/')) + { + /* When do we remove null elements from temp_results? And + how to avoid duplicate elements in the final result? */ + /* If (dflags & GX_NULLDIR) glob_filename potentially left a + NULL placeholder in the temp results just in case + glob_vector/glob_dir_to_array did something with it, but + if it didn't, and we're not supposed to be passing them + through for some reason ((flags & GX_NULLDIR) == 0) we + need to remove all the NULL elements from the beginning + of TEMP_RESULTS. */ + /* If we have a null directory name and ** as the filename, + we have just searched for everything from the current + directory on down. Break now (shouldbreak = 1) to avoid + duplicate entries in the final result. */ +#define NULL_PLACEHOLDER(x) ((x) && *(x) && **(x) == 0) + if ((dflags & GX_NULLDIR) && (flags & GX_NULLDIR) == 0 && + NULL_PLACEHOLDER (temp_results)) +#undef NULL_PLACEHOLDER + { + register int i, n; + for (n = 0; temp_results[n] && *temp_results[n] == 0; n++) + ; + i = n; + do + temp_results[i - n] = temp_results[i]; + while (temp_results[i++] != 0); + array = temp_results; + shouldbreak = 1; + } + else + array = temp_results; + } + else if (dflags & GX_SYMLINK) + array = glob_dir_to_array (directories[i], temp_results, flags); + else + array = glob_dir_to_array (directories[i], temp_results, flags); + l = 0; + while (array[l] != NULL) + ++l; + + new_result = (char **)realloc (result, (result_size + l) * sizeof (char *)); + + if (new_result == NULL) + { + for (l = 0; array[l]; ++l) + free (array[l]); + free ((char *)array); + goto memory_error; + } + result = new_result; + + for (l = 0; array[l] != NULL; ++l) + result[result_size++ - 1] = array[l]; + + result[result_size - 1] = NULL; + + /* Note that the elements of ARRAY are not freed. */ + if (array != temp_results) + free ((char *) array); + else if ((dflags & GX_ALLDIRS) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0') + free (temp_results); /* expanding ** case above */ + + if (shouldbreak) + break; + } + } + /* Free the directories. */ + for (i = 0; directories[i]; i++) + free (directories[i]); + + free ((char *) directories); + + return (result); + } + +only_filename: + /* If there is only a directory name, return it. */ + if (*filename == '\0') + { + result = (char **) realloc ((char *) result, 2 * sizeof (char *)); + if (result == NULL) + { + if (free_dirname) + free (directory_name); + return (NULL); + } + /* If we have a directory name with quoted characters, and we are + being called recursively to glob the directory portion of a pathname, + we need to dequote the directory name before returning it so the + caller can read the directory */ + if (directory_len > 0 && hasglob == 2 && (flags & GX_RECURSE) != 0) + { + dequote_pathname (directory_name); + directory_len = strlen (directory_name); + } + + /* We could check whether or not the dequoted directory_name is a + directory and return it here, returning the original directory_name + if not, but we don't do that. We do return the dequoted directory + name if we're not being called recursively and the dequoted name + corresponds to an actual directory. For better backwards compatibility, + we can return &glob_error_return unconditionally in this case. */ + + if (directory_len > 0 && hasglob == 2 && (flags & GX_RECURSE) == 0) + { + dequote_pathname (directory_name); + if (glob_testdir (directory_name, 0) < 0) + { + if (free_dirname) + free (directory_name); + free ((char *) result); + return ((char **)&glob_error_return); + } + } + + /* Handle GX_MARKDIRS here. */ + result[0] = (char *) malloc (directory_len + 1); + if (result[0] == NULL) + goto memory_error; + bcopy (directory_name, result[0], directory_len + 1); + if (free_dirname) + free (directory_name); + result[1] = NULL; + return (result); + } + else + { + char **temp_results; + + /* There are no unquoted globbing characters in DIRECTORY_NAME. + Dequote it before we try to open the directory since there may + be quoted globbing characters which should be treated verbatim. */ + if (directory_len > 0) + dequote_pathname (directory_name); + + /* We allocated a small array called RESULT, which we won't be using. + Free that memory now. */ + free (result); + + /* Just return what glob_vector () returns appended to the + directory name. */ + /* If flags & GX_ALLDIRS, we're called recursively */ + dflags = flags & ~GX_MARKDIRS; + if (directory_len == 0) + dflags |= GX_NULLDIR; + if ((flags & GX_GLOBSTAR) && filename[0] == '*' && filename[1] == '*' && filename[2] == '\0') + { + dflags |= GX_ALLDIRS|GX_ADDCURDIR; +#if 0 + /* If we want all directories (dflags & GX_ALLDIRS) and we're not + being called recursively as something like `echo [star][star]/[star].o' + ((flags & GX_ALLDIRS) == 0), we want to prevent glob_vector from + adding a null directory name to the front of the temp_results + array. We turn off ADDCURDIR if not called recursively and + dlen == 0 */ +#endif + if (directory_len == 0 && (flags & GX_ALLDIRS) == 0) + dflags &= ~GX_ADDCURDIR; + } + temp_results = glob_vector (filename, + (directory_len == 0 ? "." : directory_name), + dflags); + + if (temp_results == NULL || temp_results == (char **)&glob_error_return) + { + if (free_dirname) + free (directory_name); + QUIT; /* XXX - shell */ + run_pending_traps (); + return (temp_results); + } + + result = glob_dir_to_array ((dflags & GX_ALLDIRS) ? "" : directory_name, temp_results, flags); + + if (free_dirname) + free (directory_name); + return (result); + } + + /* We get to memory_error if the program has run out of memory, or + if this is the shell, and we have been interrupted. */ + memory_error: + if (result != NULL) + { + register unsigned int i; + for (i = 0; result[i] != NULL; ++i) + free (result[i]); + free ((char *) result); + } + + if (free_dirname && directory_name) + free (directory_name); + + QUIT; + run_pending_traps (); + + return (NULL); +} + +#if defined (TEST) + +main (argc, argv) + int argc; + char **argv; +{ + unsigned int i; + + for (i = 1; i < argc; ++i) + { + char **value = glob_filename (argv[i], 0); + if (value == NULL) + puts ("Out of memory."); + else if (value == &glob_error_return) + perror (argv[i]); + else + for (i = 0; value[i] != NULL; i++) + puts (value[i]); + } + + exit (0); +} +#endif /* TEST. */ diff --git a/third_party/bash/glob.h b/third_party/bash/glob.h new file mode 100644 index 000000000..47410577c --- /dev/null +++ b/third_party/bash/glob.h @@ -0,0 +1,47 @@ +/* File-name wildcard pattern matching for GNU. + Copyright (C) 1985-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne-Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _GLOB_H_ +#define _GLOB_H_ + +#include "stdc.h" + +#define GX_MARKDIRS 0x001 /* mark directory names with trailing `/' */ +#define GX_NOCASE 0x002 /* ignore case */ +#define GX_MATCHDOT 0x004 /* match `.' literally */ +#define GX_MATCHDIRS 0x008 /* match only directory names */ +#define GX_ALLDIRS 0x010 /* match all directory names, no others */ +#define GX_NULLDIR 0x100 /* internal -- no directory preceding pattern */ +#define GX_ADDCURDIR 0x200 /* internal -- add passed directory name */ +#define GX_GLOBSTAR 0x400 /* turn on special handling of ** */ +#define GX_RECURSE 0x800 /* internal -- glob_filename called recursively */ +#define GX_SYMLINK 0x1000 /* internal -- symlink to a directory */ +#define GX_NEGATE 0x2000 /* internal -- extglob pattern being negated */ + +extern int glob_pattern_p PARAMS((const char *)); +extern char **glob_vector PARAMS((char *, char *, int)); +extern char **glob_filename PARAMS((char *, int)); + +extern int extglob_pattern_p PARAMS((const char *)); + +extern char *glob_error_return; +extern int noglob_dot_filenames; +extern int glob_ignore_case; + +#endif /* _GLOB_H_ */ diff --git a/third_party/bash/glob_loop.inc b/third_party/bash/glob_loop.inc new file mode 100644 index 000000000..467e7ae33 --- /dev/null +++ b/third_party/bash/glob_loop.inc @@ -0,0 +1,84 @@ +/* Copyright (C) 1991-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +static int INTERNAL_GLOB_PATTERN_P PARAMS((const GCHAR *)); + +/* Return nonzero if PATTERN has any special globbing chars in it. + Compiled twice, once each for single-byte and multibyte characters. */ +static int +INTERNAL_GLOB_PATTERN_P (pattern) + const GCHAR *pattern; +{ + register const GCHAR *p; + register GCHAR c; + int bopen, bsquote; + + p = pattern; + bopen = bsquote = 0; + + while ((c = *p++) != L('\0')) + switch (c) + { + case L('?'): + case L('*'): + return 1; + + case L('['): /* Only accept an open brace if there is a close */ + bopen++; /* brace to match it. Bracket expressions must be */ + continue; /* complete, according to Posix.2 */ + case L(']'): + if (bopen) + return 1; + continue; + + case L('+'): /* extended matching operators */ + case L('@'): + case L('!'): + if (*p == L('(')) /*) */ + return 1; + continue; + + case L('\\'): + /* Don't let the pattern end in a backslash (GMATCH returns no match + if the pattern ends in a backslash anyway), but otherwise note that + we have seen this, since the matching engine uses backslash as an + escape character and it can be removed. We return 2 later if we + have seen only backslash-escaped characters, so interested callers + know they can shortcut and just dequote the pathname. */ + if (*p != L('\0')) + { + p++; + bsquote = 1; + continue; + } + else /* (*p == L('\0')) */ + return 0; + } + +#if 0 + return bsquote ? 2 : 0; +#else + return (0); +#endif +} + +#undef INTERNAL_GLOB_PATTERN_P +#undef L +#undef INT +#undef CHAR +#undef GCHAR diff --git a/third_party/bash/gm_loop.inc b/third_party/bash/gm_loop.inc new file mode 100644 index 000000000..ac516f82c --- /dev/null +++ b/third_party/bash/gm_loop.inc @@ -0,0 +1,208 @@ +/* Copyright (C) 1991-2017 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if EXTENDED_GLOB +int +EXTGLOB_PATTERN_P (pat) + const CHAR *pat; +{ + switch (pat[0]) + { + case L('*'): + case L('+'): + case L('!'): + case L('@'): + case L('?'): + return (pat[1] == L('(')); /* ) */ + default: + return 0; + } + + return 0; +} +#endif + +/* Return 1 of the first character of STRING could match the first + character of pattern PAT. Compiled to both single and wiide character + versions. FLAGS is a subset of strmatch flags; used to do case-insensitive + matching for now. */ +int +MATCH_PATTERN_CHAR (pat, string, flags) + CHAR *pat, *string; + int flags; +{ + CHAR c; + + if (*string == 0) + return (*pat == L('*')); /* XXX - allow only * to match empty string */ + + switch (c = *pat++) + { + default: + return (FOLD(*string) == FOLD(c)); + case L('\\'): + return (FOLD(*string) == FOLD(*pat)); + case L('?'): + return (*pat == L('(') ? 1 : (*string != L'\0')); + case L('*'): + return (1); + case L('+'): + case L('!'): + case L('@'): + return (*pat == L('(') ? 1 : (FOLD(*string) == FOLD(c))); + case L('['): + return (*string != L('\0')); + } +} + +int +MATCHLEN (pat, max) + CHAR *pat; + size_t max; +{ + CHAR c; + int matlen, bracklen, t, in_cclass, in_collsym, in_equiv; + + if (*pat == 0) + return (0); + + matlen = in_cclass = in_collsym = in_equiv = 0; + while (c = *pat++) + { + switch (c) + { + default: + matlen++; + break; + case L('\\'): + if (*pat == 0) + return ++matlen; + else + { + matlen++; + pat++; + } + break; + case L('?'): + if (*pat == LPAREN) + return (matlen = -1); /* XXX for now */ + else + matlen++; + break; + case L('*'): + return (matlen = -1); + case L('+'): + case L('!'): + case L('@'): + if (*pat == LPAREN) + return (matlen = -1); /* XXX for now */ + else + matlen++; + break; + case L('['): + /* scan for ending `]', skipping over embedded [:...:] */ + bracklen = 1; + c = *pat++; + do + { + if (c == 0) + { + pat--; /* back up to NUL */ + matlen += bracklen; + goto bad_bracket; + } + else if (c == L('\\')) + { + /* *pat == backslash-escaped character */ + bracklen++; + /* If the backslash or backslash-escape ends the string, + bail. The ++pat skips over the backslash escape */ + if (*pat == 0 || *++pat == 0) + { + matlen += bracklen; + goto bad_bracket; + } + } + else if (c == L('[') && *pat == L(':')) /* character class */ + { + pat++; + bracklen++; + in_cclass = 1; + } + else if (in_cclass && c == L(':') && *pat == L(']')) + { + pat++; + bracklen++; + in_cclass = 0; + } + else if (c == L('[') && *pat == L('.')) /* collating symbol */ + { + pat++; + bracklen++; + if (*pat == L(']')) /* right bracket can appear as collating symbol */ + { + pat++; + bracklen++; + } + in_collsym = 1; + } + else if (in_collsym && c == L('.') && *pat == L(']')) + { + pat++; + bracklen++; + in_collsym = 0; + } + else if (c == L('[') && *pat == L('=')) /* equivalence class */ + { + pat++; + bracklen++; + if (*pat == L(']')) /* right bracket can appear as equivalence class */ + { + pat++; + bracklen++; + } + in_equiv = 1; + } + else if (in_equiv && c == L('=') && *pat == L(']')) + { + pat++; + bracklen++; + in_equiv = 0; + } + else + bracklen++; + } + while ((c = *pat++) != L(']')); + matlen++; /* bracket expression can only match one char */ +bad_bracket: + break; + } + } + + return matlen; +} + +#undef EXTGLOB_PATTERN_P +#undef MATCH_PATTERN_CHAR +#undef MATCHLEN +#undef FOLD +#undef L +#undef LPAREN +#undef RPAREN +#undef INT +#undef CHAR diff --git a/third_party/bash/gmisc.c b/third_party/bash/gmisc.c new file mode 100644 index 000000000..11d3b0294 --- /dev/null +++ b/third_party/bash/gmisc.c @@ -0,0 +1,108 @@ +/* gmisc.c -- miscellaneous pattern matching utility functions for Bash. + + Copyright (C) 2010-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne-Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "shmbutil.h" +#include "chartypes.h" + +#include "stdc.h" + +#ifndef FNM_CASEFOLD +# include "strmatch.h" +#endif +#include "glob.h" + +/* Make sure these names continue to agree with what's in smatch.c */ +extern char *glob_patscan PARAMS((char *, char *, int)); + +/* Compile `gm_loop.inc' for single-byte characters. */ +#define CHAR char +#define INT int +#define L(CS) CS +#define EXTGLOB_PATTERN_P extglob_pattern_p +#define MATCH_PATTERN_CHAR match_pattern_char +#define MATCHLEN umatchlen +#define FOLD(c) ((flags & FNM_CASEFOLD) \ + ? TOLOWER ((unsigned char)c) \ + : ((unsigned char)c)) +#ifndef LPAREN +#define LPAREN '(' +#define RPAREN ')' +#endif +#include "gm_loop.inc" + +/* Compile `gm_loop.inc' again for multibyte characters. */ +#if HANDLE_MULTIBYTE + +#define CHAR wchar_t +#define INT wint_t +#define L(CS) L##CS +#define EXTGLOB_PATTERN_P wextglob_pattern_p +#define MATCH_PATTERN_CHAR match_pattern_wchar +#define MATCHLEN wmatchlen + +#define FOLD(c) ((flags & FNM_CASEFOLD) && iswupper (c) ? towlower (c) : (c)) +#define LPAREN L'(' +#define RPAREN L')' +#include "gm_loop.inc" + +#endif /* HANDLE_MULTIBYTE */ + + +#if defined (EXTENDED_GLOB) +/* Skip characters in PAT and return the final occurrence of DIRSEP. This + is only called when extended_glob is set, so we have to skip over extglob + patterns x(...) */ +char * +glob_dirscan (pat, dirsep) + char *pat; + int dirsep; +{ + char *p, *d, *pe, *se; + + d = pe = se = 0; + for (p = pat; p && *p; p++) + { + if (extglob_pattern_p (p)) + { + if (se == 0) + se = p + strlen (p) - 1; + pe = glob_patscan (p + 2, se, 0); + if (pe == 0) + continue; + else if (*pe == 0) + break; + p = pe - 1; /* will do increment above */ + continue; + } + if (*p == dirsep) + d = p; + } + return d; +} +#endif /* EXTENDED_GLOB */ diff --git a/third_party/bash/hashcmd.c b/third_party/bash/hashcmd.c new file mode 100644 index 000000000..f6e6f11d1 --- /dev/null +++ b/third_party/bash/hashcmd.c @@ -0,0 +1,195 @@ +/* hashcmd.c - functions for managing a hash table mapping command names to + full pathnames. */ + +/* Copyright (C) 1997-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" + +#include "shell.h" +#include "flags.h" +#include "findcmd.h" +#include "hashcmd.h" + +HASH_TABLE *hashed_filenames = (HASH_TABLE *)NULL; + +static void phash_freedata PARAMS((PTR_T)); + +void +phash_create () +{ + if (hashed_filenames == 0) + hashed_filenames = hash_create (FILENAME_HASH_BUCKETS); +} + +static void +phash_freedata (data) + PTR_T data; +{ + free (((PATH_DATA *)data)->path); + free (data); +} + +void +phash_flush () +{ + if (hashed_filenames) + hash_flush (hashed_filenames, phash_freedata); +} + +/* Remove FILENAME from the table of hashed commands. */ +int +phash_remove (filename) + const char *filename; +{ + register BUCKET_CONTENTS *item; + + if (hashing_enabled == 0 || hashed_filenames == 0) + return 0; + + item = hash_remove (filename, hashed_filenames, 0); + if (item) + { + if (item->data) + phash_freedata (item->data); + free (item->key); + free (item); + return 0; + } + return 1; +} + +/* Place FILENAME (key) and FULL_PATH (data->path) into the + hash table. CHECK_DOT if non-null is for future calls to + phash_search (); it means that this file was found + in a directory in $PATH that is not an absolute pathname. + FOUND is the initial value for times_found. */ +void +phash_insert (filename, full_path, check_dot, found) + char *filename, *full_path; + int check_dot, found; +{ + register BUCKET_CONTENTS *item; + + if (hashing_enabled == 0) + return; + + if (hashed_filenames == 0) + phash_create (); + + item = hash_insert (filename, hashed_filenames, 0); + if (item->data) + free (pathdata(item)->path); + else + { + item->key = savestring (filename); + item->data = xmalloc (sizeof (PATH_DATA)); + } + pathdata(item)->path = savestring (full_path); + pathdata(item)->flags = 0; + if (check_dot) + pathdata(item)->flags |= HASH_CHKDOT; + if (*full_path != '/') + pathdata(item)->flags |= HASH_RELPATH; + item->times_found = found; +} + +/* Return the full pathname that FILENAME hashes to. If FILENAME + is hashed, but (data->flags & HASH_CHKDOT) is non-zero, check + ./FILENAME and return that if it is executable. This always + returns a newly-allocated string; the caller is responsible + for freeing it. */ +char * +phash_search (filename) + const char *filename; +{ + register BUCKET_CONTENTS *item; + char *path, *dotted_filename, *tail; + int same; + + if (hashing_enabled == 0 || hashed_filenames == 0) + return ((char *)NULL); + + item = hash_search (filename, hashed_filenames, 0); + + if (item == NULL) + return ((char *)NULL); + + /* If this filename is hashed, but `.' comes before it in the path, + see if ./filename is executable. If the hashed value is not an + absolute pathname, see if ./`hashed-value' exists. */ + path = pathdata(item)->path; + if (pathdata(item)->flags & (HASH_CHKDOT|HASH_RELPATH)) + { + tail = (pathdata(item)->flags & HASH_RELPATH) ? path : (char *)filename; /* XXX - fix const later */ + /* If the pathname does not start with a `./', add a `./' to it. */ + if (tail[0] != '.' || tail[1] != '/') + { + dotted_filename = (char *)xmalloc (3 + strlen (tail)); + dotted_filename[0] = '.'; dotted_filename[1] = '/'; + strcpy (dotted_filename + 2, tail); + } + else + dotted_filename = savestring (tail); + + if (executable_file (dotted_filename)) + return (dotted_filename); + + free (dotted_filename); + +#if 0 + if (pathdata(item)->flags & HASH_RELPATH) + return ((char *)NULL); +#endif + + /* Watch out. If this file was hashed to "./filename", and + "./filename" is not executable, then return NULL. */ + + /* Since we already know "./filename" is not executable, what + we're really interested in is whether or not the `path' + portion of the hashed filename is equivalent to the current + directory, but only if it starts with a `.'. (This catches + ./. and so on.) same_file () tests general Unix file + equivalence -- same device and inode. */ + if (*path == '.') + { + same = 0; + tail = (char *)strrchr (path, '/'); + + if (tail) + { + *tail = '\0'; + same = same_file (".", path, (struct stat *)NULL, (struct stat *)NULL); + *tail = '/'; + } + + return same ? (char *)NULL : savestring (path); + } + } + + return (savestring (path)); +} diff --git a/third_party/bash/hashcmd.h b/third_party/bash/hashcmd.h new file mode 100644 index 000000000..2459f2004 --- /dev/null +++ b/third_party/bash/hashcmd.h @@ -0,0 +1,43 @@ +/* hashcmd.h - Common defines for hashing filenames. */ + +/* Copyright (C) 1993-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "stdc.h" +#include "hashlib.h" + +#define FILENAME_HASH_BUCKETS 256 /* must be power of two */ + +extern HASH_TABLE *hashed_filenames; + +typedef struct _pathdata { + char *path; /* The full pathname of the file. */ + int flags; +} PATH_DATA; + +#define HASH_RELPATH 0x01 /* this filename is a relative pathname. */ +#define HASH_CHKDOT 0x02 /* check `.' since it was earlier in $PATH */ + +#define pathdata(x) ((PATH_DATA *)(x)->data) + +extern void phash_create PARAMS((void)); +extern void phash_flush PARAMS((void)); + +extern void phash_insert PARAMS((char *, char *, int, int)); +extern int phash_remove PARAMS((const char *)); +extern char *phash_search PARAMS((const char *)); diff --git a/third_party/bash/hashlib.c b/third_party/bash/hashlib.c new file mode 100644 index 000000000..a3c896051 --- /dev/null +++ b/third_party/bash/hashlib.c @@ -0,0 +1,545 @@ +/* hashlib.c -- functions to manage and access hash tables for bash. */ + +/* Copyright (C) 1987,1989,1991,1995,1998,2001,2003,2005,2006,2008,2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashansi.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include + +#include "shell.h" +#include "hashlib.h" + +/* tunable constants for rehashing */ +#define HASH_REHASH_MULTIPLIER 4 +#define HASH_REHASH_FACTOR 2 + +#define HASH_SHOULDGROW(table) \ + ((table)->nentries >= (table)->nbuckets * HASH_REHASH_FACTOR) + +/* an initial approximation */ +#define HASH_SHOULDSHRINK(table) \ + (((table)->nbuckets > DEFAULT_HASH_BUCKETS) && \ + ((table)->nentries < (table)->nbuckets / HASH_REHASH_MULTIPLIER)) + +/* Rely on properties of unsigned division (unsigned/int -> unsigned) and + don't discard the upper 32 bits of the value, if present. */ +#define HASH_BUCKET(s, t, h) (((h) = hash_string (s)) & ((t)->nbuckets - 1)) + +static BUCKET_CONTENTS *copy_bucket_array PARAMS((BUCKET_CONTENTS *, sh_string_func_t *)); + +static void hash_rehash PARAMS((HASH_TABLE *, int)); +static void hash_grow PARAMS((HASH_TABLE *)); +static void hash_shrink PARAMS((HASH_TABLE *)); + +/* Make a new hash table with BUCKETS number of buckets. Initialize + each slot in the table to NULL. */ +HASH_TABLE * +hash_create (buckets) + int buckets; +{ + HASH_TABLE *new_table; + register int i; + + new_table = (HASH_TABLE *)xmalloc (sizeof (HASH_TABLE)); + if (buckets == 0) + buckets = DEFAULT_HASH_BUCKETS; + + new_table->bucket_array = + (BUCKET_CONTENTS **)xmalloc (buckets * sizeof (BUCKET_CONTENTS *)); + new_table->nbuckets = buckets; + new_table->nentries = 0; + + for (i = 0; i < buckets; i++) + new_table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; + + return (new_table); +} + +int +hash_size (table) + HASH_TABLE *table; +{ + return (HASH_ENTRIES(table)); +} + +static BUCKET_CONTENTS * +copy_bucket_array (ba, cpdata) + BUCKET_CONTENTS *ba; + sh_string_func_t *cpdata; /* data copy function */ +{ + BUCKET_CONTENTS *new_bucket, *n, *e; + + if (ba == 0) + return ((BUCKET_CONTENTS *)0); + + for (n = (BUCKET_CONTENTS *)0, e = ba; e; e = e->next) + { + if (n == 0) + { + new_bucket = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); + n = new_bucket; + } + else + { + n->next = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); + n = n->next; + } + + n->key = savestring (e->key); + n->data = e->data ? (cpdata ? (*cpdata) (e->data) : savestring (e->data)) + : NULL; + n->khash = e->khash; + n->times_found = e->times_found; + n->next = (BUCKET_CONTENTS *)NULL; + } + + return new_bucket; +} + +static void +hash_rehash (table, nsize) + HASH_TABLE *table; + int nsize; +{ + int osize, i, j; + BUCKET_CONTENTS **old_bucket_array, *item, *next; + + if (table == NULL || nsize == table->nbuckets) + return; + + osize = table->nbuckets; + old_bucket_array = table->bucket_array; + + table->nbuckets = nsize; + table->bucket_array = (BUCKET_CONTENTS **)xmalloc (table->nbuckets * sizeof (BUCKET_CONTENTS *)); + for (i = 0; i < table->nbuckets; i++) + table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; + + for (j = 0; j < osize; j++) + { + for (item = old_bucket_array[j]; item; item = next) + { + next = item->next; + i = item->khash & (table->nbuckets - 1); + item->next = table->bucket_array[i]; + table->bucket_array[i] = item; + } + } + + free (old_bucket_array); +} + +static void +hash_grow (table) + HASH_TABLE *table; +{ + int nsize; + + nsize = table->nbuckets * HASH_REHASH_MULTIPLIER; + if (nsize > 0) /* overflow */ + hash_rehash (table, nsize); +} + +static void +hash_shrink (table) + HASH_TABLE *table; +{ + int nsize; + + nsize = table->nbuckets / HASH_REHASH_MULTIPLIER; + hash_rehash (table, nsize); +} + +HASH_TABLE * +hash_copy (table, cpdata) + HASH_TABLE *table; + sh_string_func_t *cpdata; +{ + HASH_TABLE *new_table; + int i; + + if (table == 0) + return ((HASH_TABLE *)NULL); + + new_table = hash_create (table->nbuckets); + + for (i = 0; i < table->nbuckets; i++) + new_table->bucket_array[i] = copy_bucket_array (table->bucket_array[i], cpdata); + + new_table->nentries = table->nentries; + return new_table; +} + +/* This is the best 32-bit string hash function I found. It's one of the + Fowler-Noll-Vo family (FNV-1). + + The magic is in the interesting relationship between the special prime + 16777619 (2^24 + 403) and 2^32 and 2^8. */ + +#define FNV_OFFSET 2166136261 +#define FNV_PRIME 16777619 + +/* If you want to use 64 bits, use +FNV_OFFSET 14695981039346656037 +FNV_PRIME 1099511628211 +*/ + +/* The `khash' check below requires that strings that compare equally with + strcmp hash to the same value. */ +unsigned int +hash_string (s) + const char *s; +{ + register unsigned int i; + + for (i = FNV_OFFSET; *s; s++) + { + /* FNV-1a has the XOR first, traditional FNV-1 has the multiply first */ + + /* was i *= FNV_PRIME */ + i += (i<<1) + (i<<4) + (i<<7) + (i<<8) + (i<<24); + i ^= *s; + } + + return i; +} + +/* Return the location of the bucket which should contain the data + for STRING. TABLE is a pointer to a HASH_TABLE. */ + +int +hash_bucket (string, table) + const char *string; + HASH_TABLE *table; +{ + unsigned int h; + + return (HASH_BUCKET (string, table, h)); +} + +/* Return a pointer to the hashed item. If the HASH_CREATE flag is passed, + create a new hash table entry for STRING, otherwise return NULL. */ +BUCKET_CONTENTS * +hash_search (string, table, flags) + const char *string; + HASH_TABLE *table; + int flags; +{ + BUCKET_CONTENTS *list; + int bucket; + unsigned int hv; + + if (table == 0 || ((flags & HASH_CREATE) == 0 && HASH_ENTRIES (table) == 0)) + return (BUCKET_CONTENTS *)NULL; + + bucket = HASH_BUCKET (string, table, hv); + + for (list = table->bucket_array ? table->bucket_array[bucket] : 0; list; list = list->next) + { + /* This is the comparison function */ + if (hv == list->khash && STREQ (list->key, string)) + { + list->times_found++; + return (list); + } + } + + if (flags & HASH_CREATE) + { + if (HASH_SHOULDGROW (table)) + { + hash_grow (table); + bucket = HASH_BUCKET (string, table, hv); + } + + list = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); + list->next = table->bucket_array[bucket]; + table->bucket_array[bucket] = list; + + list->data = NULL; + list->key = (char *)string; /* XXX fix later */ + list->khash = hv; + list->times_found = 0; + + table->nentries++; + return (list); + } + + return (BUCKET_CONTENTS *)NULL; +} + +/* Remove the item specified by STRING from the hash table TABLE. + The item removed is returned, so you can free its contents. If + the item isn't in this table NULL is returned. */ +BUCKET_CONTENTS * +hash_remove (string, table, flags) + const char *string; + HASH_TABLE *table; + int flags; +{ + int bucket; + BUCKET_CONTENTS *prev, *temp; + unsigned int hv; + + if (table == 0 || HASH_ENTRIES (table) == 0) + return (BUCKET_CONTENTS *)NULL; + + bucket = HASH_BUCKET (string, table, hv); + prev = (BUCKET_CONTENTS *)NULL; + for (temp = table->bucket_array[bucket]; temp; temp = temp->next) + { + if (hv == temp->khash && STREQ (temp->key, string)) + { + if (prev) + prev->next = temp->next; + else + table->bucket_array[bucket] = temp->next; + + table->nentries--; + return (temp); + } + prev = temp; + } + return ((BUCKET_CONTENTS *) NULL); +} + +/* Create an entry for STRING, in TABLE. If the entry already + exists, then return it (unless the HASH_NOSRCH flag is set). */ +BUCKET_CONTENTS * +hash_insert (string, table, flags) + char *string; + HASH_TABLE *table; + int flags; +{ + BUCKET_CONTENTS *item; + int bucket; + unsigned int hv; + + if (table == 0) + table = hash_create (0); + + item = (flags & HASH_NOSRCH) ? (BUCKET_CONTENTS *)NULL + : hash_search (string, table, 0); + + if (item == 0) + { + if (HASH_SHOULDGROW (table)) + hash_grow (table); + + bucket = HASH_BUCKET (string, table, hv); + + item = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); + item->next = table->bucket_array[bucket]; + table->bucket_array[bucket] = item; + + item->data = NULL; + item->key = string; + item->khash = hv; + item->times_found = 0; + + table->nentries++; + } + + return (item); +} + +/* Remove and discard all entries in TABLE. If FREE_DATA is non-null, it + is a function to call to dispose of a hash item's data. Otherwise, + free() is called. */ +void +hash_flush (table, free_data) + HASH_TABLE *table; + sh_free_func_t *free_data; +{ + int i; + register BUCKET_CONTENTS *bucket, *item; + + if (table == 0 || HASH_ENTRIES (table) == 0) + return; + + for (i = 0; i < table->nbuckets; i++) + { + bucket = table->bucket_array[i]; + + while (bucket) + { + item = bucket; + bucket = bucket->next; + + if (free_data) + (*free_data) (item->data); + else + free (item->data); + free (item->key); + free (item); + } + table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; + } + + table->nentries = 0; +} + +/* Free the hash table pointed to by TABLE. */ +void +hash_dispose (table) + HASH_TABLE *table; +{ + free (table->bucket_array); + free (table); +} + +void +hash_walk (table, func) + HASH_TABLE *table; + hash_wfunc *func; +{ + register int i; + BUCKET_CONTENTS *item; + + if (table == 0 || HASH_ENTRIES (table) == 0) + return; + + for (i = 0; i < table->nbuckets; i++) + { + for (item = hash_items (i, table); item; item = item->next) + if ((*func) (item) < 0) + return; + } +} + +#if defined (DEBUG) || defined (TEST_HASHING) +void +hash_pstats (table, name) + HASH_TABLE *table; + char *name; +{ + register int slot, bcount; + register BUCKET_CONTENTS *bc; + + if (name == 0) + name = "unknown hash table"; + + fprintf (stderr, "%s: %d buckets; %d items\n", name, table->nbuckets, table->nentries); + + /* Print out a count of how many strings hashed to each bucket, so we can + see how even the distribution is. */ + for (slot = 0; slot < table->nbuckets; slot++) + { + bc = hash_items (slot, table); + + fprintf (stderr, "\tslot %3d: ", slot); + for (bcount = 0; bc; bc = bc->next) + bcount++; + + fprintf (stderr, "%d\n", bcount); + } +} +#endif + +#ifdef TEST_HASHING + +/* link with xmalloc.o and lib/malloc/libmalloc.a */ +#undef NULL +#include + +#ifndef NULL +#define NULL 0 +#endif + +HASH_TABLE *table, *ntable; + +int interrupt_immediately = 0; +int running_trap = 0; + +int +signal_is_trapped (s) + int s; +{ + return (0); +} + +void +programming_error (const char *format, ...) +{ + abort(); +} + +void +fatal_error (const char *format, ...) +{ + abort(); +} + +void +internal_warning (const char *format, ...) +{ +} + +int +main () +{ + char string[256]; + int count = 0; + BUCKET_CONTENTS *tt; + +#if defined (TEST_NBUCKETS) + table = hash_create (TEST_NBUCKETS); +#else + table = hash_create (0); +#endif + + for (;;) + { + char *temp_string; + if (fgets (string, sizeof (string), stdin) == 0) + break; + if (!*string) + break; + temp_string = savestring (string); + tt = hash_insert (temp_string, table, 0); + if (tt->times_found) + { + fprintf (stderr, "You have already added item `%s'\n", string); + free (temp_string); + } + else + { + count++; + } + } + + hash_pstats (table, "hash test"); + + ntable = hash_copy (table, (sh_string_func_t *)NULL); + hash_flush (table, (sh_free_func_t *)NULL); + hash_pstats (ntable, "hash copy test"); + + exit (0); +} + +#endif /* TEST_HASHING */ diff --git a/third_party/bash/hashlib.h b/third_party/bash/hashlib.h new file mode 100644 index 000000000..cf2de9884 --- /dev/null +++ b/third_party/bash/hashlib.h @@ -0,0 +1,92 @@ +/* hashlib.h -- the data structures used in hashing in Bash. */ + +/* Copyright (C) 1993-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_HASHLIB_H_) +#define _HASHLIB_H_ + +#include "stdc.h" + +#ifndef PTR_T +# ifdef __STDC__ +# define PTR_T void * +# else +# define PTR_T char * +# endif +#endif + +typedef struct bucket_contents { + struct bucket_contents *next; /* Link to next hashed key in this bucket. */ + char *key; /* What we look up. */ + PTR_T data; /* What we really want. */ + unsigned int khash; /* What key hashes to */ + int times_found; /* Number of times this item has been found. */ +} BUCKET_CONTENTS; + +typedef struct hash_table { + BUCKET_CONTENTS **bucket_array; /* Where the data is kept. */ + int nbuckets; /* How many buckets does this table have. */ + int nentries; /* How many entries does this table have. */ +} HASH_TABLE; + +typedef int hash_wfunc PARAMS((BUCKET_CONTENTS *)); + +/* Operations on tables as a whole */ +extern HASH_TABLE *hash_create PARAMS((int)); +extern HASH_TABLE *hash_copy PARAMS((HASH_TABLE *, sh_string_func_t *)); +extern void hash_flush PARAMS((HASH_TABLE *, sh_free_func_t *)); +extern void hash_dispose PARAMS((HASH_TABLE *)); +extern void hash_walk PARAMS((HASH_TABLE *, hash_wfunc *)); + +/* Operations to extract information from or pieces of tables */ +extern int hash_bucket PARAMS((const char *, HASH_TABLE *)); +extern int hash_size PARAMS((HASH_TABLE *)); + +/* Operations on hash table entries */ +extern BUCKET_CONTENTS *hash_search PARAMS((const char *, HASH_TABLE *, int)); +extern BUCKET_CONTENTS *hash_insert PARAMS((char *, HASH_TABLE *, int)); +extern BUCKET_CONTENTS *hash_remove PARAMS((const char *, HASH_TABLE *, int)); + +/* Miscellaneous */ +extern unsigned int hash_string PARAMS((const char *)); + +/* Redefine the function as a macro for speed. */ +#define hash_items(bucket, table) \ + ((table && (bucket < table->nbuckets)) ? \ + table->bucket_array[bucket] : \ + (BUCKET_CONTENTS *)NULL) + +/* Default number of buckets in the hash table. */ +#define DEFAULT_HASH_BUCKETS 128 /* must be power of two */ + +#define HASH_ENTRIES(ht) ((ht) ? (ht)->nentries : 0) + +/* flags for hash_search and hash_insert */ +#define HASH_NOSRCH 0x01 +#define HASH_CREATE 0x02 + +#if !defined (NULL) +# if defined (__STDC__) +# define NULL ((void *) 0) +# else +# define NULL 0x0 +# endif /* !__STDC__ */ +#endif /* !NULL */ + +#endif /* _HASHLIB_H */ diff --git a/third_party/bash/input.c b/third_party/bash/input.c new file mode 100644 index 000000000..7b439f8c8 --- /dev/null +++ b/third_party/bash/input.c @@ -0,0 +1,677 @@ +/* input.c -- functions to perform buffered input with synchronization. */ + +/* Copyright (C) 1992-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "filecntl.h" +#include "posixstat.h" +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "input.h" +#include "externs.h" +#include "trap.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#if defined (EAGAIN) +# define X_EAGAIN EAGAIN +#else +# define X_EAGAIN -99 +#endif + +#if defined (EWOULDBLOCK) +# define X_EWOULDBLOCK EWOULDBLOCK +#else +# define X_EWOULDBLOCK -99 +#endif + +extern void termsig_handler PARAMS((int)); + +/* Functions to handle reading input on systems that don't restart read(2) + if a signal is received. */ + +static char localbuf[1024]; +static int local_index = 0, local_bufused = 0; + +/* Posix and USG systems do not guarantee to restart read () if it is + interrupted by a signal. We do the read ourselves, and restart it + if it returns EINTR. */ +int +getc_with_restart (stream) + FILE *stream; +{ + unsigned char uc; + + CHECK_TERMSIG; + + /* Try local buffering to reduce the number of read(2) calls. */ + if (local_index == local_bufused || local_bufused == 0) + { + while (1) + { + QUIT; + run_pending_traps (); + + local_bufused = read (fileno (stream), localbuf, sizeof(localbuf)); + if (local_bufused > 0) + break; + else if (local_bufused == 0) + { + local_index = 0; + return EOF; + } + else if (errno == X_EAGAIN || errno == X_EWOULDBLOCK) + { + if (sh_unset_nodelay_mode (fileno (stream)) < 0) + { + sys_error (_("cannot reset nodelay mode for fd %d"), fileno (stream)); + local_index = local_bufused = 0; + return EOF; + } + continue; + } + else if (errno != EINTR) + { + local_index = local_bufused = 0; + return EOF; + } + else if (interrupt_state || terminating_signal) /* QUIT; */ + local_index = local_bufused = 0; + } + local_index = 0; + } + uc = localbuf[local_index++]; + return uc; +} + +int +ungetc_with_restart (c, stream) + int c; + FILE *stream; +{ + if (local_index == 0 || c == EOF) + return EOF; + localbuf[--local_index] = c; + return c; +} + +#if defined (BUFFERED_INPUT) + +/* A facility similar to stdio, but input-only. */ + +#if defined (USING_BASH_MALLOC) +# define MAX_INPUT_BUFFER_SIZE 8172 +#else +# define MAX_INPUT_BUFFER_SIZE 8192 +#endif + +#if !defined (SEEK_CUR) +# define SEEK_CUR 1 +#endif /* !SEEK_CUR */ + +#ifdef max +# undef max +#endif +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#ifdef min +# undef min +#endif +#define min(a, b) ((a) > (b) ? (b) : (a)) + +int bash_input_fd_changed; + +/* This provides a way to map from a file descriptor to the buffer + associated with that file descriptor, rather than just the other + way around. This is needed so that buffers are managed properly + in constructs like 3<&4. buffers[x]->b_fd == x -- that is how the + correspondence is maintained. */ +static BUFFERED_STREAM **buffers = (BUFFERED_STREAM **)NULL; +static int nbuffers; + +#define ALLOCATE_BUFFERS(n) \ + do { if ((n) >= nbuffers) allocate_buffers (n); } while (0) + +/* Make sure `buffers' has at least N elements. */ +static void +allocate_buffers (n) + int n; +{ + register int i, orig_nbuffers; + + orig_nbuffers = nbuffers; + nbuffers = n + 20; + buffers = (BUFFERED_STREAM **)xrealloc + (buffers, nbuffers * sizeof (BUFFERED_STREAM *)); + + /* Zero out the new buffers. */ + for (i = orig_nbuffers; i < nbuffers; i++) + buffers[i] = (BUFFERED_STREAM *)NULL; +} + +/* Construct and return a BUFFERED_STREAM corresponding to file descriptor + FD, using BUFFER. */ +static BUFFERED_STREAM * +make_buffered_stream (fd, buffer, bufsize) + int fd; + char *buffer; + size_t bufsize; +{ + BUFFERED_STREAM *bp; + + bp = (BUFFERED_STREAM *)xmalloc (sizeof (BUFFERED_STREAM)); + ALLOCATE_BUFFERS (fd); + buffers[fd] = bp; + bp->b_fd = fd; + bp->b_buffer = buffer; + bp->b_size = bufsize; + bp->b_used = bp->b_inputp = bp->b_flag = 0; + if (bufsize == 1) + bp->b_flag |= B_UNBUFF; + if (O_TEXT && (fcntl (fd, F_GETFL) & O_TEXT) != 0) + bp->b_flag |= B_TEXT; + return (bp); +} + +/* Allocate a new BUFFERED_STREAM, copy BP to it, and return the new copy. */ +static BUFFERED_STREAM * +copy_buffered_stream (bp) + BUFFERED_STREAM *bp; +{ + BUFFERED_STREAM *nbp; + + if (!bp) + return ((BUFFERED_STREAM *)NULL); + + nbp = (BUFFERED_STREAM *)xmalloc (sizeof (BUFFERED_STREAM)); + xbcopy ((char *)bp, (char *)nbp, sizeof (BUFFERED_STREAM)); + return (nbp); +} + +int +set_bash_input_fd (fd) + int fd; +{ + if (bash_input.type == st_bstream) + bash_input.location.buffered_fd = fd; + else if (interactive_shell == 0) + default_buffered_input = fd; + return 0; +} + +int +fd_is_bash_input (fd) + int fd; +{ + if (bash_input.type == st_bstream && bash_input.location.buffered_fd == fd) + return 1; + else if (interactive_shell == 0 && default_buffered_input == fd) + return 1; + return 0; +} + +/* Save the buffered stream corresponding to file descriptor FD (which bash + is using to read input) to a buffered stream associated with NEW_FD. If + NEW_FD is -1, a new file descriptor is allocated with fcntl. The new + file descriptor is returned on success, -1 on error. */ +int +save_bash_input (fd, new_fd) + int fd, new_fd; +{ + int nfd; + + /* Sync the stream so we can re-read from the new file descriptor. We + might be able to avoid this by copying the buffered stream verbatim + to the new file descriptor. */ + if (buffers[fd]) + sync_buffered_stream (fd); + + /* Now take care of duplicating the file descriptor that bash is + using for input, so we can reinitialize it later. */ + nfd = (new_fd == -1) ? fcntl (fd, F_DUPFD, 10) : new_fd; + if (nfd == -1) + { + if (fcntl (fd, F_GETFD, 0) == 0) + sys_error (_("cannot allocate new file descriptor for bash input from fd %d"), fd); + return -1; + } + + if (nfd < nbuffers && buffers[nfd]) + { + /* What's this? A stray buffer without an associated open file + descriptor? Free up the buffer and report the error. */ + internal_error (_("save_bash_input: buffer already exists for new fd %d"), nfd); + if (buffers[nfd]->b_flag & B_SHAREDBUF) + buffers[nfd]->b_buffer = (char *)NULL; + free_buffered_stream (buffers[nfd]); + } + + /* Reinitialize bash_input.location. */ + if (bash_input.type == st_bstream) + { + bash_input.location.buffered_fd = nfd; + fd_to_buffered_stream (nfd); + close_buffered_fd (fd); /* XXX */ + } + else + /* If the current input type is not a buffered stream, but the shell + is not interactive and therefore using a buffered stream to read + input (e.g. with an `eval exec 3>output' inside a script), note + that the input fd has been changed. pop_stream() looks at this + value and adjusts the input fd to the new value of + default_buffered_input accordingly. */ + bash_input_fd_changed++; + + if (default_buffered_input == fd) + default_buffered_input = nfd; + + SET_CLOSE_ON_EXEC (nfd); + return nfd; +} + +/* Check that file descriptor FD is not the one that bash is currently + using to read input from a script. FD is about to be duplicated onto, + which means that the kernel will close it for us. If FD is the bash + input file descriptor, we need to seek backwards in the script (if + possible and necessary -- scripts read from stdin are still unbuffered), + allocate a new file descriptor to use for bash input, and re-initialize + the buffered stream. Make sure the file descriptor used to save bash + input is set close-on-exec. Returns 0 on success, -1 on failure. This + works only if fd is > 0 -- if fd == 0 and bash is reading input from + fd 0, sync_buffered_stream is used instead, to cooperate with input + redirection (look at redir.c:add_undo_redirect()). */ +int +check_bash_input (fd) + int fd; +{ + if (fd_is_bash_input (fd)) + { + if (fd > 0) + return ((save_bash_input (fd, -1) == -1) ? -1 : 0); + else if (fd == 0) + return ((sync_buffered_stream (fd) == -1) ? -1 : 0); + } + return 0; +} + +/* This is the buffered stream analogue of dup2(fd1, fd2). The + BUFFERED_STREAM corresponding to fd2 is deallocated, if one exists. + BUFFERS[fd1] is copied to BUFFERS[fd2]. This is called by the + redirect code for constructs like 4<&0 and 3b_buffer && buffers[fd1]->b_buffer == buffers[fd2]->b_buffer) + buffers[fd2] = (BUFFERED_STREAM *)NULL; + /* If this buffer is shared with another fd, don't free the buffer */ + else if (buffers[fd2]->b_flag & B_SHAREDBUF) + { + buffers[fd2]->b_buffer = (char *)NULL; + free_buffered_stream (buffers[fd2]); + } + else + free_buffered_stream (buffers[fd2]); + } + buffers[fd2] = copy_buffered_stream (buffers[fd1]); + if (buffers[fd2]) + buffers[fd2]->b_fd = fd2; + + if (is_bash_input) + { + if (!buffers[fd2]) + fd_to_buffered_stream (fd2); + buffers[fd2]->b_flag |= B_WASBASHINPUT; + } + + if (fd_is_bash_input (fd1) || (buffers[fd1] && (buffers[fd1]->b_flag & B_SHAREDBUF))) + buffers[fd2]->b_flag |= B_SHAREDBUF; + + return (fd2); +} + +/* Return 1 if a seek on FD will succeed. */ +#define fd_is_seekable(fd) (lseek ((fd), 0L, SEEK_CUR) >= 0) + +/* Take FD, a file descriptor, and create and return a buffered stream + corresponding to it. If something is wrong and the file descriptor + is invalid, return a NULL stream. */ +BUFFERED_STREAM * +fd_to_buffered_stream (fd) + int fd; +{ + char *buffer; + size_t size; + struct stat sb; + + if (fstat (fd, &sb) < 0) + { + close (fd); + return ((BUFFERED_STREAM *)NULL); + } + + size = (fd_is_seekable (fd)) ? min (sb.st_size, MAX_INPUT_BUFFER_SIZE) : 1; + if (size == 0) + size = 1; + buffer = (char *)xmalloc (size); + + return (make_buffered_stream (fd, buffer, size)); +} + +/* Return a buffered stream corresponding to FILE, a file name. */ +BUFFERED_STREAM * +open_buffered_stream (file) + char *file; +{ + int fd; + + fd = open (file, O_RDONLY); + return ((fd >= 0) ? fd_to_buffered_stream (fd) : (BUFFERED_STREAM *)NULL); +} + +/* Deallocate a buffered stream and free up its resources. Make sure we + zero out the slot in BUFFERS that points to BP. */ +void +free_buffered_stream (bp) + BUFFERED_STREAM *bp; +{ + int n; + + if (!bp) + return; + + n = bp->b_fd; + if (bp->b_buffer) + free (bp->b_buffer); + free (bp); + buffers[n] = (BUFFERED_STREAM *)NULL; +} + +/* Close the file descriptor associated with BP, a buffered stream, and free + up the stream. Return the status of closing BP's file descriptor. */ +int +close_buffered_stream (bp) + BUFFERED_STREAM *bp; +{ + int fd; + + if (!bp) + return (0); + fd = bp->b_fd; + if (bp->b_flag & B_SHAREDBUF) + bp->b_buffer = (char *)NULL; + free_buffered_stream (bp); + return (close (fd)); +} + +/* Deallocate the buffered stream associated with file descriptor FD, and + close FD. Return the status of the close on FD. */ +int +close_buffered_fd (fd) + int fd; +{ + if (fd < 0) + { + errno = EBADF; + return -1; + } + if (fd >= nbuffers || !buffers || !buffers[fd]) + return (close (fd)); + return (close_buffered_stream (buffers[fd])); +} + +/* Make the BUFFERED_STREAM associated with buffers[FD] be BP, and return + the old BUFFERED_STREAM. */ +BUFFERED_STREAM * +set_buffered_stream (fd, bp) + int fd; + BUFFERED_STREAM *bp; +{ + BUFFERED_STREAM *ret; + + ret = buffers[fd]; + buffers[fd] = bp; + return ret; +} + +/* Read a buffer full of characters from BP, a buffered stream. */ +static int +b_fill_buffer (bp) + BUFFERED_STREAM *bp; +{ + ssize_t nr; + off_t o; + + CHECK_TERMSIG; + /* In an environment where text and binary files are treated differently, + compensate for lseek() on text files returning an offset different from + the count of characters read() returns. Text-mode streams have to be + treated as unbuffered. */ + if ((bp->b_flag & (B_TEXT | B_UNBUFF)) == B_TEXT) + { + o = lseek (bp->b_fd, 0, SEEK_CUR); + nr = zread (bp->b_fd, bp->b_buffer, bp->b_size); + if (nr > 0 && nr < lseek (bp->b_fd, 0, SEEK_CUR) - o) + { + lseek (bp->b_fd, o, SEEK_SET); + bp->b_flag |= B_UNBUFF; + bp->b_size = 1; + nr = zread (bp->b_fd, bp->b_buffer, bp->b_size); + } + } + else + nr = zread (bp->b_fd, bp->b_buffer, bp->b_size); + if (nr <= 0) + { + bp->b_used = bp->b_inputp = 0; + bp->b_buffer[0] = 0; + if (nr == 0) + bp->b_flag |= B_EOF; + else + bp->b_flag |= B_ERROR; + return (EOF); + } + + bp->b_used = nr; + bp->b_inputp = 0; + return (bp->b_buffer[bp->b_inputp++] & 0xFF); +} + +/* Get a character from buffered stream BP. */ +#define bufstream_getc(bp) \ + (bp->b_inputp == bp->b_used || !bp->b_used) \ + ? b_fill_buffer (bp) \ + : bp->b_buffer[bp->b_inputp++] & 0xFF + +/* Push C back onto buffered stream BP. */ +static int +bufstream_ungetc(c, bp) + int c; + BUFFERED_STREAM *bp; +{ + if (c == EOF || bp == 0 || bp->b_inputp == 0) + return (EOF); + + bp->b_buffer[--bp->b_inputp] = c; + return (c); +} + +/* Seek backwards on file BFD to synchronize what we've read so far + with the underlying file pointer. */ +int +sync_buffered_stream (bfd) + int bfd; +{ + BUFFERED_STREAM *bp; + off_t chars_left; + + if (buffers == 0 || (bp = buffers[bfd]) == 0) + return (-1); + + chars_left = bp->b_used - bp->b_inputp; + if (chars_left) + lseek (bp->b_fd, -chars_left, SEEK_CUR); + bp->b_used = bp->b_inputp = 0; + return (0); +} + +int +buffered_getchar () +{ + CHECK_TERMSIG; + + if (bash_input.location.buffered_fd < 0 || buffers[bash_input.location.buffered_fd] == 0) + return EOF; + +#if !defined (DJGPP) + return (bufstream_getc (buffers[bash_input.location.buffered_fd])); +#else + /* On DJGPP, ignore \r. */ + int ch; + while ((ch = bufstream_getc (buffers[bash_input.location.buffered_fd])) == '\r') + ; + return ch; +#endif +} + +int +buffered_ungetchar (c) + int c; +{ + return (bufstream_ungetc (c, buffers[bash_input.location.buffered_fd])); +} + +/* Make input come from file descriptor BFD through a buffered stream. */ +void +with_input_from_buffered_stream (bfd, name) + int bfd; + char *name; +{ + INPUT_STREAM location; + BUFFERED_STREAM *bp; + + location.buffered_fd = bfd; + /* Make sure the buffered stream exists. */ + bp = fd_to_buffered_stream (bfd); + init_yy_io (bp == 0 ? return_EOF : buffered_getchar, + buffered_ungetchar, st_bstream, name, location); +} + +#if defined (TEST) +void * +xmalloc(s) +int s; +{ + return (malloc (s)); +} + +void * +xrealloc(s, size) +char *s; +int size; +{ + if (!s) + return(malloc (size)); + else + return(realloc (s, size)); +} + +void +init_yy_io () +{ +} + +process(bp) +BUFFERED_STREAM *bp; +{ + int c; + + while ((c = bufstream_getc(bp)) != EOF) + putchar(c); +} + +BASH_INPUT bash_input; + +struct stat dsb; /* can be used from gdb */ + +/* imitate /bin/cat */ +main(argc, argv) +int argc; +char **argv; +{ + register int i; + BUFFERED_STREAM *bp; + + if (argc == 1) { + bp = fd_to_buffered_stream (0); + process(bp); + exit(0); + } + for (i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] == '\0') { + bp = fd_to_buffered_stream (0); + if (!bp) + continue; + process(bp); + free_buffered_stream (bp); + } else { + bp = open_buffered_stream (argv[i]); + if (!bp) + continue; + process(bp); + close_buffered_stream (bp); + } + } + exit(0); +} +#endif /* TEST */ +#endif /* BUFFERED_INPUT */ diff --git a/third_party/bash/input.h b/third_party/bash/input.h new file mode 100644 index 000000000..cb3eee425 --- /dev/null +++ b/third_party/bash/input.h @@ -0,0 +1,135 @@ +/* input.h -- Structures and unions used for reading input. */ + +/* Copyright (C) 1993-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_INPUT_H_) +#define _INPUT_H_ + +#include "stdc.h" + +/* Function pointers can be declared as (Function *)foo. */ +#if !defined (_FUNCTION_DEF) +# define _FUNCTION_DEF +typedef int Function (); +typedef void VFunction (); +typedef char *CPFunction (); /* no longer used */ +typedef char **CPPFunction (); /* no longer used */ +#endif /* _FUNCTION_DEF */ + +typedef int sh_cget_func_t PARAMS((void)); /* sh_ivoidfunc_t */ +typedef int sh_cunget_func_t PARAMS((int)); /* sh_intfunc_t */ + +enum stream_type {st_none, st_stdin, st_stream, st_string, st_bstream}; + +#if defined (BUFFERED_INPUT) + +/* Possible values for b_flag. */ +#undef B_EOF +#undef B_ERROR /* There are some systems with this define */ +#undef B_UNBUFF + +#define B_EOF 0x01 +#define B_ERROR 0x02 +#define B_UNBUFF 0x04 +#define B_WASBASHINPUT 0x08 +#define B_TEXT 0x10 +#define B_SHAREDBUF 0x20 /* shared input buffer */ + +/* A buffered stream. Like a FILE *, but with our own buffering and + synchronization. Look in input.c for the implementation. */ +typedef struct BSTREAM +{ + int b_fd; + char *b_buffer; /* The buffer that holds characters read. */ + size_t b_size; /* How big the buffer is. */ + size_t b_used; /* How much of the buffer we're using, */ + int b_flag; /* Flag values. */ + size_t b_inputp; /* The input pointer, index into b_buffer. */ +} BUFFERED_STREAM; + +#if 0 +extern BUFFERED_STREAM **buffers; +#endif + +extern int default_buffered_input; +extern int bash_input_fd_changed; + +#endif /* BUFFERED_INPUT */ + +typedef union { + FILE *file; + char *string; +#if defined (BUFFERED_INPUT) + int buffered_fd; +#endif +} INPUT_STREAM; + +typedef struct { + enum stream_type type; + char *name; + INPUT_STREAM location; + sh_cget_func_t *getter; + sh_cunget_func_t *ungetter; +} BASH_INPUT; + +extern BASH_INPUT bash_input; + +/* Functions from parse.y whose use directly or indirectly depends on the + definitions in this file. */ +extern void initialize_bash_input PARAMS((void)); +extern void init_yy_io PARAMS((sh_cget_func_t *, sh_cunget_func_t *, enum stream_type, const char *, INPUT_STREAM)); +extern char *yy_input_name PARAMS((void)); +extern void with_input_from_stdin PARAMS((void)); +extern void with_input_from_string PARAMS((char *, const char *)); +extern void with_input_from_stream PARAMS((FILE *, const char *)); +extern void push_stream PARAMS((int)); +extern void pop_stream PARAMS((void)); +extern int stream_on_stack PARAMS((enum stream_type)); +extern char *read_secondary_line PARAMS((int)); +extern int find_reserved_word PARAMS((char *)); +extern void gather_here_documents PARAMS((void)); +extern void execute_variable_command PARAMS((char *, char *)); + +extern int *save_token_state PARAMS((void)); +extern void restore_token_state PARAMS((int *)); + +/* Functions from input.c */ +extern int getc_with_restart PARAMS((FILE *)); +extern int ungetc_with_restart PARAMS((int, FILE *)); + +#if defined (BUFFERED_INPUT) +/* Functions from input.c. */ +extern int fd_is_bash_input PARAMS((int)); +extern int set_bash_input_fd PARAMS((int)); +extern int save_bash_input PARAMS((int, int)); +extern int check_bash_input PARAMS((int)); +extern int duplicate_buffered_stream PARAMS((int, int)); +extern BUFFERED_STREAM *fd_to_buffered_stream PARAMS((int)); +extern BUFFERED_STREAM *set_buffered_stream PARAMS((int, BUFFERED_STREAM *)); +extern BUFFERED_STREAM *open_buffered_stream PARAMS((char *)); +extern void free_buffered_stream PARAMS((BUFFERED_STREAM *)); +extern int close_buffered_stream PARAMS((BUFFERED_STREAM *)); +extern int close_buffered_fd PARAMS((int)); +extern int sync_buffered_stream PARAMS((int)); +extern int buffered_getchar PARAMS((void)); +extern int buffered_ungetchar PARAMS((int)); +extern void with_input_from_buffered_stream PARAMS((int, char *)); +#endif /* BUFFERED_INPUT */ + +#endif /* _INPUT_H_ */ diff --git a/third_party/bash/input_avail.c b/third_party/bash/input_avail.c new file mode 100644 index 000000000..36981cf26 --- /dev/null +++ b/third_party/bash/input_avail.c @@ -0,0 +1,165 @@ +/* input_avail.c -- check whether or not data is available for reading on a + specified file descriptor. */ + +/* Copyright (C) 2008,2009-2019 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if defined (__TANDEM) +# include +#endif + +#if defined (HAVE_CONFIG_H) +# include "config.h" +#endif + +#include +#include +#if defined (HAVE_SYS_FILE_H) +# include +#endif /* HAVE_SYS_FILE_H */ + +#if defined (HAVE_PSELECT) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif /* HAVE_UNISTD_H */ + +#include "bashansi.h" + +#include "posixselect.h" + +#if defined (FIONREAD_IN_SYS_IOCTL) +# include +#endif + +#include +#include + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#if !defined (O_NDELAY) && defined (O_NONBLOCK) +# define O_NDELAY O_NONBLOCK /* Posix style */ +#endif + +/* Return >= 1 if select/FIONREAD indicates data available for reading on + file descriptor FD; 0 if no data available. Return -1 on error. */ +int +input_avail (fd) + int fd; +{ + int result, chars_avail; +#if defined(HAVE_SELECT) + fd_set readfds, exceptfds; + struct timeval timeout; +#endif + + if (fd < 0) + return -1; + + chars_avail = 0; + +#if defined (HAVE_SELECT) + FD_ZERO (&readfds); + FD_ZERO (&exceptfds); + FD_SET (fd, &readfds); + FD_SET (fd, &exceptfds); + timeout.tv_sec = 0; + timeout.tv_usec = 0; + result = select (fd + 1, &readfds, (fd_set *)NULL, &exceptfds, &timeout); + return ((result <= 0) ? 0 : 1); +#endif + +#if defined (FIONREAD) + errno = 0; + result = ioctl (fd, FIONREAD, &chars_avail); + if (result == -1 && errno == EIO) + return -1; + return (chars_avail); +#endif + + return 0; +} + +/* Wait until NCHARS are available for reading on file descriptor FD. + This can wait indefinitely. Return -1 on error. */ +int +nchars_avail (fd, nchars) + int fd; + int nchars; +{ + int result, chars_avail; +#if defined(HAVE_SELECT) + fd_set readfds, exceptfds; +#endif +#if defined (HAVE_PSELECT) || defined (HAVE_SELECT) + sigset_t set, oset; +#endif + + if (fd < 0 || nchars < 0) + return -1; + if (nchars == 0) + return (input_avail (fd)); + + chars_avail = 0; + +#if defined (HAVE_SELECT) + FD_ZERO (&readfds); + FD_ZERO (&exceptfds); + FD_SET (fd, &readfds); + FD_SET (fd, &exceptfds); +#endif +#if defined (HAVE_SELECT) || defined (HAVE_PSELECT) + sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &set); +# ifdef SIGCHLD + sigaddset (&set, SIGCHLD); +# endif + sigemptyset (&oset); +#endif + + while (1) + { + result = 0; +#if defined (HAVE_PSELECT) + /* XXX - use pselect(2) to block SIGCHLD atomically */ + result = pselect (fd + 1, &readfds, (fd_set *)NULL, &exceptfds, (struct timespec *)NULL, &set); +#elif defined (HAVE_SELECT) + sigprocmask (SIG_BLOCK, &set, &oset); + result = select (fd + 1, &readfds, (fd_set *)NULL, &exceptfds, (struct timeval *)NULL); + sigprocmask (SIG_BLOCK, &oset, (sigset_t *)NULL); +#endif + if (result < 0) + return -1; + +#if defined (FIONREAD) + errno = 0; + result = ioctl (fd, FIONREAD, &chars_avail); + if (result == -1 && errno == EIO) + return -1; + if (chars_avail >= nchars) + break; +#else + break; +#endif + } + + return 0; +} diff --git a/third_party/bash/itos.c b/third_party/bash/itos.c new file mode 100644 index 000000000..ecdc99686 --- /dev/null +++ b/third_party/bash/itos.c @@ -0,0 +1,84 @@ +/* itos.c -- Convert integer to string. */ + +/* Copyright (C) 1998-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include "shell.h" + +char * +inttostr (i, buf, len) + intmax_t i; + char *buf; + size_t len; +{ + return (fmtumax (i, 10, buf, len, 0)); +} + +/* Integer to string conversion. This conses the string; the + caller should free it. */ +char * +itos (i) + intmax_t i; +{ + char *p, lbuf[INT_STRLEN_BOUND(intmax_t) + 1]; + + p = fmtumax (i, 10, lbuf, sizeof(lbuf), 0); + return (savestring (p)); +} + +/* Integer to string conversion. This conses the string using strdup; + caller should free it and be prepared to deal with NULL return. */ +char * +mitos (i) + intmax_t i; +{ + char *p, lbuf[INT_STRLEN_BOUND(intmax_t) + 1]; + + p = fmtumax (i, 10, lbuf, sizeof(lbuf), 0); + return (strdup (p)); +} + +char * +uinttostr (i, buf, len) + uintmax_t i; + char *buf; + size_t len; +{ + return (fmtumax (i, 10, buf, len, FL_UNSIGNED)); +} + +/* Integer to string conversion. This conses the string; the + caller should free it. */ +char * +uitos (i) + uintmax_t i; +{ + char *p, lbuf[INT_STRLEN_BOUND(uintmax_t) + 1]; + + p = fmtumax (i, 10, lbuf, sizeof(lbuf), FL_UNSIGNED); + return (savestring (p)); +} diff --git a/third_party/bash/jobs.c b/third_party/bash/jobs.c new file mode 100644 index 000000000..c6e9eaf3f --- /dev/null +++ b/third_party/bash/jobs.c @@ -0,0 +1,5119 @@ +/* jobs.c - functions that make children, remember them, and handle their termination. */ + +/* This file works with both POSIX and BSD systems. It implements job + control. */ + +/* Copyright (C) 1989-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include "trap.h" +#include +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "posixtime.h" + +#if defined (HAVE_SYS_RESOURCE_H) && defined (HAVE_WAIT3) && !defined (_POSIX_VERSION) && !defined (RLIMTYPE) +# include +#endif /* !_POSIX_VERSION && HAVE_SYS_RESOURCE_H && HAVE_WAIT3 && !RLIMTYPE */ + +#if defined (HAVE_SYS_FILE_H) +# include +#endif + +#include "filecntl.h" +#include +#if defined (HAVE_SYS_PARAM_H) +#include +#endif + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +/* Need to include this up here for *_TTY_DRIVER definitions. */ +#include "shtty.h" + +/* Define this if your output is getting swallowed. It's a no-op on + machines with the termio or termios tty drivers. */ +/* #define DRAIN_OUTPUT */ + +/* For the TIOCGPGRP and TIOCSPGRP ioctl parameters on HP-UX */ +#if defined (hpux) && !defined (TERMIOS_TTY_DRIVER) +# include +#endif /* hpux && !TERMIOS_TTY_DRIVER */ + +#include "bashansi.h" +#include "bashintl.h" +#include "shell.h" +#include "parser.h" +#include "jobs.h" +#include "execute_cmd.h" +#include "flags.h" + +#include "typemax.h" + +#include "builtext.h" +#include "common.h" + +#if defined (READLINE) +# include "third_party/readline/readline.h" +#endif + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#if !defined (HAVE_KILLPG) +extern int killpg PARAMS((pid_t, int)); +#endif + +#if !DEFAULT_CHILD_MAX +# define DEFAULT_CHILD_MAX 4096 +#endif + +#if !MAX_CHILD_MAX +# define MAX_CHILD_MAX 32768 +#endif + +#if !defined (DEBUG) +#define MAX_JOBS_IN_ARRAY 4096 /* production */ +#else +#define MAX_JOBS_IN_ARRAY 128 /* testing */ +#endif + +/* XXX for now */ +#define PIDSTAT_TABLE_SZ 4096 +#define BGPIDS_TABLE_SZ 512 + +/* Flag values for second argument to delete_job */ +#define DEL_WARNSTOPPED 1 /* warn about deleting stopped jobs */ +#define DEL_NOBGPID 2 /* don't add pgrp leader to bgpids */ + +/* Take care of system dependencies that must be handled when waiting for + children. The arguments to the WAITPID macro match those to the Posix.1 + waitpid() function. */ + +#if defined (ultrix) && defined (mips) && defined (_POSIX_VERSION) +# define WAITPID(pid, statusp, options) \ + wait3 ((union wait *)statusp, options, (struct rusage *)0) +#else +# if defined (_POSIX_VERSION) || defined (HAVE_WAITPID) +# define WAITPID(pid, statusp, options) \ + waitpid ((pid_t)pid, statusp, options) +# else +# if defined (HAVE_WAIT3) +# define WAITPID(pid, statusp, options) \ + wait3 (statusp, options, (struct rusage *)0) +# else +# define WAITPID(pid, statusp, options) \ + wait3 (statusp, options, (int *)0) +# endif /* HAVE_WAIT3 */ +# endif /* !_POSIX_VERSION && !HAVE_WAITPID*/ +#endif /* !(Ultrix && mips && _POSIX_VERSION) */ + +/* getpgrp () varies between systems. Even systems that claim to be + Posix.1 compatible lie sometimes (Ultrix, SunOS4, apollo). */ +#if defined (GETPGRP_VOID) +# define getpgid(p) getpgrp () +#else +# define getpgid(p) getpgrp (p) +#endif /* !GETPGRP_VOID */ + +/* If the system needs it, REINSTALL_SIGCHLD_HANDLER will reinstall the + handler for SIGCHLD. */ +#if defined (MUST_REINSTALL_SIGHANDLERS) +# define REINSTALL_SIGCHLD_HANDLER signal (SIGCHLD, sigchld_handler) +#else +# define REINSTALL_SIGCHLD_HANDLER +#endif /* !MUST_REINSTALL_SIGHANDLERS */ + +/* Some systems let waitpid(2) tell callers about stopped children. */ +#if !defined (WCONTINUED) || defined (WCONTINUED_BROKEN) +# undef WCONTINUED +# define WCONTINUED 0 +#endif +#if !defined (WIFCONTINUED) +# define WIFCONTINUED(s) (0) +#endif + +/* The number of additional slots to allocate when we run out. */ +#define JOB_SLOTS 8 + +typedef int sh_job_map_func_t PARAMS((JOB *, int, int, int)); + +/* Variables used here but defined in other files. */ +extern WORD_LIST *subst_assign_varlist; + +extern SigHandler **original_signals; + +extern void set_original_signal PARAMS((int, SigHandler *)); + +static struct jobstats zerojs = { -1L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NO_JOB, NO_JOB, 0, 0 }; +struct jobstats js = { -1L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NO_JOB, NO_JOB, 0, 0 }; + +ps_index_t pidstat_table[PIDSTAT_TABLE_SZ]; +struct bgpids bgpids = { 0, 0, 0, 0 }; + +struct procchain procsubs = { 0, 0, 0 }; + +/* The array of known jobs. */ +JOB **jobs = (JOB **)NULL; + +#if 0 +/* The number of slots currently allocated to JOBS. */ +int job_slots = 0; +#endif + +/* The controlling tty for this shell. */ +int shell_tty = -1; + +/* The shell's process group. */ +pid_t shell_pgrp = NO_PID; + +/* The terminal's process group. */ +pid_t terminal_pgrp = NO_PID; + +/* The process group of the shell's parent. */ +pid_t original_pgrp = NO_PID; + +/* The process group of the pipeline currently being made. */ +pid_t pipeline_pgrp = (pid_t)0; + +#if defined (PGRP_PIPE) +/* Pipes which each shell uses to communicate with the process group leader + until all of the processes in a pipeline have been started. Then the + process leader is allowed to continue. */ +int pgrp_pipe[2] = { -1, -1 }; +#endif + +/* Last child made by the shell. */ +volatile pid_t last_made_pid = NO_PID; + +/* Pid of the last asynchronous child. */ +volatile pid_t last_asynchronous_pid = NO_PID; + +/* The pipeline currently being built. */ +PROCESS *the_pipeline = (PROCESS *)NULL; + +/* If this is non-zero, do job control. */ +int job_control = 1; + +/* Are we running in background? (terminal_pgrp != shell_pgrp) */ +int running_in_background = 0; + +/* Call this when you start making children. */ +int already_making_children = 0; + +/* If this is non-zero, $LINES and $COLUMNS are reset after every process + exits from get_tty_state(). */ +int check_window_size = CHECKWINSIZE_DEFAULT; + +PROCESS *last_procsub_child = (PROCESS *)NULL; + +/* Functions local to this file. */ + +void debug_print_pgrps (void); + +static sighandler wait_sigint_handler PARAMS((int)); +static sighandler sigchld_handler PARAMS((int)); +static sighandler sigcont_sighandler PARAMS((int)); +static sighandler sigstop_sighandler PARAMS((int)); + +static int waitchld PARAMS((pid_t, int)); + +static PROCESS *find_pid_in_pipeline PARAMS((pid_t, PROCESS *, int)); +static PROCESS *find_pipeline PARAMS((pid_t, int, int *)); +static PROCESS *find_process PARAMS((pid_t, int, int *)); + +static char *current_working_directory PARAMS((void)); +static char *job_working_directory PARAMS((void)); +static char *j_strsignal PARAMS((int)); +static char *printable_job_status PARAMS((int, PROCESS *, int)); + +static PROCESS *find_last_proc PARAMS((int, int)); +static pid_t find_last_pid PARAMS((int, int)); + +static int set_new_line_discipline PARAMS((int)); +static int map_over_jobs PARAMS((sh_job_map_func_t *, int, int)); +static int job_last_stopped PARAMS((int)); +static int job_last_running PARAMS((int)); +static int most_recent_job_in_state PARAMS((int, JOB_STATE)); +static int find_job PARAMS((pid_t, int, PROCESS **)); +static int print_job PARAMS((JOB *, int, int, int)); +static int process_exit_status PARAMS((WAIT)); +static int process_exit_signal PARAMS((WAIT)); +static int set_job_status_and_cleanup PARAMS((int)); + +static WAIT job_signal_status PARAMS((int)); +static WAIT raw_job_exit_status PARAMS((int)); + +static void notify_of_job_status PARAMS((void)); +static void reset_job_indices PARAMS((void)); +static void cleanup_dead_jobs PARAMS((void)); +static int processes_in_job PARAMS((int)); +static void realloc_jobs_list PARAMS((void)); +static int compact_jobs_list PARAMS((int)); +static void add_process PARAMS((char *, pid_t)); +static void print_pipeline PARAMS((PROCESS *, int, int, FILE *)); +static void pretty_print_job PARAMS((int, int, FILE *)); +static void set_current_job PARAMS((int)); +static void reset_current PARAMS((void)); +static void set_job_running PARAMS((int)); +static void setjstatus PARAMS((int)); +static int maybe_give_terminal_to PARAMS((pid_t, pid_t, int)); +static void mark_all_jobs_as_dead PARAMS((void)); +static void mark_dead_jobs_as_notified PARAMS((int)); +static void restore_sigint_handler PARAMS((void)); +#if defined (PGRP_PIPE) +static void pipe_read PARAMS((int *)); +#endif + +/* Hash table manipulation */ + +static ps_index_t *pshash_getbucket PARAMS((pid_t)); +static void pshash_delindex PARAMS((ps_index_t)); + +/* Saved background process status management */ +static struct pidstat *bgp_add PARAMS((pid_t, int)); +static int bgp_delete PARAMS((pid_t)); +static void bgp_clear PARAMS((void)); +static int bgp_search PARAMS((pid_t)); + +static struct pipeline_saver *alloc_pipeline_saver PARAMS((void)); + +static ps_index_t bgp_getindex PARAMS((void)); +static void bgp_resize PARAMS((void)); /* XXX */ + +#if defined (ARRAY_VARS) +static int *pstatuses; /* list of pipeline statuses */ +static int statsize; +#endif + +/* Used to synchronize between wait_for and other functions and the SIGCHLD + signal handler. */ +static int sigchld; +static int queue_sigchld; + +#define QUEUE_SIGCHLD(os) (os) = sigchld, queue_sigchld++ + +/* We set queue_sigchld around the call to waitchld to protect data structures + from a SIGCHLD arriving while waitchld is executing. */ +#define UNQUEUE_SIGCHLD(os) \ + do { \ + queue_sigchld--; \ + if (queue_sigchld == 0 && os != sigchld) \ + { \ + queue_sigchld = 1; \ + waitchld (-1, 0); \ + queue_sigchld = 0; \ + } \ + } while (0) + +static SigHandler *old_tstp, *old_ttou, *old_ttin; +static SigHandler *old_cont = (SigHandler *)SIG_DFL; + +/* A place to temporarily save the current pipeline. */ +static struct pipeline_saver *saved_pipeline; +static int saved_already_making_children; + +/* Set this to non-zero whenever you don't want the jobs list to change at + all: no jobs deleted and no status change notifications. This is used, + for example, when executing SIGCHLD traps, which may run arbitrary + commands. */ +static int jobs_list_frozen; + +static char retcode_name_buffer[64]; + +#if !defined (_POSIX_VERSION) + +/* These are definitions to map POSIX 1003.1 functions onto existing BSD + library functions and system calls. */ +#define setpgid(pid, pgrp) setpgrp (pid, pgrp) +#define tcsetpgrp(fd, pgrp) ioctl ((fd), TIOCSPGRP, &(pgrp)) + +pid_t +tcgetpgrp (fd) + int fd; +{ + pid_t pgrp; + + /* ioctl will handle setting errno correctly. */ + if (ioctl (fd, TIOCGPGRP, &pgrp) < 0) + return (-1); + return (pgrp); +} + +#endif /* !_POSIX_VERSION */ + +/* Initialize the global job stats structure and other bookkeeping variables */ +void +init_job_stats () +{ + js = zerojs; +} + +/* Return the working directory for the current process. Unlike + job_working_directory, this does not call malloc (), nor do any + of the functions it calls. This is so that it can safely be called + from a signal handler. */ +static char * +current_working_directory () +{ + char *dir; + static char d[PATH_MAX]; + + dir = get_string_value ("PWD"); + + if (dir == 0 && the_current_working_directory && no_symbolic_links) + dir = the_current_working_directory; + + if (dir == 0) + { + dir = getcwd (d, sizeof(d)); + if (dir) + dir = d; + } + + return (dir == 0) ? "" : dir; +} + +/* Return the working directory for the current process. */ +static char * +job_working_directory () +{ + char *dir; + + dir = get_string_value ("PWD"); + if (dir) + return (savestring (dir)); + + dir = get_working_directory ("job-working-directory"); + if (dir) + return (dir); + + return (savestring ("")); +} + +void +making_children () +{ + if (already_making_children) + return; + + already_making_children = 1; + start_pipeline (); +} + +void +stop_making_children () +{ + already_making_children = 0; +} + +void +cleanup_the_pipeline () +{ + PROCESS *disposer; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + disposer = the_pipeline; + the_pipeline = (PROCESS *)NULL; + UNBLOCK_CHILD (oset); + + if (disposer) + discard_pipeline (disposer); +} + +/* Not used right now */ +void +discard_last_procsub_child () +{ + PROCESS *disposer; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + disposer = last_procsub_child; + last_procsub_child = (PROCESS *)NULL; + UNBLOCK_CHILD (oset); + + if (disposer) + discard_pipeline (disposer); +} + +static struct pipeline_saver * +alloc_pipeline_saver () +{ + struct pipeline_saver *ret; + + ret = (struct pipeline_saver *)xmalloc (sizeof (struct pipeline_saver)); + ret->pipeline = 0; + ret->next = 0; + return ret; +} + +void +save_pipeline (clear) + int clear; +{ + sigset_t set, oset; + struct pipeline_saver *saver; + + BLOCK_CHILD (set, oset); + saver = alloc_pipeline_saver (); + saver->pipeline = the_pipeline; + saver->next = saved_pipeline; + saved_pipeline = saver; + if (clear) + the_pipeline = (PROCESS *)NULL; + saved_already_making_children = already_making_children; + UNBLOCK_CHILD (oset); +} + +PROCESS * +restore_pipeline (discard) + int discard; +{ + PROCESS *old_pipeline; + sigset_t set, oset; + struct pipeline_saver *saver; + + BLOCK_CHILD (set, oset); + old_pipeline = the_pipeline; + the_pipeline = saved_pipeline->pipeline; + saver = saved_pipeline; + saved_pipeline = saved_pipeline->next; + free (saver); + already_making_children = saved_already_making_children; + UNBLOCK_CHILD (oset); + + if (discard && old_pipeline) + { + discard_pipeline (old_pipeline); + return ((PROCESS *)NULL); + } + return old_pipeline; +} + +/* Start building a pipeline. */ +void +start_pipeline () +{ + if (the_pipeline) + { + cleanup_the_pipeline (); + /* If job_control == 0, pipeline_pgrp will always be equal to shell_pgrp; + if job_control != 0, pipeline_pgrp == shell_pgrp for command and + process substitution, in which case we want it to be the same as + shell_pgrp for the lifetime of this shell instance. */ + if (pipeline_pgrp != shell_pgrp) + pipeline_pgrp = 0; +#if defined (PGRP_PIPE) + sh_closepipe (pgrp_pipe); +#endif + } + +#if defined (PGRP_PIPE) + if (job_control) + { + if (pipe (pgrp_pipe) == -1) + sys_error (_("start_pipeline: pgrp pipe")); + } +#endif +} + +/* Stop building a pipeline. Install the process list in the job array. + This returns the index of the newly installed job. + DEFERRED is a command structure to be executed upon satisfactory + execution exit of this pipeline. */ +int +stop_pipeline (async, deferred) + int async; + COMMAND *deferred; +{ + register int i, j; + JOB *newjob; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + +#if defined (PGRP_PIPE) + /* The parent closes the process group synchronization pipe. */ + sh_closepipe (pgrp_pipe); +#endif + + cleanup_dead_jobs (); + + if (js.j_jobslots == 0) + { + js.j_jobslots = JOB_SLOTS; + jobs = (JOB **)xmalloc (js.j_jobslots * sizeof (JOB *)); + + /* Now blank out these new entries. */ + for (i = 0; i < js.j_jobslots; i++) + jobs[i] = (JOB *)NULL; + + js.j_firstj = js.j_lastj = js.j_njobs = 0; + } + + /* Scan from the last slot backward, looking for the next free one. */ + /* XXX - revisit this interactive assumption */ + /* XXX - this way for now */ + if (interactive) + { + for (i = js.j_jobslots; i; i--) + if (jobs[i - 1]) + break; + } + else + { +#if 0 + /* This wraps around, but makes it inconvenient to extend the array */ + for (i = js.j_lastj+1; i != js.j_lastj; i++) + { + if (i >= js.j_jobslots) + i = 0; + if (jobs[i] == 0) + break; + } + if (i == js.j_lastj) + i = js.j_jobslots; +#else + /* This doesn't wrap around yet. */ + for (i = js.j_lastj ? js.j_lastj + 1 : js.j_lastj; i < js.j_jobslots; i++) + if (jobs[i] == 0) + break; +#endif + } + + /* Do we need more room? */ + + /* First try compaction */ + if ((interactive_shell == 0 || subshell_environment) && i == js.j_jobslots && js.j_jobslots >= MAX_JOBS_IN_ARRAY) + i = compact_jobs_list (0); + + /* If we can't compact, reallocate */ + if (i == js.j_jobslots) + { + js.j_jobslots += JOB_SLOTS; + jobs = (JOB **)xrealloc (jobs, (js.j_jobslots * sizeof (JOB *))); + + for (j = i; j < js.j_jobslots; j++) + jobs[j] = (JOB *)NULL; + } + + /* Add the current pipeline to the job list. */ + if (the_pipeline) + { + register PROCESS *p; + int any_running, any_stopped, n; + + newjob = (JOB *)xmalloc (sizeof (JOB)); + + for (n = 1, p = the_pipeline; p->next != the_pipeline; n++, p = p->next) + ; + p->next = (PROCESS *)NULL; + newjob->pipe = REVERSE_LIST (the_pipeline, PROCESS *); + for (p = newjob->pipe; p->next; p = p->next) + ; + p->next = newjob->pipe; + + the_pipeline = (PROCESS *)NULL; + newjob->pgrp = pipeline_pgrp; + + /* Invariant: if the shell is executing a command substitution, + pipeline_pgrp == shell_pgrp. Other parts of the shell assume this. */ + if (pipeline_pgrp != shell_pgrp) + pipeline_pgrp = 0; + + newjob->flags = 0; + if (pipefail_opt) + newjob->flags |= J_PIPEFAIL; + + /* Flag to see if in another pgrp. */ + if (job_control) + newjob->flags |= J_JOBCONTROL; + + /* Set the state of this pipeline. */ + p = newjob->pipe; + any_running = any_stopped = 0; + do + { + any_running |= PRUNNING (p); + any_stopped |= PSTOPPED (p); + p = p->next; + } + while (p != newjob->pipe); + + newjob->state = any_running ? JRUNNING : (any_stopped ? JSTOPPED : JDEAD); + newjob->wd = job_working_directory (); + newjob->deferred = deferred; + + newjob->j_cleanup = (sh_vptrfunc_t *)NULL; + newjob->cleanarg = (PTR_T) NULL; + + jobs[i] = newjob; + if (newjob->state == JDEAD && (newjob->flags & J_FOREGROUND)) + setjstatus (i); + if (newjob->state == JDEAD) + { + js.c_reaped += n; /* wouldn't have been done since this was not part of a job */ + js.j_ndead++; + } + js.c_injobs += n; + + js.j_lastj = i; + js.j_njobs++; + } + else + newjob = (JOB *)NULL; + + if (newjob) + js.j_lastmade = newjob; + + if (async) + { + if (newjob) + { + newjob->flags &= ~J_FOREGROUND; + newjob->flags |= J_ASYNC; + js.j_lastasync = newjob; + } + reset_current (); + } + else + { + if (newjob) + { + newjob->flags |= J_FOREGROUND; + /* + * !!!!! NOTE !!!!! (chet@po.cwru.edu) + * + * The currently-accepted job control wisdom says to set the + * terminal's process group n+1 times in an n-step pipeline: + * once in the parent and once in each child. This is where + * the parent gives it away. + * + * Don't give the terminal away if this shell is an asynchronous + * subshell or if we're a (presumably non-interactive) shell running + * in the background. + * + */ + if (job_control && newjob->pgrp && (subshell_environment&SUBSHELL_ASYNC) == 0 && running_in_background == 0) + maybe_give_terminal_to (shell_pgrp, newjob->pgrp, 0); + } + } + + stop_making_children (); + UNBLOCK_CHILD (oset); + return (newjob ? i : js.j_current); +} + +/* Functions to manage the list of exited background pids whose status has + been saved. + + pidstat_table: + + The current implementation is a hash table using a single (separate) arena + for storage that can be allocated and freed as a unit. The size of the hash + table is a multiple of PIDSTAT_TABLE_SZ (4096) and multiple PIDs that hash + to the same value are chained through the bucket_next and bucket_prev + pointers (basically coalesced hashing for collision resolution). + + bgpids.storage: + + All pid/status storage is done using the circular buffer bgpids.storage. + This must contain at least js.c_childmax entries. The circular buffer is + used to supply the ordered list Posix requires ("the last CHILD_MAX + processes"). To avoid searching the entire storage table for a given PID, + the hash table (pidstat_table) holds pointers into the storage arena and + uses a doubly-linked list of cells (bucket_next/bucket_prev, also pointers + into the arena) to implement collision resolution. */ + +/* The number of elements in bgpids.storage always has to be > js.c_childmax for + the circular buffer to work right. */ +static void +bgp_resize () +{ + ps_index_t nsize, nsize_cur, nsize_max; + ps_index_t psi; + + if (bgpids.nalloc == 0) + { + /* invalidate hash table when bgpids table is reallocated */ + for (psi = 0; psi < PIDSTAT_TABLE_SZ; psi++) + pidstat_table[psi] = NO_PIDSTAT; + nsize = BGPIDS_TABLE_SZ; /* should be power of 2 */ + bgpids.head = 0; + } + else + nsize = bgpids.nalloc; + + nsize_max = TYPE_MAXIMUM (ps_index_t); + nsize_cur = (ps_index_t)js.c_childmax; + if (nsize_cur < 0) /* overflow */ + nsize_cur = MAX_CHILD_MAX; + + while (nsize > 0 && nsize < nsize_cur) /* > 0 should catch overflow */ + nsize <<= 1; + if (nsize > nsize_max || nsize <= 0) /* overflow? */ + nsize = nsize_max; + if (nsize > MAX_CHILD_MAX) + nsize = nsize_max = MAX_CHILD_MAX; /* hard cap */ + + if (bgpids.nalloc < nsize_cur && bgpids.nalloc < nsize_max) + { + bgpids.storage = (struct pidstat *)xrealloc (bgpids.storage, nsize * sizeof (struct pidstat)); + + for (psi = bgpids.nalloc; psi < nsize; psi++) + bgpids.storage[psi].pid = NO_PID; + + bgpids.nalloc = nsize; + + } + else if (bgpids.head >= bgpids.nalloc) /* wrap around */ + bgpids.head = 0; +} + +static ps_index_t +bgp_getindex () +{ + if (bgpids.nalloc < (ps_index_t)js.c_childmax || bgpids.head >= bgpids.nalloc) + bgp_resize (); + + pshash_delindex (bgpids.head); /* XXX - clear before reusing */ + return bgpids.head++; +} + +static ps_index_t * +pshash_getbucket (pid) + pid_t pid; +{ + unsigned long hash; /* XXX - u_bits32_t */ + + hash = pid * 0x9e370001UL; + return (&pidstat_table[hash % PIDSTAT_TABLE_SZ]); +} + +static struct pidstat * +bgp_add (pid, status) + pid_t pid; + int status; +{ + ps_index_t *bucket, psi; + struct pidstat *ps; + + /* bucket == existing chain of pids hashing to same value + psi = where were going to put this pid/status */ + + bucket = pshash_getbucket (pid); /* index into pidstat_table */ + psi = bgp_getindex (); /* bgpids.head, index into storage */ + + /* XXX - what if psi == *bucket? */ + if (psi == *bucket) + { + internal_debug ("hashed pid %d (pid %d) collides with bgpids.head, skipping", psi, pid); + bgpids.storage[psi].pid = NO_PID; /* make sure */ + psi = bgp_getindex (); /* skip to next one */ + } + + ps = &bgpids.storage[psi]; + + ps->pid = pid; + ps->status = status; + ps->bucket_next = *bucket; + ps->bucket_prev = NO_PIDSTAT; + + bgpids.npid++; + +#if 0 + if (bgpids.npid > js.c_childmax) + bgp_prune (); +#endif + + if (ps->bucket_next != NO_PIDSTAT) + bgpids.storage[ps->bucket_next].bucket_prev = psi; + + *bucket = psi; /* set chain head in hash table */ + + return ps; +} + +static void +pshash_delindex (psi) + ps_index_t psi; +{ + struct pidstat *ps; + ps_index_t *bucket; + + ps = &bgpids.storage[psi]; + if (ps->pid == NO_PID) + return; + + if (ps->bucket_next != NO_PIDSTAT) + bgpids.storage[ps->bucket_next].bucket_prev = ps->bucket_prev; + if (ps->bucket_prev != NO_PIDSTAT) + bgpids.storage[ps->bucket_prev].bucket_next = ps->bucket_next; + else + { + bucket = pshash_getbucket (ps->pid); + *bucket = ps->bucket_next; /* deleting chain head in hash table */ + } + + /* clear out this cell, in case it gets reused. */ + ps->pid = NO_PID; + ps->bucket_next = ps->bucket_prev = NO_PIDSTAT; +} + +static int +bgp_delete (pid) + pid_t pid; +{ + ps_index_t psi, orig_psi; + + if (bgpids.storage == 0 || bgpids.nalloc == 0 || bgpids.npid == 0) + return 0; + + /* Search chain using hash to find bucket in pidstat_table */ + for (orig_psi = psi = *(pshash_getbucket (pid)); psi != NO_PIDSTAT; psi = bgpids.storage[psi].bucket_next) + { + if (bgpids.storage[psi].pid == pid) + break; + if (orig_psi == bgpids.storage[psi].bucket_next) /* catch reported bug */ + { + internal_warning (_("bgp_delete: LOOP: psi (%d) == storage[psi].bucket_next"), psi); + return 0; + } + } + + if (psi == NO_PIDSTAT) + return 0; /* not found */ + +#if 0 + itrace("bgp_delete: deleting %d", pid); +#endif + + pshash_delindex (psi); /* hash table management */ + + bgpids.npid--; + return 1; +} + +/* Clear out the list of saved statuses */ +static void +bgp_clear () +{ + if (bgpids.storage == 0 || bgpids.nalloc == 0) + return; + + free (bgpids.storage); + + bgpids.storage = 0; + bgpids.nalloc = 0; + bgpids.head = 0; + + bgpids.npid = 0; +} + +/* Search for PID in the list of saved background pids; return its status if + found. If not found, return -1. We hash to the right spot in pidstat_table + and follow the bucket chain to the end. */ +static int +bgp_search (pid) + pid_t pid; +{ + ps_index_t psi, orig_psi; + + if (bgpids.storage == 0 || bgpids.nalloc == 0 || bgpids.npid == 0) + return -1; + + /* Search chain using hash to find bucket in pidstat_table */ + for (orig_psi = psi = *(pshash_getbucket (pid)); psi != NO_PIDSTAT; psi = bgpids.storage[psi].bucket_next) + { + if (bgpids.storage[psi].pid == pid) + return (bgpids.storage[psi].status); + if (orig_psi == bgpids.storage[psi].bucket_next) /* catch reported bug */ + { + internal_warning (_("bgp_search: LOOP: psi (%d) == storage[psi].bucket_next"), psi); + return -1; + } + } + + return -1; +} + +#if 0 +static void +bgp_prune () +{ + return; +} +#endif + +/* External interface to bgp_add; takes care of blocking and unblocking + SIGCHLD. Not really used. */ +void +save_proc_status (pid, status) + pid_t pid; + int status; +{ + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + bgp_add (pid, status); + UNBLOCK_CHILD (oset); +} + +#if defined (PROCESS_SUBSTITUTION) +/* Functions to add and remove PROCESS * children from the list of running + asynchronous process substitutions. The list is currently a simple singly + linked list of PROCESS *, so it works with the set of callers that want + a child. subst.c:process_substitute adds to the list, the various wait* + functions manipulate child->running and child->status, and processes are + eventually removed from the list and added to the bgpids table. */ + +static void +procsub_free (p) + PROCESS *p; +{ + FREE (p->command); + free (p); +} + +PROCESS * +procsub_add (p) + PROCESS *p; +{ + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + if (procsubs.head == 0) + { + procsubs.head = procsubs.end = p; + procsubs.nproc = 0; + } + else + { + procsubs.end->next = p; + procsubs.end = p; + } + procsubs.nproc++; + UNBLOCK_CHILD (oset); + + return p; +} + +PROCESS * +procsub_search (pid) + pid_t pid; +{ + PROCESS *p; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + for (p = procsubs.head; p; p = p->next) + if (p->pid == pid) + break; + UNBLOCK_CHILD (oset); + + return p; +} + +PROCESS * +procsub_delete (pid) + pid_t pid; +{ + PROCESS *p, *prev; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + for (p = prev = procsubs.head; p; prev = p, p = p->next) + if (p->pid == pid) + { + prev->next = p->next; + break; + } + + if (p == 0) + { + UNBLOCK_CHILD (oset); + return p; + } + + if (p == procsubs.head) + procsubs.head = procsubs.head->next; + else if (p == procsubs.end) + procsubs.end = prev; + + procsubs.nproc--; + if (procsubs.nproc == 0) + procsubs.head = procsubs.end = 0; + else if (procsubs.nproc == 1) /* XXX */ + procsubs.end = procsubs.head; + + /* this can't be called anywhere in a signal handling path */ + bgp_add (p->pid, process_exit_status (p->status)); + UNBLOCK_CHILD (oset); + return (p); +} + +int +procsub_waitpid (pid) + pid_t pid; +{ + PROCESS *p; + int r; + + p = procsub_search (pid); + if (p == 0) + return -1; + if (p->running == PS_DONE) + return (p->status); + r = wait_for (p->pid, 0); + return (r); /* defer removing until later */ +} + +void +procsub_waitall () +{ + PROCESS *p; + int r; + + for (p = procsubs.head; p; p = p->next) + { + if (p->running == PS_DONE) + continue; + r = wait_for (p->pid, 0); + } +} + +void +procsub_clear () +{ + PROCESS *p, *ps; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + for (ps = procsubs.head; ps; ) + { + p = ps; + ps = ps->next; + procsub_free (p); + } + procsubs.head = procsubs.end = 0; + procsubs.nproc = 0; + UNBLOCK_CHILD (oset); +} + +/* Must be called with SIGCHLD blocked. */ +void +procsub_prune () +{ + PROCESS *ohead, *oend, *ps, *p; + int onproc; + + if (procsubs.nproc == 0) + return; + + ohead = procsubs.head; + oend = procsubs.end; + onproc = procsubs.nproc; + + procsubs.head = procsubs.end = 0; + procsubs.nproc = 0; + + for (p = ohead; p; ) + { + ps = p->next; + p->next = 0; + if (p->running == PS_DONE) + { + bgp_add (p->pid, process_exit_status (p->status)); + procsub_free (p); + } + else + procsub_add (p); + p = ps; + } +} +#endif + +/* Reset the values of js.j_lastj and js.j_firstj after one or both have + been deleted. The caller should check whether js.j_njobs is 0 before + calling this. This wraps around, but the rest of the code does not. At + this point, it should not matter. */ +static void +reset_job_indices () +{ + int old; + + if (jobs[js.j_firstj] == 0) + { + old = js.j_firstj++; + if (old >= js.j_jobslots) + old = js.j_jobslots - 1; + while (js.j_firstj != old) + { + if (js.j_firstj >= js.j_jobslots) + js.j_firstj = 0; + if (jobs[js.j_firstj] || js.j_firstj == old) /* needed if old == 0 */ + break; + js.j_firstj++; + } + if (js.j_firstj == old) + js.j_firstj = js.j_lastj = js.j_njobs = 0; + } + if (jobs[js.j_lastj] == 0) + { + old = js.j_lastj--; + if (old < 0) + old = 0; + while (js.j_lastj != old) + { + if (js.j_lastj < 0) + js.j_lastj = js.j_jobslots - 1; + if (jobs[js.j_lastj] || js.j_lastj == old) /* needed if old == js.j_jobslots */ + break; + js.j_lastj--; + } + if (js.j_lastj == old) + js.j_firstj = js.j_lastj = js.j_njobs = 0; + } +} + +/* Delete all DEAD jobs that the user had received notification about. */ +static void +cleanup_dead_jobs () +{ + register int i; + int os; + PROCESS *discard; + + if (js.j_jobslots == 0 || jobs_list_frozen) + return; + + QUEUE_SIGCHLD(os); + + /* XXX could use js.j_firstj and js.j_lastj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (i < js.j_firstj && jobs[i]) + INTERNAL_DEBUG (("cleanup_dead_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj)); + if (i > js.j_lastj && jobs[i]) + INTERNAL_DEBUG(("cleanup_dead_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj)); + + if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i)) + delete_job (i, 0); + } + +#if defined (PROCESS_SUBSTITUTION) + procsub_prune (); + last_procsub_child = (PROCESS *)NULL; +#endif + +#if defined (COPROCESS_SUPPORT) + coproc_reap (); +#endif + + UNQUEUE_SIGCHLD(os); +} + +static int +processes_in_job (job) + int job; +{ + int nproc; + register PROCESS *p; + + nproc = 0; + p = jobs[job]->pipe; + do + { + p = p->next; + nproc++; + } + while (p != jobs[job]->pipe); + + return nproc; +} + +static void +delete_old_job (pid) + pid_t pid; +{ + PROCESS *p; + int job; + + job = find_job (pid, 0, &p); + if (job != NO_JOB) + { + INTERNAL_DEBUG (("delete_old_job: found pid %d in job %d with state %d", pid, job, jobs[job]->state)); + if (JOBSTATE (job) == JDEAD) + delete_job (job, DEL_NOBGPID); + else + { + internal_debug (_("forked pid %d appears in running job %d"), pid, job+1); + if (p) + p->pid = 0; + } + } +} + +/* Reallocate and compress the jobs list. This returns with a jobs array + whose size is a multiple of JOB_SLOTS and can hold the current number of + jobs. Heuristics are used to minimize the number of new reallocs. */ +static void +realloc_jobs_list () +{ + sigset_t set, oset; + int nsize, i, j, ncur, nprev; + JOB **nlist; + + ncur = nprev = NO_JOB; + nsize = ((js.j_njobs + JOB_SLOTS - 1) / JOB_SLOTS); + nsize *= JOB_SLOTS; + i = js.j_njobs % JOB_SLOTS; + if (i == 0 || i > (JOB_SLOTS >> 1)) + nsize += JOB_SLOTS; + + BLOCK_CHILD (set, oset); + nlist = (js.j_jobslots == nsize) ? jobs : (JOB **) xmalloc (nsize * sizeof (JOB *)); + + js.c_reaped = js.j_ndead = 0; + for (i = j = 0; i < js.j_jobslots; i++) + if (jobs[i]) + { + if (i == js.j_current) + ncur = j; + if (i == js.j_previous) + nprev = j; + nlist[j++] = jobs[i]; + if (jobs[i]->state == JDEAD) + { + js.j_ndead++; + js.c_reaped += processes_in_job (i); + } + } + +#if 0 + itrace ("realloc_jobs_list: resize jobs list from %d to %d", js.j_jobslots, nsize); + itrace ("realloc_jobs_list: j_lastj changed from %d to %d", js.j_lastj, (j > 0) ? j - 1 : 0); + itrace ("realloc_jobs_list: j_njobs changed from %d to %d", js.j_njobs, j); + itrace ("realloc_jobs_list: js.j_ndead %d js.c_reaped %d", js.j_ndead, js.c_reaped); +#endif + + js.j_firstj = 0; + js.j_lastj = (j > 0) ? j - 1 : 0; + js.j_njobs = j; + js.j_jobslots = nsize; + + /* Zero out remaining slots in new jobs list */ + for ( ; j < nsize; j++) + nlist[j] = (JOB *)NULL; + + if (jobs != nlist) + { + free (jobs); + jobs = nlist; + } + + if (ncur != NO_JOB) + js.j_current = ncur; + if (nprev != NO_JOB) + js.j_previous = nprev; + + /* Need to reset these */ + if (js.j_current == NO_JOB || js.j_previous == NO_JOB || js.j_current > js.j_lastj || js.j_previous > js.j_lastj) + reset_current (); + +#if 0 + itrace ("realloc_jobs_list: reset js.j_current (%d) and js.j_previous (%d)", js.j_current, js.j_previous); +#endif + + UNBLOCK_CHILD (oset); +} + +/* Compact the jobs list by removing dead jobs. Assume that we have filled + the jobs array to some predefined maximum. Called when the shell is not + the foreground process (subshell_environment != 0). Returns the first + available slot in the compacted list. If that value is js.j_jobslots, then + the list needs to be reallocated. The jobs array may be in new memory if + this returns > 0 and < js.j_jobslots. FLAGS is reserved for future use. */ +static int +compact_jobs_list (flags) + int flags; +{ + if (js.j_jobslots == 0 || jobs_list_frozen) + return js.j_jobslots; + + reap_dead_jobs (); + realloc_jobs_list (); + +#if 0 + itrace("compact_jobs_list: returning %d", (js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0); +#endif + + return ((js.j_lastj || jobs[js.j_lastj]) ? js.j_lastj + 1 : 0); +} + +/* Delete the job at INDEX from the job list. Must be called + with SIGCHLD blocked. */ +void +delete_job (job_index, dflags) + int job_index, dflags; +{ + register JOB *temp; + PROCESS *proc; + int ndel; + + if (js.j_jobslots == 0 || jobs_list_frozen) + return; + + if ((dflags & DEL_WARNSTOPPED) && subshell_environment == 0 && STOPPED (job_index)) + internal_warning (_("deleting stopped job %d with process group %ld"), job_index+1, (long)jobs[job_index]->pgrp); + temp = jobs[job_index]; + if (temp == 0) + return; + + if ((dflags & DEL_NOBGPID) == 0 && (temp->flags & (J_ASYNC|J_FOREGROUND)) == J_ASYNC) + { + proc = find_last_proc (job_index, 0); + if (proc) + bgp_add (proc->pid, process_exit_status (proc->status)); + } + + jobs[job_index] = (JOB *)NULL; + if (temp == js.j_lastmade) + js.j_lastmade = 0; + else if (temp == js.j_lastasync) + js.j_lastasync = 0; + + free (temp->wd); + ndel = discard_pipeline (temp->pipe); + + js.c_injobs -= ndel; + if (temp->state == JDEAD) + { + /* XXX - save_pipeline and restore_pipeline (e.g., for DEBUG trap) can + mess with this total. */ + js.c_reaped -= ndel; /* assumes proc hadn't been reaped earlier */ + js.j_ndead--; + if (js.c_reaped < 0) + { + INTERNAL_DEBUG (("delete_job (%d pgrp %d): js.c_reaped (%d) < 0 ndel = %d js.j_ndead = %d", job_index, temp->pgrp, js.c_reaped, ndel, js.j_ndead)); + js.c_reaped = 0; + } + } + + if (temp->deferred) + dispose_command (temp->deferred); + + free (temp); + + js.j_njobs--; + if (js.j_njobs == 0) + js.j_firstj = js.j_lastj = 0; + else if (jobs[js.j_firstj] == 0 || jobs[js.j_lastj] == 0) + reset_job_indices (); + + if (job_index == js.j_current || job_index == js.j_previous) + reset_current (); +} + +/* Must be called with SIGCHLD blocked. */ +void +nohup_job (job_index) + int job_index; +{ + register JOB *temp; + + if (js.j_jobslots == 0) + return; + + if (temp = jobs[job_index]) + temp->flags |= J_NOHUP; +} + +/* Get rid of the data structure associated with a process chain. */ +int +discard_pipeline (chain) + register PROCESS *chain; +{ + register PROCESS *this, *next; + int n; + + this = chain; + n = 0; + do + { + next = this->next; + FREE (this->command); + free (this); + n++; + this = next; + } + while (this != chain); + + return n; +} + +/* Add this process to the chain being built in the_pipeline. + NAME is the command string that will be exec'ed later. + PID is the process id of the child. */ +static void +add_process (name, pid) + char *name; + pid_t pid; +{ + PROCESS *t, *p; + +#if defined (RECYCLES_PIDS) + int j; + p = find_process (pid, 0, &j); + if (p) + { + if (j == NO_JOB) + internal_debug ("add_process: process %5ld (%s) in the_pipeline", (long)p->pid, p->command); + if (PALIVE (p)) + internal_warning (_("add_process: pid %5ld (%s) marked as still alive"), (long)p->pid, p->command); + p->running = PS_RECYCLED; /* mark as recycled */ + } +#endif + + t = (PROCESS *)xmalloc (sizeof (PROCESS)); + t->next = the_pipeline; + t->pid = pid; + WSTATUS (t->status) = 0; + t->running = PS_RUNNING; + t->command = name; + the_pipeline = t; + + if (t->next == 0) + t->next = t; + else + { + p = t->next; + while (p->next != t->next) + p = p->next; + p->next = t; + } +} + +/* Create a (dummy) PROCESS with NAME, PID, and STATUS, and make it the last + process in jobs[JID]->pipe. Used by the lastpipe code. */ +void +append_process (name, pid, status, jid) + char *name; + pid_t pid; + int status; + int jid; +{ + PROCESS *t, *p; + + t = (PROCESS *)xmalloc (sizeof (PROCESS)); + t->next = (PROCESS *)NULL; + t->pid = pid; + /* set process exit status using offset discovered by configure */ + t->status = (status & 0xff) << WEXITSTATUS_OFFSET; + t->running = PS_DONE; + t->command = name; + + js.c_reaped++; /* XXX */ + + for (p = jobs[jid]->pipe; p->next != jobs[jid]->pipe; p = p->next) + ; + p->next = t; + t->next = jobs[jid]->pipe; +} + +#if 0 +/* Take the last job and make it the first job. Must be called with + SIGCHLD blocked. */ +int +rotate_the_pipeline () +{ + PROCESS *p; + + if (the_pipeline->next == the_pipeline) + return; + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + the_pipeline = p; +} + +/* Reverse the order of the processes in the_pipeline. Must be called with + SIGCHLD blocked. */ +int +reverse_the_pipeline () +{ + PROCESS *p, *n; + + if (the_pipeline->next == the_pipeline) + return; + + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + p->next = (PROCESS *)NULL; + + n = REVERSE_LIST (the_pipeline, PROCESS *); + + the_pipeline = n; + for (p = the_pipeline; p->next; p = p->next) + ; + p->next = the_pipeline; +} +#endif + +/* Map FUNC over the list of jobs. If FUNC returns non-zero, + then it is time to stop mapping, and that is the return value + for map_over_jobs. FUNC is called with a JOB, arg1, arg2, + and INDEX. */ +static int +map_over_jobs (func, arg1, arg2) + sh_job_map_func_t *func; + int arg1, arg2; +{ + register int i; + int result; + sigset_t set, oset; + + if (js.j_jobslots == 0) + return 0; + + BLOCK_CHILD (set, oset); + + /* XXX could use js.j_firstj here */ + for (i = result = 0; i < js.j_jobslots; i++) + { + if (i < js.j_firstj && jobs[i]) + INTERNAL_DEBUG (("map_over_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj)); + if (i > js.j_lastj && jobs[i]) + INTERNAL_DEBUG (("map_over_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj)); + + if (jobs[i]) + { + result = (*func)(jobs[i], arg1, arg2, i); + if (result) + break; + } + } + + UNBLOCK_CHILD (oset); + + return (result); +} + +/* Cause all the jobs in the current pipeline to exit. */ +void +terminate_current_pipeline () +{ + if (pipeline_pgrp && pipeline_pgrp != shell_pgrp) + { + killpg (pipeline_pgrp, SIGTERM); + killpg (pipeline_pgrp, SIGCONT); + } +} + +/* Cause all stopped jobs to exit. */ +void +terminate_stopped_jobs () +{ + register int i; + + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (jobs[i] && STOPPED (i)) + { + killpg (jobs[i]->pgrp, SIGTERM); + killpg (jobs[i]->pgrp, SIGCONT); + } + } +} + +/* Cause all jobs, running or stopped, to receive a hangup signal. If + a job is marked J_NOHUP, don't send the SIGHUP. */ +void +hangup_all_jobs () +{ + register int i; + + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (jobs[i]) + { + if (jobs[i]->flags & J_NOHUP) + continue; + killpg (jobs[i]->pgrp, SIGHUP); + if (STOPPED (i)) + killpg (jobs[i]->pgrp, SIGCONT); + } + } +} + +void +kill_current_pipeline () +{ + stop_making_children (); + start_pipeline (); +} + +static PROCESS * +find_pid_in_pipeline (pid, pipeline, alive_only) + pid_t pid; + PROCESS *pipeline; + int alive_only; +{ + PROCESS *p; + + p = pipeline; + do + { + /* Return it if we found it. Don't ever return a recycled pid. */ + if (p->pid == pid && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p))) + return (p); + + p = p->next; + } + while (p != pipeline); + return ((PROCESS *)NULL); +} + +/* Return the pipeline that PID belongs to. Note that the pipeline + doesn't have to belong to a job. Must be called with SIGCHLD blocked. + If JOBP is non-null, return the index of the job containing PID. */ +static PROCESS * +find_pipeline (pid, alive_only, jobp) + pid_t pid; + int alive_only; + int *jobp; /* index into jobs list or NO_JOB */ +{ + int job; + PROCESS *p; + struct pipeline_saver *save; + + /* See if this process is in the pipeline that we are building. */ + p = (PROCESS *)NULL; + if (jobp) + *jobp = NO_JOB; + + if (the_pipeline && (p = find_pid_in_pipeline (pid, the_pipeline, alive_only))) + return (p); + + /* Is this process in a saved pipeline? */ + for (save = saved_pipeline; save; save = save->next) + if (save->pipeline && (p = find_pid_in_pipeline (pid, save->pipeline, alive_only))) + return (p); + +#if defined (PROCESS_SUBSTITUTION) + if (procsubs.nproc > 0 && (p = procsub_search (pid)) && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p))) + return (p); +#endif + + job = find_job (pid, alive_only, &p); + if (jobp) + *jobp = job; + return (job == NO_JOB) ? (PROCESS *)NULL : jobs[job]->pipe; +} + +/* Return the PROCESS * describing PID. If JOBP is non-null return the index + into the jobs array of the job containing PID. Must be called with + SIGCHLD blocked. */ +static PROCESS * +find_process (pid, alive_only, jobp) + pid_t pid; + int alive_only; + int *jobp; /* index into jobs list or NO_JOB */ +{ + PROCESS *p; + + p = find_pipeline (pid, alive_only, jobp); + while (p && p->pid != pid) + p = p->next; + return p; +} + +/* Return the job index that PID belongs to, or NO_JOB if it doesn't + belong to any job. Must be called with SIGCHLD blocked. */ +static int +find_job (pid, alive_only, procp) + pid_t pid; + int alive_only; + PROCESS **procp; +{ + register int i; + PROCESS *p; + + /* XXX could use js.j_firstj here, and should check js.j_lastj */ + for (i = 0; i < js.j_jobslots; i++) + { + if (i < js.j_firstj && jobs[i]) + INTERNAL_DEBUG (("find_job: job %d non-null before js.j_firstj (%d)", i, js.j_firstj)); + if (i > js.j_lastj && jobs[i]) + INTERNAL_DEBUG (("find_job: job %d non-null after js.j_lastj (%d)", i, js.j_lastj)); + + if (jobs[i]) + { + p = jobs[i]->pipe; + + do + { + if (p->pid == pid && ((alive_only == 0 && PRECYCLED(p) == 0) || PALIVE(p))) + { + if (procp) + *procp = p; + return (i); + } + + p = p->next; + } + while (p != jobs[i]->pipe); + } + } + + return (NO_JOB); +} + +/* Find a job given a PID. If BLOCK is non-zero, block SIGCHLD as + required by find_job. */ +int +get_job_by_pid (pid, block, procp) + pid_t pid; + int block; + PROCESS **procp; +{ + int job; + sigset_t set, oset; + + if (block) + BLOCK_CHILD (set, oset); + + job = find_job (pid, 0, procp); + + if (block) + UNBLOCK_CHILD (oset); + + return job; +} + +/* Print descriptive information about the job with leader pid PID. */ +void +describe_pid (pid) + pid_t pid; +{ + int job; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + job = find_job (pid, 0, NULL); + + if (job != NO_JOB) + fprintf (stderr, "[%d] %ld\n", job + 1, (long)pid); + else + programming_error (_("describe_pid: %ld: no such pid"), (long)pid); + + UNBLOCK_CHILD (oset); +} + +static char * +j_strsignal (s) + int s; +{ + char *x; + + x = strsignal (s); + if (x == 0) + { + x = retcode_name_buffer; + snprintf (x, sizeof(retcode_name_buffer), _("Signal %d"), s); + } + return x; +} + +static char * +printable_job_status (j, p, format) + int j; + PROCESS *p; + int format; +{ + static char *temp; + int es; + + temp = _("Done"); + + if (STOPPED (j) && format == 0) + { + if (posixly_correct == 0 || p == 0 || (WIFSTOPPED (p->status) == 0)) + temp = _("Stopped"); + else + { + temp = retcode_name_buffer; + snprintf (temp, sizeof(retcode_name_buffer), _("Stopped(%s)"), signal_name (WSTOPSIG (p->status))); + } + } + else if (RUNNING (j)) + temp = _("Running"); + else + { + if (WIFSTOPPED (p->status)) + temp = j_strsignal (WSTOPSIG (p->status)); + else if (WIFSIGNALED (p->status)) + temp = j_strsignal (WTERMSIG (p->status)); + else if (WIFEXITED (p->status)) + { + temp = retcode_name_buffer; + es = WEXITSTATUS (p->status); + if (es == 0) + { + strncpy (temp, _("Done"), sizeof (retcode_name_buffer) - 1); + temp[sizeof (retcode_name_buffer) - 1] = '\0'; + } + else if (posixly_correct) + snprintf (temp, sizeof(retcode_name_buffer), _("Done(%d)"), es); + else + snprintf (temp, sizeof(retcode_name_buffer), _("Exit %d"), es); + } + else + temp = _("Unknown status"); + } + + return temp; +} + +/* This is the way to print out information on a job if you + know the index. FORMAT is: + + JLIST_NORMAL) [1]+ Running emacs + JLIST_LONG ) [1]+ 2378 Running emacs + -1 ) [1]+ 2378 emacs + + JLIST_NORMAL) [1]+ Stopped ls | more + JLIST_LONG ) [1]+ 2369 Stopped ls + 2367 | more + JLIST_PID_ONLY) + Just list the pid of the process group leader (really + the process group). + JLIST_CHANGED_ONLY) + Use format JLIST_NORMAL, but list only jobs about which + the user has not been notified. */ + +/* Print status for pipeline P. If JOB_INDEX is >= 0, it is the index into + the JOBS array corresponding to this pipeline. FORMAT is as described + above. Must be called with SIGCHLD blocked. + + If you're printing a pipeline that's not in the jobs array, like the + current pipeline as it's being created, pass -1 for JOB_INDEX */ +static void +print_pipeline (p, job_index, format, stream) + PROCESS *p; + int job_index, format; + FILE *stream; +{ + PROCESS *first, *last, *show; + int es, name_padding; + char *temp; + + if (p == 0) + return; + + first = last = p; + while (last->next != first) + last = last->next; + + for (;;) + { + if (p != first) + fprintf (stream, format ? " " : " |"); + + if (format != JLIST_STANDARD) + fprintf (stream, "%5ld", (long)p->pid); + + fprintf (stream, " "); + + if (format > -1 && job_index >= 0) + { + show = format ? p : last; + temp = printable_job_status (job_index, show, format); + + if (p != first) + { + if (format) + { + if (show->running == first->running && + WSTATUS (show->status) == WSTATUS (first->status)) + temp = ""; + } + else + temp = (char *)NULL; + } + + if (temp) + { + fprintf (stream, "%s", temp); + + es = STRLEN (temp); + if (es == 0) + es = 2; /* strlen ("| ") */ + name_padding = LONGEST_SIGNAL_DESC - es; + + fprintf (stream, "%*s", name_padding, ""); + + if ((WIFSTOPPED (show->status) == 0) && + (WIFCONTINUED (show->status) == 0) && + WIFCORED (show->status)) + fprintf (stream, _("(core dumped) ")); + } + } + + if (p != first && format) + fprintf (stream, "| "); + + if (p->command) + fprintf (stream, "%s", p->command); + + if (p == last && job_index >= 0) + { + temp = current_working_directory (); + + if (RUNNING (job_index) && (IS_FOREGROUND (job_index) == 0)) + fprintf (stream, " &"); + + if (strcmp (temp, jobs[job_index]->wd) != 0) + fprintf (stream, + _(" (wd: %s)"), polite_directory_format (jobs[job_index]->wd)); + } + + if (format || (p == last)) + { + /* We need to add a CR only if this is an interactive shell, and + we're reporting the status of a completed job asynchronously. + We can't really check whether this particular job is being + reported asynchronously, so just add the CR if the shell is + currently interactive and asynchronous notification is enabled. */ + if (asynchronous_notification && interactive) + putc ('\r', stream); + fprintf (stream, "\n"); + } + + if (p == last) + break; + p = p->next; + } + fflush (stream); +} + +/* Print information to STREAM about jobs[JOB_INDEX] according to FORMAT. + Must be called with SIGCHLD blocked or queued with queue_sigchld */ +static void +pretty_print_job (job_index, format, stream) + int job_index, format; + FILE *stream; +{ + register PROCESS *p; + + /* Format only pid information about the process group leader? */ + if (format == JLIST_PID_ONLY) + { + fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid); + return; + } + + if (format == JLIST_CHANGED_ONLY) + { + if (IS_NOTIFIED (job_index)) + return; + format = JLIST_STANDARD; + } + + if (format != JLIST_NONINTERACTIVE) + fprintf (stream, "[%d]%c ", job_index + 1, + (job_index == js.j_current) ? '+': + (job_index == js.j_previous) ? '-' : ' '); + + if (format == JLIST_NONINTERACTIVE) + format = JLIST_LONG; + + p = jobs[job_index]->pipe; + + print_pipeline (p, job_index, format, stream); + + /* We have printed information about this job. When the job's + status changes, waitchld () sets the notification flag to 0. */ + jobs[job_index]->flags |= J_NOTIFIED; +} + +static int +print_job (job, format, state, job_index) + JOB *job; + int format, state, job_index; +{ + if (state == -1 || (JOB_STATE)state == job->state) + pretty_print_job (job_index, format, stdout); + return (0); +} + +void +list_one_job (job, format, ignore, job_index) + JOB *job; + int format, ignore, job_index; +{ + pretty_print_job (job_index, format, stdout); + cleanup_dead_jobs (); +} + +void +list_stopped_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, (int)JSTOPPED); +} + +void +list_running_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, (int)JRUNNING); +} + +/* List jobs. If FORMAT is non-zero, then the long form of the information + is printed, else just a short version. */ +void +list_all_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, -1); +} + +/* Fork, handling errors. Returns the pid of the newly made child, or 0. + COMMAND is just for remembering the name of the command; we don't do + anything else with it. ASYNC_P says what to do with the tty. If + non-zero, then don't give it away. */ +pid_t +make_child (command, flags) + char *command; + int flags; +{ + int async_p, forksleep; + sigset_t set, oset, termset, chldset, oset_copy; + pid_t pid; + SigHandler *oterm; + + sigemptyset (&oset_copy); + sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &oset_copy); + sigaddset (&oset_copy, SIGTERM); + + /* Block SIGTERM here and unblock in child after fork resets the + set of pending signals. */ + sigemptyset (&set); + sigaddset (&set, SIGCHLD); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGTERM); + + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + + /* Blocked in the parent, child will receive it after unblocking SIGTERM */ + if (interactive_shell) + oterm = set_signal_handler (SIGTERM, SIG_DFL); + + making_children (); + + async_p = (flags & FORK_ASYNC); + forksleep = 1; + +#if defined (BUFFERED_INPUT) + /* If default_buffered_input is active, we are reading a script. If + the command is asynchronous, we have already duplicated /dev/null + as fd 0, but have not changed the buffered stream corresponding to + the old fd 0. We don't want to sync the stream in this case. */ + if (default_buffered_input != -1 && + (!async_p || default_buffered_input > 0)) + sync_buffered_stream (default_buffered_input); +#endif /* BUFFERED_INPUT */ + + /* Create the child, handle severe errors. Retry on EAGAIN. */ + while ((pid = fork ()) < 0 && errno == EAGAIN && forksleep < FORKSLEEP_MAX) + { + /* bash-4.2 */ + /* keep SIGTERM blocked until we reset the handler to SIG_IGN */ + sigprocmask (SIG_SETMASK, &oset_copy, (sigset_t *)NULL); + /* If we can't create any children, try to reap some dead ones. */ + waitchld (-1, 0); + + errno = EAGAIN; /* restore errno */ + sys_error ("fork: retry"); + + if (sleep (forksleep) != 0) + break; + forksleep <<= 1; + + if (interrupt_state) + break; + sigprocmask (SIG_SETMASK, &set, (sigset_t *)NULL); + } + + if (pid != 0) + if (interactive_shell) + set_signal_handler (SIGTERM, oterm); + + if (pid < 0) + { + sys_error ("fork"); + + /* Kill all of the processes in the current pipeline. */ + terminate_current_pipeline (); + + /* Discard the current pipeline, if any. */ + if (the_pipeline) + kill_current_pipeline (); + + set_exit_status (EX_NOEXEC); + throw_to_top_level (); /* Reset signals, etc. */ + } + + if (pid == 0) + { + /* In the child. Give this child the right process group, set the + signals to the default state for a new process. */ + pid_t mypid; + + subshell_environment |= SUBSHELL_IGNTRAP; + + /* If this ends up being changed to modify or use `command' in the + child process, go back and change callers who free `command' in + the child process when this returns. */ + mypid = getpid (); +#if defined (BUFFERED_INPUT) + /* Close default_buffered_input if it's > 0. We don't close it if it's + 0 because that's the file descriptor used when redirecting input, + and it's wrong to close the file in that case. */ + unset_bash_input (0); +#endif /* BUFFERED_INPUT */ + + CLRINTERRUPT; /* XXX - children have their own interrupt state */ + + /* Restore top-level signal mask, including unblocking SIGTERM */ + restore_sigmask (); + + if (job_control) + { + /* All processes in this pipeline belong in the same + process group. */ + + if (pipeline_pgrp == 0) /* This is the first child. */ + pipeline_pgrp = mypid; + + /* Check for running command in backquotes. */ + if (pipeline_pgrp == shell_pgrp) + ignore_tty_job_signals (); + else + default_tty_job_signals (); + + /* Set the process group before trying to mess with the terminal's + process group. This is mandated by POSIX. */ + /* This is in accordance with the Posix 1003.1 standard, + section B.7.2.4, which says that trying to set the terminal + process group with tcsetpgrp() to an unused pgrp value (like + this would have for the first child) is an error. Section + B.4.3.3, p. 237 also covers this, in the context of job control + shells. */ + if (setpgid (mypid, pipeline_pgrp) < 0) + sys_error (_("child setpgid (%ld to %ld)"), (long)mypid, (long)pipeline_pgrp); + + /* By convention (and assumption above), if + pipeline_pgrp == shell_pgrp, we are making a child for + command substitution. + In this case, we don't want to give the terminal to the + shell's process group (we could be in the middle of a + pipeline, for example). */ + if ((flags & FORK_NOTERM) == 0 && async_p == 0 && pipeline_pgrp != shell_pgrp && ((subshell_environment&(SUBSHELL_ASYNC|SUBSHELL_PIPE)) == 0) && running_in_background == 0) + give_terminal_to (pipeline_pgrp, 0); + +#if defined (PGRP_PIPE) + if (pipeline_pgrp == mypid) + pipe_read (pgrp_pipe); +#endif + } + else /* Without job control... */ + { + if (pipeline_pgrp == 0) + pipeline_pgrp = shell_pgrp; + + /* If these signals are set to SIG_DFL, we encounter the curious + situation of an interactive ^Z to a running process *working* + and stopping the process, but being unable to do anything with + that process to change its state. On the other hand, if they + are set to SIG_IGN, jobs started from scripts do not stop when + the shell running the script gets a SIGTSTP and stops. */ + + default_tty_job_signals (); + } + +#if defined (PGRP_PIPE) + /* Release the process group pipe, since our call to setpgid () + is done. The last call to sh_closepipe is done in stop_pipeline. */ + sh_closepipe (pgrp_pipe); +#endif /* PGRP_PIPE */ + + /* Don't set last_asynchronous_pid in the child */ + +#if defined (RECYCLES_PIDS) + if (last_asynchronous_pid == mypid) + /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */ + last_asynchronous_pid = 1; +#endif + } + else + { + /* In the parent. Remember the pid of the child just created + as the proper pgrp if this is the first child. */ + + if (job_control) + { + if (pipeline_pgrp == 0) + { + pipeline_pgrp = pid; + /* Don't twiddle terminal pgrps in the parent! This is the bug, + not the good thing of twiddling them in the child! */ + /* give_terminal_to (pipeline_pgrp, 0); */ + } + /* This is done on the recommendation of the Rationale section of + the POSIX 1003.1 standard, where it discusses job control and + shells. It is done to avoid possible race conditions. (Ref. + 1003.1 Rationale, section B.4.3.3, page 236). */ + setpgid (pid, pipeline_pgrp); + } + else + { + if (pipeline_pgrp == 0) + pipeline_pgrp = shell_pgrp; + } + + /* Place all processes into the jobs array regardless of the + state of job_control. */ + add_process (command, pid); + + if (async_p) + last_asynchronous_pid = pid; +#if defined (RECYCLES_PIDS) + else if (last_asynchronous_pid == pid) + /* Avoid pid aliasing. 1 seems like a safe, unusual pid value. */ + last_asynchronous_pid = 1; +#endif + + /* Delete the saved status for any job containing this PID in case it's + been reused. */ + delete_old_job (pid); + + /* Perform the check for pid reuse unconditionally. Some systems reuse + PIDs before giving a process CHILD_MAX/_SC_CHILD_MAX unique ones. */ + bgp_delete (pid); /* new process, discard any saved status */ + + last_made_pid = pid; + + /* keep stats */ + js.c_totforked++; + js.c_living++; + + /* Unblock SIGTERM, SIGINT, and SIGCHLD unless creating a pipeline, in + which case SIGCHLD remains blocked until all commands in the pipeline + have been created (execute_cmd.c:execute_pipeline()). */ + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + } + + return (pid); +} + +/* These two functions are called only in child processes. */ +void +ignore_tty_job_signals () +{ + set_signal_handler (SIGTSTP, SIG_IGN); + set_signal_handler (SIGTTIN, SIG_IGN); + set_signal_handler (SIGTTOU, SIG_IGN); +} + +/* Reset the tty-generated job control signals to SIG_DFL unless that signal + was ignored at entry to the shell, in which case we need to set it to + SIG_IGN in the child. We can't rely on resetting traps, since the hard + ignored signals can't be trapped. */ +void +default_tty_job_signals () +{ + if (signal_is_trapped (SIGTSTP) == 0 && signal_is_hard_ignored (SIGTSTP)) + set_signal_handler (SIGTSTP, SIG_IGN); + else + set_signal_handler (SIGTSTP, SIG_DFL); + + if (signal_is_trapped (SIGTTIN) == 0 && signal_is_hard_ignored (SIGTTIN)) + set_signal_handler (SIGTTIN, SIG_IGN); + else + set_signal_handler (SIGTTIN, SIG_DFL); + + if (signal_is_trapped (SIGTTOU) == 0 && signal_is_hard_ignored (SIGTTOU)) + set_signal_handler (SIGTTOU, SIG_IGN); + else + set_signal_handler (SIGTTOU, SIG_DFL); +} + +/* Called once in a parent process. */ +void +get_original_tty_job_signals () +{ + static int fetched = 0; + + if (fetched == 0) + { + if (interactive_shell) + { + set_original_signal (SIGTSTP, SIG_DFL); + set_original_signal (SIGTTIN, SIG_DFL); + set_original_signal (SIGTTOU, SIG_DFL); + } + else + { + get_original_signal (SIGTSTP); + get_original_signal (SIGTTIN); + get_original_signal (SIGTTOU); + } + fetched = 1; + } +} + +/* When we end a job abnormally, or if we stop a job, we set the tty to the + state kept in here. When a job ends normally, we set the state in here + to the state of the tty. */ + +static TTYSTRUCT shell_tty_info; + +#if defined (NEW_TTY_DRIVER) +static struct tchars shell_tchars; +static struct ltchars shell_ltchars; +#endif /* NEW_TTY_DRIVER */ + +#if defined (NEW_TTY_DRIVER) && defined (DRAIN_OUTPUT) +/* Since the BSD tty driver does not allow us to change the tty modes + while simultaneously waiting for output to drain and preserving + typeahead, we have to drain the output ourselves before calling + ioctl. We cheat by finding the length of the output queue, and + using select to wait for an appropriate length of time. This is + a hack, and should be labeled as such (it's a hastily-adapted + mutation of a `usleep' implementation). It's only reason for + existing is the flaw in the BSD tty driver. */ + +static int ttspeeds[] = +{ + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, + 1800, 2400, 4800, 9600, 19200, 38400 +}; + +static void +draino (fd, ospeed) + int fd, ospeed; +{ + register int delay = ttspeeds[ospeed]; + int n; + + if (!delay) + return; + + while ((ioctl (fd, TIOCOUTQ, &n) == 0) && n) + { + if (n > (delay / 100)) + { + struct timeval tv; + + n *= 10; /* 2 bits more for conservativeness. */ + tv.tv_sec = n / delay; + tv.tv_usec = ((n % delay) * 1000000) / delay; + select (fd, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv); + } + else + break; + } +} +#endif /* NEW_TTY_DRIVER && DRAIN_OUTPUT */ + +/* Return the fd from which we are actually getting input. */ +#define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr) + +/* Fill the contents of shell_tty_info with the current tty info. */ +int +get_tty_state () +{ + int tty; + + tty = input_tty (); + if (tty != -1) + { +#if defined (NEW_TTY_DRIVER) + ioctl (tty, TIOCGETP, &shell_tty_info); + ioctl (tty, TIOCGETC, &shell_tchars); + ioctl (tty, TIOCGLTC, &shell_ltchars); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) + ioctl (tty, TCGETA, &shell_tty_info); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) + if (tcgetattr (tty, &shell_tty_info) < 0) + { +#if 0 + /* Only print an error message if we're really interactive at + this time. */ + if (interactive) + sys_error ("[%ld: %d (%d)] tcgetattr", (long)getpid (), shell_level, tty); +#endif + return -1; + } +#endif /* TERMIOS_TTY_DRIVER */ + if (check_window_size) + get_new_window_size (0, (int *)0, (int *)0); + } + return 0; +} + +/* Make the current tty use the state in shell_tty_info. */ +int +set_tty_state () +{ + int tty; + + tty = input_tty (); + if (tty != -1) + { +#if defined (NEW_TTY_DRIVER) +# if defined (DRAIN_OUTPUT) + draino (tty, shell_tty_info.sg_ospeed); +# endif /* DRAIN_OUTPUT */ + ioctl (tty, TIOCSETN, &shell_tty_info); + ioctl (tty, TIOCSETC, &shell_tchars); + ioctl (tty, TIOCSLTC, &shell_ltchars); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) + ioctl (tty, TCSETAW, &shell_tty_info); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) + if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0) + { + /* Only print an error message if we're really interactive at + this time. */ + if (interactive) + sys_error ("[%ld: %d (%d)] tcsetattr", (long)getpid (), shell_level, tty); + return -1; + } +#endif /* TERMIOS_TTY_DRIVER */ + } + return 0; +} + +/* Given an index into the jobs array JOB, return the PROCESS struct of the last + process in that job's pipeline. This is the one whose exit status + counts. Must be called with SIGCHLD blocked or queued. */ +static PROCESS * +find_last_proc (job, block) + int job; + int block; +{ + register PROCESS *p; + sigset_t set, oset; + + if (block) + BLOCK_CHILD (set, oset); + + p = jobs[job]->pipe; + while (p && p->next != jobs[job]->pipe) + p = p->next; + + if (block) + UNBLOCK_CHILD (oset); + + return (p); +} + +static pid_t +find_last_pid (job, block) + int job; + int block; +{ + PROCESS *p; + + p = find_last_proc (job, block); + /* Possible race condition here. */ + return p->pid; +} + +/* Wait for a particular child of the shell to finish executing. + This low-level function prints an error message if PID is not + a child of this shell. It returns -1 if it fails, or whatever + wait_for returns otherwise. If the child is not found in the + jobs table, it returns 127. If FLAGS doesn't include JWAIT_PERROR, + we suppress the error message if PID isn't found. */ + +int +wait_for_single_pid (pid, flags) + pid_t pid; + int flags; +{ + register PROCESS *child; + sigset_t set, oset; + int r, job, alive; + + BLOCK_CHILD (set, oset); + child = find_pipeline (pid, 0, (int *)NULL); + UNBLOCK_CHILD (oset); + + if (child == 0) + { + r = bgp_search (pid); + if (r >= 0) + return r; + } + + if (child == 0) + { + if (flags & JWAIT_PERROR) + internal_error (_("wait: pid %ld is not a child of this shell"), (long)pid); + return (257); + } + + alive = 0; + do + { + r = wait_for (pid, 0); + if ((flags & JWAIT_FORCE) == 0) + break; + + BLOCK_CHILD (set, oset); + alive = PALIVE (child); + UNBLOCK_CHILD (oset); + } + while (alive); + + /* POSIX.2: if we just waited for a job, we can remove it from the jobs + table. */ + BLOCK_CHILD (set, oset); + job = find_job (pid, 0, NULL); + if (job != NO_JOB && jobs[job] && DEADJOB (job)) + jobs[job]->flags |= J_NOTIFIED; + UNBLOCK_CHILD (oset); + + /* If running in posix mode, remove the job from the jobs table immediately */ + if (posixly_correct) + { + cleanup_dead_jobs (); + bgp_delete (pid); + } + + /* Check for a trapped signal interrupting the wait builtin and jump out */ + CHECK_WAIT_INTR; + + return r; +} + +/* Wait for all of the background processes started by this shell to finish. */ +int +wait_for_background_pids (ps) + struct procstat *ps; +{ + register int i, r; + int any_stopped, check_async, njobs; + sigset_t set, oset; + pid_t pid; + + for (njobs = any_stopped = 0, check_async = 1;;) + { + BLOCK_CHILD (set, oset); + + /* find first running job; if none running in foreground, break */ + /* XXX could use js.j_firstj and js.j_lastj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (i < js.j_firstj && jobs[i]) + INTERNAL_DEBUG (("wait_for_background_pids: job %d non-null before js.j_firstj (%d)", i, js.j_firstj)); + if (i > js.j_lastj && jobs[i]) + INTERNAL_DEBUG (("wait_for_background_pids: job %d non-null after js.j_lastj (%d)", i, js.j_lastj)); + + if (jobs[i] && STOPPED (i)) + { + builtin_warning ("job %d[%d] stopped", i+1, find_last_pid (i, 0)); + any_stopped = 1; + } + + if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0) + break; + } + if (i == js.j_jobslots) + { + UNBLOCK_CHILD (oset); + break; + } + + /* now wait for the last pid in that job. */ + pid = find_last_pid (i, 0); + UNBLOCK_CHILD (oset); + QUIT; + errno = 0; /* XXX */ + r = wait_for_single_pid (pid, JWAIT_PERROR); + if (ps) + { + ps->pid = pid; + ps->status = (r < 0 || r > 256) ? 127 : r; + } + if (r == -1 && errno == ECHILD) + { + /* If we're mistaken about job state, compensate. */ + check_async = 0; + mark_all_jobs_as_dead (); + } + njobs++; + } + +#if defined (PROCESS_SUBSTITUTION) + procsub_waitall (); +#endif + + /* POSIX.2 says the shell can discard the statuses of all completed jobs if + `wait' is called with no arguments. */ + mark_dead_jobs_as_notified (1); + cleanup_dead_jobs (); + bgp_clear (); + + return njobs; +} + +/* Make OLD_SIGINT_HANDLER the SIGINT signal handler. */ +#define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids +static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER; + +static int wait_sigint_received; +static int child_caught_sigint; + +int waiting_for_child; + +/* Clean up state after longjmp to wait_intr_buf */ +void +wait_sigint_cleanup () +{ + queue_sigchld = 0; + waiting_for_child = 0; + restore_sigint_handler (); +} + +static void +restore_sigint_handler () +{ + if (old_sigint_handler != INVALID_SIGNAL_HANDLER) + { + set_signal_handler (SIGINT, old_sigint_handler); + old_sigint_handler = INVALID_SIGNAL_HANDLER; + waiting_for_child = 0; + } +} + +/* Handle SIGINT while we are waiting for children in a script to exit. + The `wait' builtin should be interruptible, but all others should be + effectively ignored (i.e. not cause the shell to exit). */ +static sighandler +wait_sigint_handler (sig) + int sig; +{ + SigHandler *sigint_handler; + + if (this_shell_builtin && this_shell_builtin == wait_builtin) + { + set_exit_status (128+SIGINT); + restore_sigint_handler (); + /* If we got a SIGINT while in `wait', and SIGINT is trapped, do + what POSIX.2 says (see builtins/wait.def for more info). */ + if (this_shell_builtin && this_shell_builtin == wait_builtin && + signal_is_trapped (SIGINT) && + ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler)) + { + trap_handler (SIGINT); /* set pending_traps[SIGINT] */ + wait_signal_received = SIGINT; + if (wait_intr_flag) + sh_longjmp (wait_intr_buf, 1); + else + /* Let CHECK_WAIT_INTR handle it in wait_for/waitchld */ + SIGRETURN (0); + } + else /* wait_builtin but signal not trapped, treat as interrupt */ + kill (getpid (), SIGINT); + } + + /* XXX - should this be interrupt_state? If it is, the shell will act + as if it got the SIGINT interrupt. */ + if (waiting_for_child) + wait_sigint_received = 1; + else + { + set_exit_status (128+SIGINT); + restore_sigint_handler (); + kill (getpid (), SIGINT); + } + + /* Otherwise effectively ignore the SIGINT and allow the running job to + be killed. */ + SIGRETURN (0); +} + +static int +process_exit_signal (status) + WAIT status; +{ + return (WIFSIGNALED (status) ? WTERMSIG (status) : 0); +} + +static int +process_exit_status (status) + WAIT status; +{ + if (WIFSIGNALED (status)) + return (128 + WTERMSIG (status)); + else if (WIFSTOPPED (status) == 0) + return (WEXITSTATUS (status)); + else + return (EXECUTION_SUCCESS); +} + +static WAIT +job_signal_status (job) + int job; +{ + register PROCESS *p; + WAIT s; + + p = jobs[job]->pipe; + do + { + s = p->status; + if (WIFSIGNALED(s) || WIFSTOPPED(s)) + break; + p = p->next; + } + while (p != jobs[job]->pipe); + + return s; +} + +/* Return the exit status of the last process in the pipeline for job JOB. + This is the exit status of the entire job. */ +static WAIT +raw_job_exit_status (job) + int job; +{ + register PROCESS *p; + int fail; + WAIT ret; + + if (jobs[job]->flags & J_PIPEFAIL) + { + fail = 0; + p = jobs[job]->pipe; + do + { + if (WSTATUS (p->status) != EXECUTION_SUCCESS) + fail = WSTATUS(p->status); + p = p->next; + } + while (p != jobs[job]->pipe); + WSTATUS (ret) = fail; + return ret; + } + + for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next) + ; + return (p->status); +} + +/* Return the exit status of job JOB. This is the exit status of the last + (rightmost) process in the job's pipeline, modified if the job was killed + by a signal or stopped. */ +int +job_exit_status (job) + int job; +{ + return (process_exit_status (raw_job_exit_status (job))); +} + +int +job_exit_signal (job) + int job; +{ + return (process_exit_signal (raw_job_exit_status (job))); +} + +#define FIND_CHILD(pid, child) \ + do \ + { \ + child = find_pipeline (pid, 0, (int *)NULL); \ + if (child == 0) \ + { \ + give_terminal_to (shell_pgrp, 0); \ + UNBLOCK_CHILD (oset); \ + internal_error (_("wait_for: No record of process %ld"), (long)pid); \ + restore_sigint_handler (); \ + return (termination_state = 127); \ + } \ + } \ + while (0) + +/* Wait for pid (one of our children) to terminate, then + return the termination state. Returns 127 if PID is not found in + the jobs table. Returns -1 if waitchld() returns -1, indicating + that there are no unwaited-for child processes. */ +int +wait_for (pid, flags) + pid_t pid; + int flags; +{ + int job, termination_state, r; + WAIT s; + register PROCESS *child; + sigset_t set, oset; + + /* In the case that this code is interrupted, and we longjmp () out of it, + we are relying on the code in throw_to_top_level () to restore the + top-level signal mask. */ + child = 0; + BLOCK_CHILD (set, oset); + + /* Ignore interrupts while waiting for a job run without job control + to finish. We don't want the shell to exit if an interrupt is + received, only if one of the jobs run is killed via SIGINT. If + job control is not set, the job will be run in the same pgrp as + the shell, and the shell will see any signals the job gets. In + fact, we want this set every time the waiting shell and the waited- + for process are in the same process group, including command + substitution. */ + + /* This is possibly a race condition -- should it go in stop_pipeline? */ + wait_sigint_received = child_caught_sigint = 0; + if (job_control == 0 || (subshell_environment&SUBSHELL_COMSUB)) + { + SigHandler *temp_sigint_handler; + + temp_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler); + if (temp_sigint_handler == wait_sigint_handler) + internal_debug ("wait_for: recursively setting old_sigint_handler to wait_sigint_handler: running_trap = %d", running_trap); + else + old_sigint_handler = temp_sigint_handler; + waiting_for_child = 0; + if (old_sigint_handler == SIG_IGN) + set_signal_handler (SIGINT, old_sigint_handler); + } + + termination_state = last_command_exit_value; + + if (interactive && job_control == 0) + QUIT; + /* Check for terminating signals and exit the shell if we receive one */ + CHECK_TERMSIG; + + /* Check for a trapped signal interrupting the wait builtin and jump out */ + CHECK_WAIT_INTR; + + /* If we say wait_for (), then we have a record of this child somewhere. + If it and none of its peers are running, don't call waitchld(). */ + + job = NO_JOB; + do + { + if (pid != ANY_PID) + FIND_CHILD (pid, child); + + /* If this child is part of a job, then we are really waiting for the + job to finish. Otherwise, we are waiting for the child to finish. + We check for JDEAD in case the job state has been set by waitchld + after receipt of a SIGCHLD. */ + if (job == NO_JOB && pid != ANY_PID) /* XXX -- && pid != ANY_PID ? */ + job = find_job (pid, 0, NULL); + + /* waitchld() takes care of setting the state of the job. If the job + has already exited before this is called, sigchld_handler will have + called waitchld and the state will be set to JDEAD. */ + + if (pid == ANY_PID || PRUNNING(child) || (job != NO_JOB && RUNNING (job))) + { + int old_waiting; + + queue_sigchld = 1; + old_waiting = waiting_for_child; + waiting_for_child = 1; + /* XXX - probably not strictly necessary but we want to catch + everything that happened before we switch the behavior of + trap_handler to longjmp on a trapped signal (waiting_for_child) */ + CHECK_WAIT_INTR; + r = waitchld (pid, 1); /* XXX */ + waiting_for_child = old_waiting; +#if 0 +itrace("wait_for: blocking wait for %d returns %d child = %p", (int)pid, r, child); +#endif + queue_sigchld = 0; + if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin) + { + termination_state = -1; + /* XXX - restore sigint handler here */ + restore_sigint_handler (); + goto wait_for_return; + } + + /* If child is marked as running, but waitpid() returns -1/ECHILD, + there is something wrong. Somewhere, wait should have returned + that child's pid. Mark the child as not running and the job, + if it exists, as JDEAD. */ + if (r == -1 && errno == ECHILD) + { + if (child) + { + child->running = PS_DONE; + WSTATUS (child->status) = 0; /* XXX -- can't find true status */ + } + js.c_living = 0; /* no living child processes */ + if (job != NO_JOB) + { + jobs[job]->state = JDEAD; + js.c_reaped++; + js.j_ndead++; + } + if (pid == ANY_PID) + { + termination_state = -1; + break; + } + } + } + + /* If the shell is interactive, and job control is disabled, see + if the foreground process has died due to SIGINT and jump out + of the wait loop if it has. waitchld has already restored the + old SIGINT signal handler. */ + if (interactive && job_control == 0) + QUIT; + /* Check for terminating signals and exit the shell if we receive one */ + CHECK_TERMSIG; + + /* Check for a trapped signal interrupting the wait builtin and jump out */ + CHECK_WAIT_INTR; + + if (pid == ANY_PID) + { + /* XXX - could set child but we don't have a handle on what waitchld + reaps. Leave termination_state alone. */ + restore_sigint_handler (); + goto wait_for_return; + } + } + while (PRUNNING (child) || (job != NO_JOB && RUNNING (job))); + + /* Restore the original SIGINT signal handler before we return. */ + restore_sigint_handler (); + + /* The exit state of the command is either the termination state of the + child, or the termination state of the job. If a job, the status + of the last child in the pipeline is the significant one. If the command + or job was terminated by a signal, note that value also. */ + termination_state = (job != NO_JOB) ? job_exit_status (job) + : (child ? process_exit_status (child->status) : EXECUTION_SUCCESS); + last_command_exit_signal = (job != NO_JOB) ? job_exit_signal (job) + : (child ? process_exit_signal (child->status) : 0); + + /* XXX */ + if ((job != NO_JOB && JOBSTATE (job) == JSTOPPED) || (child && WIFSTOPPED (child->status))) + termination_state = 128 + WSTOPSIG (child->status); + + if (job == NO_JOB || IS_JOBCONTROL (job)) + { + /* XXX - under what circumstances is a job not present in the jobs + table (job == NO_JOB)? + 1. command substitution + + In the case of command substitution, at least, it's probably not + the right thing to give the terminal to the shell's process group, + even though there is code in subst.c:command_substitute to work + around it. + + Things that don't: + $PROMPT_COMMAND execution + process substitution + */ +#if 0 +if (job == NO_JOB) + itrace("wait_for: job == NO_JOB, giving the terminal to shell_pgrp (%ld)", (long)shell_pgrp); +#endif + /* Don't modify terminal pgrp if we are running in background or a + subshell. Make sure subst.c:command_substitute uses the same + conditions to determine whether or not it should undo this and + give the terminal to pipeline_pgrp. */ + + if ((flags & JWAIT_NOTERM) == 0 && running_in_background == 0 && + (subshell_environment & (SUBSHELL_ASYNC|SUBSHELL_PIPE)) == 0) + give_terminal_to (shell_pgrp, 0); + } + + /* If the command did not exit cleanly, or the job is just + being stopped, then reset the tty state back to what it + was before this command. Reset the tty state and notify + the user of the job termination only if the shell is + interactive. Clean up any dead jobs in either case. */ + if (job != NO_JOB) + { + if (interactive_shell && subshell_environment == 0) + { + /* This used to use `child->status'. That's wrong, however, for + pipelines. `child' is the first process in the pipeline. It's + likely that the process we want to check for abnormal termination + or stopping is the last process in the pipeline, especially if + it's long-lived and the first process is short-lived. Since we + know we have a job here, we can check all the processes in this + job's pipeline and see if one of them stopped or terminated due + to a signal. We might want to change this later to just check + the last process in the pipeline. If no process exits due to a + signal, S is left as the status of the last job in the pipeline. */ + s = job_signal_status (job); + + if (WIFSIGNALED (s) || WIFSTOPPED (s)) + { + set_tty_state (); + + /* If the current job was stopped or killed by a signal, and + the user has requested it, get a possibly new window size */ + if (check_window_size && (job == js.j_current || IS_FOREGROUND (job))) + get_new_window_size (0, (int *)0, (int *)0); + } + else +#if defined (READLINE) + /* We don't want to do this if we are running a process during + programmable completion or a command bound to `bind -x'. */ + if (RL_ISSTATE (RL_STATE_COMPLETING|RL_STATE_DISPATCHING|RL_STATE_TERMPREPPED) == 0) +#endif + get_tty_state (); + + /* If job control is enabled, the job was started with job + control, the job was the foreground job, and it was killed + by SIGINT, then print a newline to compensate for the kernel + printing the ^C without a trailing newline. */ + if (job_control && IS_JOBCONTROL (job) && IS_FOREGROUND (job) && + WIFSIGNALED (s) && WTERMSIG (s) == SIGINT) + { + /* If SIGINT is not trapped and the shell is in a for, while, + or until loop, act as if the shell received SIGINT as + well, so the loop can be broken. This doesn't call the + SIGINT signal handler; maybe it should. */ + if (signal_is_trapped (SIGINT) == 0 && (loop_level || (shell_compatibility_level > 32 && executing_list))) + ADDINTERRUPT; + /* Call any SIGINT trap handler if the shell is running a loop, so + the loop can be broken. This seems more useful and matches the + behavior when the shell is running a builtin command in a loop + when it is interrupted. Change ADDINTERRUPT to + trap_handler (SIGINT) to run the trap without interrupting the + loop. */ + else if (signal_is_trapped (SIGINT) && loop_level) + ADDINTERRUPT; + /* If an interactive shell with job control enabled is sourcing + a file, allow the interrupt to terminate the file sourcing. */ + else if (interactive_shell && signal_is_trapped (SIGINT) == 0 && sourcelevel) + ADDINTERRUPT; + else + { + putchar ('\n'); + fflush (stdout); + } + } + } + else if ((subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PIPE)) && wait_sigint_received) + { + /* If waiting for a job in a subshell started to do command + substitution or to run a pipeline element that consists of + something like a while loop or a for loop, simulate getting + and being killed by the SIGINT to pass the status back to our + parent. */ + if (child_caught_sigint == 0 && signal_is_trapped (SIGINT) == 0) + { + UNBLOCK_CHILD (oset); + old_sigint_handler = set_signal_handler (SIGINT, SIG_DFL); + if (old_sigint_handler == SIG_IGN) + restore_sigint_handler (); + else + kill (getpid (), SIGINT); + } + } + else if (interactive_shell == 0 && subshell_environment == 0 && IS_FOREGROUND (job)) + { + s = job_signal_status (job); + + /* If we are non-interactive, but job control is enabled, and the job + died due to SIGINT, pretend we got the SIGINT */ + if (job_control && IS_JOBCONTROL (job) && WIFSIGNALED (s) && WTERMSIG (s) == SIGINT) + { + ADDINTERRUPT; /* For now */ + } + + if (check_window_size) + get_new_window_size (0, (int *)0, (int *)0); + } + + /* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD + signal handler path */ + if (DEADJOB (job) && IS_FOREGROUND (job) /*&& subshell_environment == 0*/) + setjstatus (job); + + /* If this job is dead, notify the user of the status. If the shell + is interactive, this will display a message on the terminal. If + the shell is not interactive, make sure we turn on the notify bit + so we don't get an unwanted message about the job's termination, + and so delete_job really clears the slot in the jobs table. */ + notify_and_cleanup (); + } + +wait_for_return: + + UNBLOCK_CHILD (oset); + + return (termination_state); +} + +/* Wait for the last process in the pipeline for JOB. Returns whatever + wait_for returns: the last process's termination state or -1 if there + are no unwaited-for child processes or an error occurs. If FLAGS + includes JWAIT_FORCE, we wait for the job to terminate, no just change + state */ +int +wait_for_job (job, flags, ps) + int job, flags; + struct procstat *ps; +{ + pid_t pid; + int r, state; + sigset_t set, oset; + + BLOCK_CHILD(set, oset); + state = JOBSTATE (job); + if (state == JSTOPPED) + internal_warning (_("wait_for_job: job %d is stopped"), job+1); + + pid = find_last_pid (job, 0); + UNBLOCK_CHILD(oset); + + do + { + r = wait_for (pid, 0); + if (r == -1 && errno == ECHILD) + mark_all_jobs_as_dead (); + + CHECK_WAIT_INTR; + + if ((flags & JWAIT_FORCE) == 0) + break; + + BLOCK_CHILD (set, oset); + state = (job != NO_JOB && jobs[job]) ? JOBSTATE (job) : JDEAD; + UNBLOCK_CHILD (oset); + } + while (state != JDEAD); + + /* POSIX.2: we can remove the job from the jobs table if we just waited + for it. */ + BLOCK_CHILD (set, oset); + if (job != NO_JOB && jobs[job] && DEADJOB (job)) + jobs[job]->flags |= J_NOTIFIED; + UNBLOCK_CHILD (oset); + + if (ps) + { + ps->pid = pid; + ps->status = (r < 0) ? 127 : r; + } + return r; +} + +/* Wait for any background job started by this shell to finish. Very + similar to wait_for_background_pids(). Returns the exit status of + the next exiting job, -1 if there are no background jobs. The caller + is responsible for translating -1 into the right return value. RPID, + if non-null, gets the pid of the job's process leader. */ +int +wait_for_any_job (flags, ps) + int flags; + struct procstat *ps; +{ + pid_t pid; + int i, r; + sigset_t set, oset; + + if (jobs_list_frozen) + return -1; + + /* First see if there are any unnotified dead jobs that we can report on */ + BLOCK_CHILD (set, oset); + for (i = 0; i < js.j_jobslots; i++) + { + if ((flags & JWAIT_WAITING) && jobs[i] && IS_WAITING (i) == 0) + continue; /* if we don't want it, skip it */ + if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i) == 0) + { +return_job: + r = job_exit_status (i); + pid = find_last_pid (i, 0); + if (ps) + { + ps->pid = pid; + ps->status = r; + } + notify_of_job_status (); /* XXX */ + delete_job (i, 0); +#if defined (COPROCESS_SUPPORT) + coproc_reap (); +#endif + UNBLOCK_CHILD (oset); + return r; + } + } + UNBLOCK_CHILD (oset); + + /* At this point, we have no dead jobs in the jobs table. Wait until we + get one, even if it takes multiple pids exiting. */ + for (;;) + { + /* Make sure there is a background job to wait for */ + BLOCK_CHILD (set, oset); + for (i = 0; i < js.j_jobslots; i++) + if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0) + break; + if (i == js.j_jobslots) + { + UNBLOCK_CHILD (oset); + return -1; + } + + UNBLOCK_CHILD (oset); + + QUIT; + CHECK_TERMSIG; + CHECK_WAIT_INTR; + + errno = 0; + r = wait_for (ANY_PID, 0); /* special sentinel value for wait_for */ + if (r == -1 && errno == ECHILD) + mark_all_jobs_as_dead (); + + /* Now we see if we have any dead jobs and return the first one */ + BLOCK_CHILD (set, oset); + for (i = 0; i < js.j_jobslots; i++) + { + if ((flags & JWAIT_WAITING) && jobs[i] && IS_WAITING (i) == 0) + continue; /* if we don't want it, skip it */ + if (jobs[i] && DEADJOB (i)) + goto return_job; + } + UNBLOCK_CHILD (oset); + } + + return -1; +} + +/* Print info about dead jobs, and then delete them from the list + of known jobs. This does not actually delete jobs when the + shell is not interactive, because the dead jobs are not marked + as notified. */ +void +notify_and_cleanup () +{ + if (jobs_list_frozen) + return; + + if (interactive || interactive_shell == 0 || sourcelevel) + notify_of_job_status (); + + cleanup_dead_jobs (); +} + +/* Make dead jobs disappear from the jobs array without notification. + This is used when the shell is not interactive. */ +void +reap_dead_jobs () +{ + mark_dead_jobs_as_notified (0); + cleanup_dead_jobs (); +} + +/* Return the next closest (chronologically) job to JOB which is in + STATE. STATE can be JSTOPPED, JRUNNING. NO_JOB is returned if + there is no next recent job. */ +static int +most_recent_job_in_state (job, state) + int job; + JOB_STATE state; +{ + register int i, result; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + for (result = NO_JOB, i = job - 1; i >= 0; i--) + { + if (jobs[i] && (JOBSTATE (i) == state)) + { + result = i; + break; + } + } + + UNBLOCK_CHILD (oset); + + return (result); +} + +/* Return the newest *stopped* job older than JOB, or NO_JOB if not + found. */ +static int +job_last_stopped (job) + int job; +{ + return (most_recent_job_in_state (job, JSTOPPED)); +} + +/* Return the newest *running* job older than JOB, or NO_JOB if not + found. */ +static int +job_last_running (job) + int job; +{ + return (most_recent_job_in_state (job, JRUNNING)); +} + +/* Make JOB be the current job, and make previous be useful. Must be + called with SIGCHLD blocked. */ +static void +set_current_job (job) + int job; +{ + int candidate; + + if (js.j_current != job) + { + js.j_previous = js.j_current; + js.j_current = job; + } + + /* First choice for previous job is the old current job. */ + if (js.j_previous != js.j_current && + js.j_previous != NO_JOB && + jobs[js.j_previous] && + STOPPED (js.j_previous)) + return; + + /* Second choice: Newest stopped job that is older than + the current job. */ + candidate = NO_JOB; + if (STOPPED (js.j_current)) + { + candidate = job_last_stopped (js.j_current); + + if (candidate != NO_JOB) + { + js.j_previous = candidate; + return; + } + } + + /* If we get here, there is either only one stopped job, in which case it is + the current job and the previous job should be set to the newest running + job, or there are only running jobs and the previous job should be set to + the newest running job older than the current job. We decide on which + alternative to use based on whether or not JOBSTATE(js.j_current) is + JSTOPPED. */ + + candidate = RUNNING (js.j_current) ? job_last_running (js.j_current) + : job_last_running (js.j_jobslots); + + if (candidate != NO_JOB) + { + js.j_previous = candidate; + return; + } + + /* There is only a single job, and it is both `+' and `-'. */ + js.j_previous = js.j_current; +} + +/* Make current_job be something useful, if it isn't already. */ + +/* Here's the deal: The newest non-running job should be `+', and the + next-newest non-running job should be `-'. If there is only a single + stopped job, the js.j_previous is the newest non-running job. If there + are only running jobs, the newest running job is `+' and the + next-newest running job is `-'. Must be called with SIGCHLD blocked. */ + +static void +reset_current () +{ + int candidate; + + if (js.j_jobslots && js.j_current != NO_JOB && jobs[js.j_current] && STOPPED (js.j_current)) + candidate = js.j_current; + else + { + candidate = NO_JOB; + + /* First choice: the previous job. */ + if (js.j_previous != NO_JOB && jobs[js.j_previous] && STOPPED (js.j_previous)) + candidate = js.j_previous; + + /* Second choice: the most recently stopped job. */ + if (candidate == NO_JOB) + candidate = job_last_stopped (js.j_jobslots); + + /* Third choice: the newest running job. */ + if (candidate == NO_JOB) + candidate = job_last_running (js.j_jobslots); + } + + /* If we found a job to use, then use it. Otherwise, there + are no jobs period. */ + if (candidate != NO_JOB) + set_current_job (candidate); + else + js.j_current = js.j_previous = NO_JOB; +} + +/* Set up the job structures so we know the job and its processes are + all running. */ +static void +set_job_running (job) + int job; +{ + register PROCESS *p; + + /* Each member of the pipeline is now running. */ + p = jobs[job]->pipe; + + do + { + if (WIFSTOPPED (p->status)) + p->running = PS_RUNNING; /* XXX - could be PS_STOPPED */ + p = p->next; + } + while (p != jobs[job]->pipe); + + /* This means that the job is running. */ + JOBSTATE (job) = JRUNNING; +} + +/* Start a job. FOREGROUND if non-zero says to do that. Otherwise, + start the job in the background. JOB is a zero-based index into + JOBS. Returns -1 if it is unable to start a job, and the return + status of the job otherwise. */ +int +start_job (job, foreground) + int job, foreground; +{ + register PROCESS *p; + int already_running; + sigset_t set, oset; + char *wd, *s; + static TTYSTRUCT save_stty; + + BLOCK_CHILD (set, oset); + + if ((subshell_environment & SUBSHELL_COMSUB) && (pipeline_pgrp == shell_pgrp)) + { + internal_error (_("%s: no current jobs"), this_command_name); + UNBLOCK_CHILD (oset); + return (-1); + } + + if (DEADJOB (job)) + { + internal_error (_("%s: job has terminated"), this_command_name); + UNBLOCK_CHILD (oset); + return (-1); + } + + already_running = RUNNING (job); + + if (foreground == 0 && already_running) + { + internal_error (_("%s: job %d already in background"), this_command_name, job + 1); + UNBLOCK_CHILD (oset); + return (0); /* XPG6/SUSv3 says this is not an error */ + } + + wd = current_working_directory (); + + /* You don't know about the state of this job. Do you? */ + jobs[job]->flags &= ~J_NOTIFIED; + + if (foreground) + { + set_current_job (job); + jobs[job]->flags |= J_FOREGROUND; + } + + /* Tell the outside world what we're doing. */ + p = jobs[job]->pipe; + + if (foreground == 0) + { + /* POSIX.2 says `bg' doesn't give any indication about current or + previous job. */ + if (posixly_correct == 0) + s = (job == js.j_current) ? "+ ": ((job == js.j_previous) ? "- " : " "); + else + s = " "; + printf ("[%d]%s", job + 1, s); + } + + do + { + printf ("%s%s", + p->command ? p->command : "", + p->next != jobs[job]->pipe? " | " : ""); + p = p->next; + } + while (p != jobs[job]->pipe); + + if (foreground == 0) + printf (" &"); + + if (strcmp (wd, jobs[job]->wd) != 0) + printf (" (wd: %s)", polite_directory_format (jobs[job]->wd)); + + printf ("\n"); + + /* Run the job. */ + if (already_running == 0) + set_job_running (job); + + /* Save the tty settings before we start the job in the foreground. */ + if (foreground) + { + get_tty_state (); + save_stty = shell_tty_info; + /* Give the terminal to this job. */ + if (IS_JOBCONTROL (job)) + give_terminal_to (jobs[job]->pgrp, 0); + } + else + jobs[job]->flags &= ~J_FOREGROUND; + + /* If the job is already running, then don't bother jump-starting it. */ + if (already_running == 0) + { + jobs[job]->flags |= J_NOTIFIED; + killpg (jobs[job]->pgrp, SIGCONT); + } + + if (foreground) + { + pid_t pid; + int st; + + pid = find_last_pid (job, 0); + UNBLOCK_CHILD (oset); + st = wait_for (pid, 0); + shell_tty_info = save_stty; + set_tty_state (); + return (st); + } + else + { + reset_current (); + UNBLOCK_CHILD (oset); + return (0); + } +} + +/* Give PID SIGNAL. This determines what job the pid belongs to (if any). + If PID does belong to a job, and the job is stopped, then CONTinue the + job after giving it SIGNAL. Returns -1 on failure. If GROUP is non-null, + then kill the process group associated with PID. */ +int +kill_pid (pid, sig, group) + pid_t pid; + int sig, group; +{ + register PROCESS *p; + int job, result, negative; + sigset_t set, oset; + + if (pid < -1) + { + pid = -pid; + group = negative = 1; + } + else + negative = 0; + + result = EXECUTION_SUCCESS; + if (group) + { + BLOCK_CHILD (set, oset); + p = find_pipeline (pid, 0, &job); + + if (job != NO_JOB) + { + jobs[job]->flags &= ~J_NOTIFIED; + + /* Kill process in backquotes or one started without job control? */ + + /* If we're passed a pid < -1, just call killpg and see what happens */ + if (negative && jobs[job]->pgrp == shell_pgrp) + result = killpg (pid, sig); + /* If we're killing using job control notification, for example, + without job control active, we have to do things ourselves. */ + else if (jobs[job]->pgrp == shell_pgrp) /* XXX - IS_JOBCONTROL(job) == 0? */ + { + p = jobs[job]->pipe; + do + { + if (PALIVE (p) == 0) + continue; /* avoid pid recycling problem */ + kill (p->pid, sig); + if (PEXITED (p) && (sig == SIGTERM || sig == SIGHUP)) + kill (p->pid, SIGCONT); + p = p->next; + } + while (p != jobs[job]->pipe); + } + else + { + result = killpg (jobs[job]->pgrp, sig); + if (p && STOPPED (job) && (sig == SIGTERM || sig == SIGHUP)) + killpg (jobs[job]->pgrp, SIGCONT); + /* If we're continuing a stopped job via kill rather than bg or + fg, emulate the `bg' behavior. */ + if (p && STOPPED (job) && (sig == SIGCONT)) + { + set_job_running (job); + jobs[job]->flags &= ~J_FOREGROUND; + jobs[job]->flags |= J_NOTIFIED; + } + } + } + else + result = killpg (pid, sig); + + UNBLOCK_CHILD (oset); + } + else + result = kill (pid, sig); + + return (result); +} + +/* sigchld_handler () flushes at least one of the children that we are + waiting for. It gets run when we have gotten a SIGCHLD signal. */ +static sighandler +sigchld_handler (sig) + int sig; +{ + int n, oerrno; + + oerrno = errno; + REINSTALL_SIGCHLD_HANDLER; + sigchld++; + n = 0; + if (queue_sigchld == 0) + n = waitchld (-1, 0); + errno = oerrno; + SIGRETURN (n); +} + +/* waitchld() reaps dead or stopped children. It's called by wait_for and + sigchld_handler, and runs until there aren't any children terminating any + more. + If BLOCK is 1, this is to be a blocking wait for a single child, although + an arriving SIGCHLD could cause the wait to be non-blocking. It returns + the number of children reaped, or -1 if there are no unwaited-for child + processes. */ +static int +waitchld (wpid, block) + pid_t wpid; + int block; +{ + WAIT status; + PROCESS *child; + pid_t pid; + int ind; + + int call_set_current, last_stopped_job, job, children_exited, waitpid_flags; + static int wcontinued = WCONTINUED; /* run-time fix for glibc problem */ + + call_set_current = children_exited = 0; + last_stopped_job = NO_JOB; + + do + { + /* We don't want to be notified about jobs stopping if job control + is not active. XXX - was interactive_shell instead of job_control */ + waitpid_flags = (job_control && subshell_environment == 0) + ? (WUNTRACED|wcontinued) + : 0; + if (sigchld || block == 0) + waitpid_flags |= WNOHANG; + + /* Check for terminating signals and exit the shell if we receive one */ + CHECK_TERMSIG; + /* Check for a trapped signal interrupting the wait builtin and jump out */ + CHECK_WAIT_INTR; + + if (block == 1 && queue_sigchld == 0 && (waitpid_flags & WNOHANG) == 0) + { + internal_warning (_("waitchld: turning on WNOHANG to avoid indefinite block")); + waitpid_flags |= WNOHANG; + } + + pid = WAITPID (-1, &status, waitpid_flags); + +#if 0 +if (wpid != -1 && block) + itrace("waitchld: blocking waitpid returns %d", pid); +#endif +#if 0 +if (wpid != -1) + itrace("waitchld: %s waitpid returns %d", block?"blocking":"non-blocking", pid); +#endif + /* WCONTINUED may be rejected by waitpid as invalid even when defined */ + if (wcontinued && pid < 0 && errno == EINVAL) + { + wcontinued = 0; + continue; /* jump back to the test and retry without WCONTINUED */ + } + + /* The check for WNOHANG is to make sure we decrement sigchld only + if it was non-zero before we called waitpid. */ + if (sigchld > 0 && (waitpid_flags & WNOHANG)) + sigchld--; + + /* If waitpid returns -1 with errno == ECHILD, there are no more + unwaited-for child processes of this shell. */ + if (pid < 0 && errno == ECHILD) + { + if (children_exited == 0) + return -1; + else + break; + } + +#if 0 +itrace("waitchld: waitpid returns %d block = %d children_exited = %d", pid, block, children_exited); +#endif + /* If waitpid returns 0, there are running children. If it returns -1, + the only other error POSIX says it can return is EINTR. */ + CHECK_TERMSIG; + CHECK_WAIT_INTR; + + /* If waitpid returns -1/EINTR and the shell saw a SIGINT, then we + assume the child has blocked or handled SIGINT. In that case, we + require the child to actually die due to SIGINT to act on the + SIGINT we received; otherwise we assume the child handled it and + let it go. */ + if (pid < 0 && errno == EINTR && wait_sigint_received) + child_caught_sigint = 1; + + if (pid <= 0) + continue; /* jumps right to the test */ + + /* Linux kernels appear to signal the parent but not interrupt the + waitpid() (or restart it even without SA_RESTART) on SIGINT, so if + we saw a SIGINT and the process exited or died due to some other + signal, assume the child caught the SIGINT. */ + if (wait_sigint_received && (WIFSIGNALED (status) == 0 || WTERMSIG (status) != SIGINT)) + child_caught_sigint = 1; + + /* If the child process did die due to SIGINT, forget our assumption + that it caught or otherwise handled it. */ + if (WIFSIGNALED (status) && WTERMSIG (status) == SIGINT) + child_caught_sigint = 0; + + /* children_exited is used to run traps on SIGCHLD. We don't want to + run the trap if a process is just being continued. */ + if (WIFCONTINUED(status) == 0) + { + children_exited++; + js.c_living--; + } + + /* Locate our PROCESS for this pid. */ + child = find_process (pid, 1, &job); /* want living procs only */ + +#if defined (COPROCESS_SUPPORT) + coproc_pidchk (pid, WSTATUS(status)); +#endif + +#if defined (PROCESS_SUBSTITUTION) + /* Only manipulate the list of process substitutions while SIGCHLD + is blocked. We only use this as a hint that we can remove FIFOs + or close file descriptors corresponding to terminated process + substitutions. */ + if ((ind = find_procsub_child (pid)) >= 0) + set_procsub_status (ind, pid, WSTATUS (status)); +#endif + + /* It is not an error to have a child terminate that we did + not have a record of. This child could have been part of + a pipeline in backquote substitution. Even so, I'm not + sure child is ever non-zero. */ + if (child == 0) + { + if (WIFEXITED (status) || WIFSIGNALED (status)) + js.c_reaped++; + continue; + } + + /* Remember status, and whether or not the process is running. */ + child->status = status; + child->running = WIFCONTINUED(status) ? PS_RUNNING : PS_DONE; + + if (PEXITED (child)) + { + js.c_totreaped++; + if (job != NO_JOB) + js.c_reaped++; + } + + if (job == NO_JOB) + continue; + + call_set_current += set_job_status_and_cleanup (job); + + if (STOPPED (job)) + last_stopped_job = job; + else if (DEADJOB (job) && last_stopped_job == job) + last_stopped_job = NO_JOB; + } + while ((sigchld || block == 0) && pid > (pid_t)0); + + /* If a job was running and became stopped, then set the current + job. Otherwise, don't change a thing. */ + if (call_set_current) + { + if (last_stopped_job != NO_JOB) + set_current_job (last_stopped_job); + else + reset_current (); + } + + /* Call a SIGCHLD trap handler for each child that exits, if one is set. */ + if (children_exited && + (signal_is_trapped (SIGCHLD) || trap_list[SIGCHLD] == (char *)IMPOSSIBLE_TRAP_HANDLER) && + trap_list[SIGCHLD] != (char *)IGNORE_SIG) + { + if (posixly_correct && this_shell_builtin && this_shell_builtin == wait_builtin) + { + /* This was trap_handler (SIGCHLD) but that can lose traps if + children_exited > 1 */ + queue_sigchld_trap (children_exited); + wait_signal_received = SIGCHLD; + /* If we're in a signal handler, let CHECK_WAIT_INTR pick it up; + run_pending_traps will call run_sigchld_trap later */ + if (sigchld == 0 && wait_intr_flag) + sh_longjmp (wait_intr_buf, 1); + } + /* If not in posix mode and not executing the wait builtin, queue the + signal for later handling. Run the trap immediately if we are + executing the wait builtin, but don't break out of `wait'. */ + else if (sigchld) /* called from signal handler */ + queue_sigchld_trap (children_exited); + else if (signal_in_progress (SIGCHLD)) + queue_sigchld_trap (children_exited); + else if (trap_list[SIGCHLD] == (char *)IMPOSSIBLE_TRAP_HANDLER) + queue_sigchld_trap (children_exited); + else if (running_trap) + queue_sigchld_trap (children_exited); + else if (this_shell_builtin == wait_builtin) + run_sigchld_trap (children_exited); /* XXX */ + else + queue_sigchld_trap (children_exited); + } + + /* We have successfully recorded the useful information about this process + that has just changed state. If we notify asynchronously, and the job + that this process belongs to is no longer running, then notify the user + of that fact now. */ + if (asynchronous_notification && interactive && executing_builtin == 0) + notify_of_job_status (); + + return (children_exited); +} + +/* Set the status of JOB and perform any necessary cleanup if the job is + marked as JDEAD. + + Currently, the cleanup activity is restricted to handling any SIGINT + received while waiting for a foreground job to finish. */ +static int +set_job_status_and_cleanup (job) + int job; +{ + PROCESS *child; + int tstatus, job_state, any_stopped, any_tstped, call_set_current; + SigHandler *temp_handler; + + child = jobs[job]->pipe; + jobs[job]->flags &= ~J_NOTIFIED; + + call_set_current = 0; + + /* + * COMPUTE JOB STATUS + */ + + /* If all children are not running, but any of them is stopped, then + the job is stopped, not dead. */ + job_state = any_stopped = any_tstped = 0; + do + { + job_state |= PRUNNING (child); +#if 0 + if (PEXITED (child) && (WIFSTOPPED (child->status))) +#else + /* Only checking for WIFSTOPPED now, not for PS_DONE */ + if (PSTOPPED (child)) +#endif + { + any_stopped = 1; + any_tstped |= job_control && (WSTOPSIG (child->status) == SIGTSTP); + } + child = child->next; + } + while (child != jobs[job]->pipe); + + /* If job_state != 0, the job is still running, so don't bother with + setting the process exit status and job state unless we're + transitioning from stopped to running. */ + if (job_state != 0 && JOBSTATE(job) != JSTOPPED) + return 0; + + /* + * SET JOB STATUS + */ + + /* The job is either stopped or dead. Set the state of the job accordingly. */ + if (any_stopped) + { + jobs[job]->state = JSTOPPED; + jobs[job]->flags &= ~J_FOREGROUND; + call_set_current++; + /* Suspending a job with SIGTSTP breaks all active loops. */ + if (any_tstped && loop_level) + breaking = loop_level; + } + else if (job_state != 0) /* was stopped, now running */ + { + jobs[job]->state = JRUNNING; + call_set_current++; + } + else + { + jobs[job]->state = JDEAD; + js.j_ndead++; + +#if 0 + if (IS_FOREGROUND (job)) + setjstatus (job); +#endif + + /* If this job has a cleanup function associated with it, call it + with `cleanarg' as the single argument, then set the function + pointer to NULL so it is not inadvertently called twice. The + cleanup function is responsible for deallocating cleanarg. */ + if (jobs[job]->j_cleanup) + { + (*jobs[job]->j_cleanup) (jobs[job]->cleanarg); + jobs[job]->j_cleanup = (sh_vptrfunc_t *)NULL; + } + } + + /* + * CLEANUP + * + * Currently, we just do special things if we got a SIGINT while waiting + * for a foreground job to complete + */ + + if (JOBSTATE (job) == JDEAD) + { + /* If we're running a shell script and we get a SIGINT with a + SIGINT trap handler, but the foreground job handles it and + does not exit due to SIGINT, run the trap handler but do not + otherwise act as if we got the interrupt. */ + if (wait_sigint_received && interactive_shell == 0 && + child_caught_sigint && IS_FOREGROUND (job) && + signal_is_trapped (SIGINT)) + { + int old_frozen; + wait_sigint_received = 0; + last_command_exit_value = process_exit_status (child->status); + + old_frozen = jobs_list_frozen; + jobs_list_frozen = 1; + tstatus = maybe_call_trap_handler (SIGINT); + jobs_list_frozen = old_frozen; + } + + /* If the foreground job is killed by SIGINT when job control is not + active, we need to perform some special handling. + + The check of wait_sigint_received is a way to determine if the + SIGINT came from the keyboard (in which case the shell has already + seen it, and wait_sigint_received is non-zero, because keyboard + signals are sent to process groups) or via kill(2) to the foreground + process by another process (or itself). If the shell did receive the + SIGINT, it needs to perform normal SIGINT processing. XXX - should + this change its behavior depending on whether the last command in an + pipeline exited due to SIGINT, or any process in the pipeline? Right + now it does this if any process in the pipeline exits due to SIGINT. */ + else if (wait_sigint_received && + child_caught_sigint == 0 && + IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0) + { + int old_frozen; + + wait_sigint_received = 0; + + /* If SIGINT is trapped, set the exit status so that the trap + handler can see it. */ + if (signal_is_trapped (SIGINT)) + last_command_exit_value = process_exit_status (child->status); + + /* If the signal is trapped, let the trap handler get it no matter + what and simply return if the trap handler returns. + maybe_call_trap_handler() may cause dead jobs to be removed from + the job table because of a call to execute_command. We work + around this by setting JOBS_LIST_FROZEN. */ + old_frozen = jobs_list_frozen; + jobs_list_frozen = 1; + tstatus = maybe_call_trap_handler (SIGINT); + jobs_list_frozen = old_frozen; + if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER) + { + /* wait_sigint_handler () has already seen SIGINT and + allowed the wait builtin to jump out. We need to + call the original SIGINT handler, if necessary. If + the original handler is SIG_DFL, we need to resend + the signal to ourselves. */ + + temp_handler = old_sigint_handler; + + /* Bogus. If we've reset the signal handler as the result + of a trap caught on SIGINT, then old_sigint_handler + will point to trap_handler, which now knows nothing about + SIGINT (if we reset the sighandler to the default). + In this case, we have to fix things up. What a crock. */ + if (temp_handler == trap_handler && signal_is_trapped (SIGINT) == 0) + temp_handler = trap_to_sighandler (SIGINT); + restore_sigint_handler (); + if (temp_handler == SIG_DFL) + termsig_handler (SIGINT); /* XXX */ + else if (temp_handler != SIG_IGN) + (*temp_handler) (SIGINT); + } + } + } + + return call_set_current; +} + +/* Build the array of values for the $PIPESTATUS variable from the set of + exit statuses of all processes in the job J. */ +static void +setjstatus (j) + int j; +{ +#if defined (ARRAY_VARS) + register int i; + register PROCESS *p; + + for (i = 1, p = jobs[j]->pipe; p->next != jobs[j]->pipe; p = p->next, i++) + ; + i++; + if (statsize < i) + { + pstatuses = (int *)xrealloc (pstatuses, i * sizeof (int)); + statsize = i; + } + i = 0; + p = jobs[j]->pipe; + do + { + pstatuses[i++] = process_exit_status (p->status); + p = p->next; + } + while (p != jobs[j]->pipe); + + pstatuses[i] = -1; /* sentinel */ + set_pipestatus_array (pstatuses, i); +#endif +} + +void +run_sigchld_trap (nchild) + int nchild; +{ + char *trap_command; + int i; + + /* Turn off the trap list during the call to parse_and_execute () + to avoid potentially infinite recursive calls. Preserve the + values of last_command_exit_value, last_made_pid, and the_pipeline + around the execution of the trap commands. */ + trap_command = savestring (trap_list[SIGCHLD]); + + begin_unwind_frame ("SIGCHLD trap"); + unwind_protect_int (last_command_exit_value); + unwind_protect_int (last_command_exit_signal); + unwind_protect_var (last_made_pid); + unwind_protect_int (jobs_list_frozen); + unwind_protect_pointer (the_pipeline); + unwind_protect_pointer (subst_assign_varlist); + unwind_protect_pointer (this_shell_builtin); + unwind_protect_pointer (temporary_env); + + /* We have to add the commands this way because they will be run + in reverse order of adding. We don't want maybe_set_sigchld_trap () + to reference freed memory. */ + add_unwind_protect (xfree, trap_command); + add_unwind_protect (maybe_set_sigchld_trap, trap_command); + + subst_assign_varlist = (WORD_LIST *)NULL; + the_pipeline = (PROCESS *)NULL; + temporary_env = 0; /* traps should not run with temporary env */ + + running_trap = SIGCHLD + 1; + + set_impossible_sigchld_trap (); + jobs_list_frozen = 1; + for (i = 0; i < nchild; i++) + { + parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST|SEVAL_RESETLINE); + } + + run_unwind_frame ("SIGCHLD trap"); + running_trap = 0; +} + +/* Function to call when you want to notify people of changes + in job status. This prints out all jobs which are pending + notification to stderr, and marks those printed as already + notified, thus making them candidates for cleanup. */ +static void +notify_of_job_status () +{ + register int job, termsig; + char *dir; + sigset_t set, oset; + WAIT s; + + if (jobs == 0 || js.j_jobslots == 0) + return; + + if (old_ttou != 0) + { + sigemptyset (&set); + sigaddset (&set, SIGCHLD); + sigaddset (&set, SIGTTOU); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + } + else + queue_sigchld++; + + /* XXX could use js.j_firstj here */ + for (job = 0, dir = (char *)NULL; job < js.j_jobslots; job++) + { + if (jobs[job] && IS_NOTIFIED (job) == 0) + { + s = raw_job_exit_status (job); + termsig = WTERMSIG (s); + + /* POSIX.2 says we have to hang onto the statuses of at most the + last CHILD_MAX background processes if the shell is running a + script. If the shell is running a script, either from a file + or standard input, don't print anything unless the job was + killed by a signal. */ + if (startup_state == 0 && WIFSIGNALED (s) == 0 && + ((DEADJOB (job) && IS_FOREGROUND (job) == 0) || STOPPED (job))) + continue; + + /* If job control is disabled, don't print the status messages. + Mark dead jobs as notified so that they get cleaned up. If + startup_state == 2 and subshell_environment has the + SUBSHELL_COMSUB bit turned on, we were started to run a command + substitution, so don't print anything. + Otherwise, if the shell is not interactive, POSIX says that `jobs' + is the only way to notify of job status. */ + if ((job_control == 0 && interactive_shell) || + (startup_state == 2 && (subshell_environment & SUBSHELL_COMSUB)) || + (startup_state == 2 && posixly_correct && (subshell_environment & SUBSHELL_COMSUB) == 0)) + { + /* POSIX.2 compatibility: if the shell is not interactive, + hang onto the job corresponding to the last asynchronous + pid until the user has been notified of its status or does + a `wait'. */ + if (DEADJOB (job) && (interactive_shell || (find_last_pid (job, 0) != last_asynchronous_pid))) + jobs[job]->flags |= J_NOTIFIED; + continue; + } + + /* Print info on jobs that are running in the background, + and on foreground jobs that were killed by anything + except SIGINT (and possibly SIGPIPE). */ + switch (JOBSTATE (job)) + { + case JDEAD: + if (interactive_shell == 0 && termsig && WIFSIGNALED (s) && + termsig != SIGINT && +#if defined (DONT_REPORT_SIGTERM) + termsig != SIGTERM && +#endif +#if defined (DONT_REPORT_SIGPIPE) + termsig != SIGPIPE && +#endif + signal_is_trapped (termsig) == 0) + { + /* Don't print `0' for a line number. */ + fprintf (stderr, _("%s: line %d: "), get_name_for_error (), (line_number == 0) ? 1 : line_number); + pretty_print_job (job, JLIST_NONINTERACTIVE, stderr); + } + else if (IS_FOREGROUND (job)) + { +#if !defined (DONT_REPORT_SIGPIPE) + if (termsig && WIFSIGNALED (s) && termsig != SIGINT) +#else + if (termsig && WIFSIGNALED (s) && termsig != SIGINT && termsig != SIGPIPE) +#endif + { + fprintf (stderr, "%s", j_strsignal (termsig)); + + if (WIFCORED (s)) + fprintf (stderr, _(" (core dumped)")); + + fprintf (stderr, "\n"); + } + } + else if (job_control) /* XXX job control test added */ + { + if (dir == 0) + dir = current_working_directory (); + pretty_print_job (job, JLIST_STANDARD, stderr); + if (dir && strcmp (dir, jobs[job]->wd) != 0) + fprintf (stderr, + _("(wd now: %s)\n"), polite_directory_format (dir)); + } + + jobs[job]->flags |= J_NOTIFIED; + break; + + case JSTOPPED: + fprintf (stderr, "\n"); + if (dir == 0) + dir = current_working_directory (); + pretty_print_job (job, JLIST_STANDARD, stderr); + if (dir && (strcmp (dir, jobs[job]->wd) != 0)) + fprintf (stderr, + _("(wd now: %s)\n"), polite_directory_format (dir)); + jobs[job]->flags |= J_NOTIFIED; + break; + + case JRUNNING: + case JMIXED: + break; + + default: + programming_error ("notify_of_job_status"); + } + } + } + if (old_ttou != 0) + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + else + queue_sigchld--; +} + +/* Initialize the job control mechanism, and set up the tty stuff. */ +int +initialize_job_control (force) + int force; +{ + pid_t t; + int t_errno, tty_sigs; + + t_errno = -1; + shell_pgrp = getpgid (0); + + if (shell_pgrp == -1) + { + sys_error (_("initialize_job_control: getpgrp failed")); + exit (1); + } + + /* We can only have job control if we are interactive unless we force it. */ + if (interactive == 0 && force == 0) + { + job_control = 0; + original_pgrp = NO_PID; + shell_tty = fileno (stderr); + terminal_pgrp = tcgetpgrp (shell_tty); /* for checking later */ + } + else + { + shell_tty = -1; + + /* If forced_interactive is set, we skip the normal check that stderr + is attached to a tty, so we need to check here. If it's not, we + need to see whether we have a controlling tty by opening /dev/tty, + since trying to use job control tty pgrp manipulations on a non-tty + is going to fail. */ + if (forced_interactive && isatty (fileno (stderr)) == 0) + shell_tty = open ("/dev/tty", O_RDWR|O_NONBLOCK); + + /* Get our controlling terminal. If job_control is set, or + interactive is set, then this is an interactive shell no + matter where fd 2 is directed. */ + if (shell_tty == -1) + shell_tty = dup (fileno (stderr)); /* fd 2 */ + + if (shell_tty != -1) + shell_tty = move_to_high_fd (shell_tty, 1, -1); + + /* Compensate for a bug in systems that compiled the BSD + rlogind with DEBUG defined, like NeXT and Alliant. */ + if (shell_pgrp == 0) + { + shell_pgrp = getpid (); + setpgid (0, shell_pgrp); + if (shell_tty != -1) + tcsetpgrp (shell_tty, shell_pgrp); + } + + tty_sigs = 0; + while ((terminal_pgrp = tcgetpgrp (shell_tty)) != -1) + { + if (shell_pgrp != terminal_pgrp) + { + SigHandler *ottin; + + CHECK_TERMSIG; + ottin = set_signal_handler (SIGTTIN, SIG_DFL); + kill (0, SIGTTIN); + set_signal_handler (SIGTTIN, ottin); + if (tty_sigs++ > 16) + { + sys_error (_("initialize_job_control: no job control in background")); + job_control = 0; + original_pgrp = terminal_pgrp; /* for eventual give_terminal_to */ + goto just_bail; + } + continue; + } + break; + } + + if (terminal_pgrp == -1) + t_errno = errno; + + /* Make sure that we are using the new line discipline. */ + if (set_new_line_discipline (shell_tty) < 0) + { + sys_error (_("initialize_job_control: line discipline")); + job_control = 0; + } + else + { + original_pgrp = shell_pgrp; + shell_pgrp = getpid (); + + if ((original_pgrp != shell_pgrp) && (setpgid (0, shell_pgrp) < 0)) + { + sys_error (_("initialize_job_control: setpgid")); + shell_pgrp = original_pgrp; + } + + job_control = 1; + + /* If (and only if) we just set our process group to our pid, + thereby becoming a process group leader, and the terminal + is not in the same process group as our (new) process group, + then set the terminal's process group to our (new) process + group. If that fails, set our process group back to what it + was originally (so we can still read from the terminal) and + turn off job control. */ + if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp) + { + if (give_terminal_to (shell_pgrp, 0) < 0) + { + t_errno = errno; + setpgid (0, original_pgrp); + shell_pgrp = original_pgrp; + errno = t_errno; + sys_error (_("cannot set terminal process group (%d)"), shell_pgrp); + job_control = 0; + } + } + + if (job_control && ((t = tcgetpgrp (shell_tty)) == -1 || t != shell_pgrp)) + { + if (t_errno != -1) + errno = t_errno; + sys_error (_("cannot set terminal process group (%d)"), t); + job_control = 0; + } + } + if (job_control == 0) + internal_error (_("no job control in this shell")); + } + +just_bail: + running_in_background = terminal_pgrp != shell_pgrp; + + if (shell_tty != fileno (stderr)) + SET_CLOSE_ON_EXEC (shell_tty); + + set_signal_handler (SIGCHLD, sigchld_handler); + + change_flag ('m', job_control ? '-' : '+'); + + if (interactive) + get_tty_state (); + + set_maxchild (0); + + return job_control; +} + +#ifdef DEBUG +void +debug_print_pgrps () +{ + itrace("original_pgrp = %ld shell_pgrp = %ld terminal_pgrp = %ld", + (long)original_pgrp, (long)shell_pgrp, (long)terminal_pgrp); + itrace("tcgetpgrp(%d) -> %ld, getpgid(0) -> %ld", + shell_tty, (long)tcgetpgrp (shell_tty), (long)getpgid(0)); + itrace("pipeline_pgrp -> %ld", (long)pipeline_pgrp); +} +#endif + +/* Set the line discipline to the best this system has to offer. + Return -1 if this is not possible. */ +static int +set_new_line_discipline (tty) + int tty; +{ +#if defined (NEW_TTY_DRIVER) + int ldisc; + + if (ioctl (tty, TIOCGETD, &ldisc) < 0) + return (-1); + + if (ldisc != NTTYDISC) + { + ldisc = NTTYDISC; + + if (ioctl (tty, TIOCSETD, &ldisc) < 0) + return (-1); + } + return (0); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) +# if defined (TERMIO_LDISC) && (NTTYDISC) + if (ioctl (tty, TCGETA, &shell_tty_info) < 0) + return (-1); + + if (shell_tty_info.c_line != NTTYDISC) + { + shell_tty_info.c_line = NTTYDISC; + if (ioctl (tty, TCSETAW, &shell_tty_info) < 0) + return (-1); + } +# endif /* TERMIO_LDISC && NTTYDISC */ + return (0); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) +# if defined (TERMIOS_LDISC) && defined (NTTYDISC) + if (tcgetattr (tty, &shell_tty_info) < 0) + return (-1); + + if (shell_tty_info.c_line != NTTYDISC) + { + shell_tty_info.c_line = NTTYDISC; + if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0) + return (-1); + } +# endif /* TERMIOS_LDISC && NTTYDISC */ + return (0); +#endif /* TERMIOS_TTY_DRIVER */ + +#if !defined (NEW_TTY_DRIVER) && !defined (TERMIO_TTY_DRIVER) && !defined (TERMIOS_TTY_DRIVER) + return (-1); +#endif +} + +/* Setup this shell to handle C-C, etc. */ +void +initialize_job_signals () +{ + if (interactive) + { + set_signal_handler (SIGINT, sigint_sighandler); + set_signal_handler (SIGTSTP, SIG_IGN); + set_signal_handler (SIGTTOU, SIG_IGN); + set_signal_handler (SIGTTIN, SIG_IGN); + } + else if (job_control) + { + old_tstp = set_signal_handler (SIGTSTP, sigstop_sighandler); + old_ttin = set_signal_handler (SIGTTIN, sigstop_sighandler); + old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler); + } + /* Leave disposition unmodified for non-interactive shells without job + control. */ +} + +/* Here we handle CONT signals. */ +static sighandler +sigcont_sighandler (sig) + int sig; +{ + initialize_job_signals (); + set_signal_handler (SIGCONT, old_cont); + kill (getpid (), SIGCONT); + + SIGRETURN (0); +} + +/* Here we handle stop signals while we are running not as a login shell. */ +static sighandler +sigstop_sighandler (sig) + int sig; +{ + set_signal_handler (SIGTSTP, old_tstp); + set_signal_handler (SIGTTOU, old_ttou); + set_signal_handler (SIGTTIN, old_ttin); + + old_cont = set_signal_handler (SIGCONT, sigcont_sighandler); + + give_terminal_to (shell_pgrp, 0); + + kill (getpid (), sig); + + SIGRETURN (0); +} + +/* Give the terminal to PGRP. */ +int +give_terminal_to (pgrp, force) + pid_t pgrp; + int force; +{ + sigset_t set, oset; + int r, e; + + r = 0; + if (job_control || force) + { + sigemptyset (&set); + sigaddset (&set, SIGTTOU); + sigaddset (&set, SIGTTIN); + sigaddset (&set, SIGTSTP); + sigaddset (&set, SIGCHLD); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + + if (tcsetpgrp (shell_tty, pgrp) < 0) + { + /* Maybe we should print an error message? */ +#if 0 + sys_error ("tcsetpgrp(%d) failed: pid %ld to pgrp %ld", + shell_tty, (long)getpid(), (long)pgrp); +#endif + r = -1; + e = errno; + } + else + terminal_pgrp = pgrp; + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + } + + if (r == -1) + errno = e; + + return r; +} + +/* Give terminal to NPGRP iff it's currently owned by OPGRP. FLAGS are the + flags to pass to give_terminal_to(). */ +static int +maybe_give_terminal_to (opgrp, npgrp, flags) + pid_t opgrp, npgrp; + int flags; +{ + int tpgrp; + + tpgrp = tcgetpgrp (shell_tty); + if (tpgrp < 0 && errno == ENOTTY) + return -1; + if (tpgrp == npgrp) + { + terminal_pgrp = npgrp; + return 0; + } + else if (tpgrp != opgrp) + { + internal_debug ("%d: maybe_give_terminal_to: terminal pgrp == %d shell pgrp = %d new pgrp = %d in_background = %d", (int)getpid(), tpgrp, opgrp, npgrp, running_in_background); + return -1; + } + else + return (give_terminal_to (npgrp, flags)); +} + +/* Clear out any jobs in the job array. This is intended to be used by + children of the shell, who should not have any job structures as baggage + when they start executing (forking subshells for parenthesized execution + and functions with pipes are the two that spring to mind). If RUNNING_ONLY + is nonzero, only running jobs are removed from the table. */ +void +delete_all_jobs (running_only) + int running_only; +{ + register int i; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + /* XXX - need to set j_lastj, j_firstj appropriately if running_only != 0. */ + if (js.j_jobslots) + { + js.j_current = js.j_previous = NO_JOB; + + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (i < js.j_firstj && jobs[i]) + INTERNAL_DEBUG (("delete_all_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj)); + if (i > js.j_lastj && jobs[i]) + INTERNAL_DEBUG (("delete_all_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj)); + + if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i)))) + /* We don't want to add any of these pids to bgpids. If running_only + is non-zero, we don't want to add running jobs to the list. + If we are interested in all jobs, not just running jobs, and + we are going to clear the bgpids list below (bgp_clear()), we + don't need to bother. */ + delete_job (i, DEL_WARNSTOPPED|DEL_NOBGPID); + } + if (running_only == 0) + { + free ((char *)jobs); + js.j_jobslots = 0; + js.j_firstj = js.j_lastj = js.j_njobs = 0; + } + } + + if (running_only == 0) + bgp_clear (); + + UNBLOCK_CHILD (oset); +} + +/* Mark all jobs in the job array so that they don't get a SIGHUP when the + shell gets one. If RUNNING_ONLY is nonzero, mark only running jobs. */ +void +nohup_all_jobs (running_only) + int running_only; +{ + register int i; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + if (js.j_jobslots) + { + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i)))) + nohup_job (i); + } + + UNBLOCK_CHILD (oset); +} + +int +count_all_jobs () +{ + int i, n; + sigset_t set, oset; + + /* This really counts all non-dead jobs. */ + BLOCK_CHILD (set, oset); + /* XXX could use js.j_firstj here */ + for (i = n = 0; i < js.j_jobslots; i++) + { + if (i < js.j_firstj && jobs[i]) + INTERNAL_DEBUG (("count_all_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj)); + if (i > js.j_lastj && jobs[i]) + INTERNAL_DEBUG (("count_all_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj)); + + if (jobs[i] && DEADJOB(i) == 0) + n++; + } + UNBLOCK_CHILD (oset); + return n; +} + +static void +mark_all_jobs_as_dead () +{ + register int i; + sigset_t set, oset; + + if (js.j_jobslots == 0) + return; + + BLOCK_CHILD (set, oset); + + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + if (jobs[i]) + { + jobs[i]->state = JDEAD; + js.j_ndead++; + } + + UNBLOCK_CHILD (oset); +} + +/* Mark all dead jobs as notified, so delete_job () cleans them out + of the job table properly. POSIX.2 says we need to save the + status of the last CHILD_MAX jobs, so we count the number of dead + jobs and mark only enough as notified to save CHILD_MAX statuses. */ +static void +mark_dead_jobs_as_notified (force) + int force; +{ + register int i, ndead, ndeadproc; + sigset_t set, oset; + + if (js.j_jobslots == 0) + return; + + BLOCK_CHILD (set, oset); + + /* If FORCE is non-zero, we don't have to keep CHILD_MAX statuses + around; just run through the array. */ + if (force) + { + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid))) + jobs[i]->flags |= J_NOTIFIED; + } + UNBLOCK_CHILD (oset); + return; + } + + /* Mark enough dead jobs as notified to keep CHILD_MAX processes left in the + array with the corresponding not marked as notified. This is a better + way to avoid pid aliasing and reuse problems than keeping the POSIX- + mandated CHILD_MAX jobs around. delete_job() takes care of keeping the + bgpids list regulated. */ + + /* Count the number of dead jobs */ + /* XXX could use js.j_firstj here */ + for (i = ndead = ndeadproc = 0; i < js.j_jobslots; i++) + { + if (i < js.j_firstj && jobs[i]) + INTERNAL_DEBUG (("mark_dead_jobs_as_notified: job %d non-null before js.j_firstj (%d)", i, js.j_firstj)); + if (i > js.j_lastj && jobs[i]) + INTERNAL_DEBUG (("mark_dead_jobs_as_notified: job %d non-null after js.j_lastj (%d)", i, js.j_lastj)); + + if (jobs[i] && DEADJOB (i)) + { + ndead++; + ndeadproc += processes_in_job (i); + } + } + +# if 0 + if (ndeadproc != js.c_reaped) + itrace("mark_dead_jobs_as_notified: ndeadproc (%d) != js.c_reaped (%d)", ndeadproc, js.c_reaped); +# endif + if (ndead != js.j_ndead) + INTERNAL_DEBUG (("mark_dead_jobs_as_notified: ndead (%d) != js.j_ndead (%d)", ndead, js.j_ndead)); + + if (js.c_childmax < 0) + set_maxchild (0); + + /* Don't do anything if the number of dead processes is less than CHILD_MAX + and we're not forcing a cleanup. */ + if (ndeadproc <= js.c_childmax) + { + UNBLOCK_CHILD (oset); + return; + } + +#if 0 +itrace("mark_dead_jobs_as_notified: child_max = %d ndead = %d ndeadproc = %d", js.c_childmax, ndead, ndeadproc); +#endif + + /* Mark enough dead jobs as notified that we keep CHILD_MAX jobs in + the list. This isn't exactly right yet; changes need to be made + to stop_pipeline so we don't mark the newer jobs after we've + created CHILD_MAX slots in the jobs array. This needs to be + integrated with a way to keep the jobs array from growing without + bound. Maybe we wrap back around to 0 after we reach some max + limit, and there are sufficient job slots free (keep track of total + size of jobs array (js.j_jobslots) and running count of number of jobs + in jobs array. Then keep a job index corresponding to the `oldest job' + and start this loop there, wrapping around as necessary. In effect, + we turn the list into a circular buffer. */ + /* XXX could use js.j_firstj here */ + for (i = 0; i < js.j_jobslots; i++) + { + if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid))) + { + if (i < js.j_firstj && jobs[i]) + INTERNAL_DEBUG (("mark_dead_jobs_as_notified: job %d non-null before js.j_firstj (%d)", i, js.j_firstj)); + if (i > js.j_lastj && jobs[i]) + INTERNAL_DEBUG (("mark_dead_jobs_as_notified: job %d non-null after js.j_lastj (%d)", i, js.j_lastj)); + + /* If marking this job as notified would drop us down below + child_max, don't mark it so we can keep at least child_max + statuses. XXX -- need to check what Posix actually says + about keeping statuses. */ + if ((ndeadproc -= processes_in_job (i)) <= js.c_childmax) + break; + jobs[i]->flags |= J_NOTIFIED; + } + } + + UNBLOCK_CHILD (oset); +} + +/* Here to allow other parts of the shell (like the trap stuff) to + freeze and unfreeze the jobs list. */ +int +freeze_jobs_list () +{ + int o; + + o = jobs_list_frozen; + jobs_list_frozen = 1; + return o; +} + +void +unfreeze_jobs_list () +{ + jobs_list_frozen = 0; +} + +void +set_jobs_list_frozen (s) + int s; +{ + jobs_list_frozen = s; +} + +/* Allow or disallow job control to take place. Returns the old value + of job_control. */ +int +set_job_control (arg) + int arg; +{ + int old; + + old = job_control; + job_control = arg; + + if (terminal_pgrp == NO_PID && shell_tty >= 0) + terminal_pgrp = tcgetpgrp (shell_tty); + + /* If we're turning on job control we're going to want to know the shell's + process group. */ + if (job_control != old && job_control) + shell_pgrp = getpgid (0); + + running_in_background = (terminal_pgrp != shell_pgrp); + +#if 0 + if (interactive_shell == 0 && running_in_background == 0 && job_control != old) + { + if (job_control) + initialize_job_signals (); + else + default_tty_job_signals (); + } +#endif + + /* If we're turning on job control, reset pipeline_pgrp so make_child will + put new child processes into the right pgrp */ + if (job_control != old && job_control) + pipeline_pgrp = 0; + + return (old); +} + +/* Turn off all traces of job control. This is run by children of the shell + which are going to do shellsy things, like wait (), etc. */ +void +without_job_control () +{ + stop_making_children (); + start_pipeline (); +#if defined (PGRP_PIPE) + sh_closepipe (pgrp_pipe); +#endif + delete_all_jobs (0); + set_job_control (0); +} + +/* If this shell is interactive, terminate all stopped jobs and + restore the original terminal process group. This is done + before the `exec' builtin calls shell_execve. */ +void +end_job_control () +{ + if (job_control) + terminate_stopped_jobs (); + + if (original_pgrp >= 0 && terminal_pgrp != original_pgrp) + give_terminal_to (original_pgrp, 1); + + if (original_pgrp >= 0 && setpgid (0, original_pgrp) == 0) + shell_pgrp = original_pgrp; +} + +/* Restart job control by closing shell tty and reinitializing. This is + called after an exec fails in an interactive shell and we do not exit. */ +void +restart_job_control () +{ + if (shell_tty != -1) + close (shell_tty); + initialize_job_control (0); +} + +/* Set the maximum number of background children we keep track of to NCHILD. + If the caller passes NCHILD as 0 or -1, this ends up setting it to + LMAXCHILD, which is initialized the first time through. */ +void +set_maxchild (nchild) + int nchild; +{ + static int lmaxchild = -1; + + /* Initialize once. */ + if (lmaxchild < 0) + { + errno = 0; + lmaxchild = getmaxchild (); + if (lmaxchild < 0 && errno == 0) + lmaxchild = MAX_CHILD_MAX; /* assume unlimited */ + } + if (lmaxchild < 0) + lmaxchild = DEFAULT_CHILD_MAX; + + /* Clamp value we set. Minimum is what Posix requires, maximum is defined + above as MAX_CHILD_MAX. */ + if (nchild < lmaxchild) + nchild = lmaxchild; + else if (nchild > MAX_CHILD_MAX) + nchild = MAX_CHILD_MAX; + + js.c_childmax = nchild; +} + +/* Set the handler to run when the shell receives a SIGCHLD signal. */ +void +set_sigchld_handler () +{ + set_signal_handler (SIGCHLD, sigchld_handler); +} + +#if defined (PGRP_PIPE) +/* Read from the read end of a pipe. This is how the process group leader + blocks until all of the processes in a pipeline have been made. */ +static void +pipe_read (pp) + int *pp; +{ + char ch; + + if (pp[1] >= 0) + { + close (pp[1]); + pp[1] = -1; + } + + if (pp[0] >= 0) + { + while (read (pp[0], &ch, 1) == -1 && errno == EINTR) + ; + } +} + +/* Functional interface closes our local-to-job-control pipes. */ +void +close_pgrp_pipe () +{ + sh_closepipe (pgrp_pipe); +} + +void +save_pgrp_pipe (p, clear) + int *p; + int clear; +{ + p[0] = pgrp_pipe[0]; + p[1] = pgrp_pipe[1]; + if (clear) + pgrp_pipe[0] = pgrp_pipe[1] = -1; +} + +void +restore_pgrp_pipe (p) + int *p; +{ + pgrp_pipe[0] = p[0]; + pgrp_pipe[1] = p[1]; +} + +#endif /* PGRP_PIPE */ diff --git a/third_party/bash/jobs.h b/third_party/bash/jobs.h new file mode 100644 index 000000000..276204f0c --- /dev/null +++ b/third_party/bash/jobs.h @@ -0,0 +1,325 @@ +/* jobs.h -- structures and definitions used by the jobs.c file. */ + +/* Copyright (C) 1993-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_JOBS_H_) +# define _JOBS_H_ + +#include "quit.h" +#include "siglist.h" + +#include "stdc.h" + +#include "posixwait.h" + +/* Defines controlling the fashion in which jobs are listed. */ +#define JLIST_STANDARD 0 +#define JLIST_LONG 1 +#define JLIST_PID_ONLY 2 +#define JLIST_CHANGED_ONLY 3 +#define JLIST_NONINTERACTIVE 4 + +/* I looked it up. For pretty_print_job (). The real answer is 24. */ +#define LONGEST_SIGNAL_DESC 24 + +/* Defines for the wait_for_* functions and for the wait builtin to use */ +#define JWAIT_PERROR (1 << 0) +#define JWAIT_FORCE (1 << 1) +#define JWAIT_NOWAIT (1 << 2) /* don't waitpid(), just return status if already exited */ +#define JWAIT_WAITING (1 << 3) /* wait for jobs marked J_WAITING only */ + +/* flags for wait_for */ +#define JWAIT_NOTERM (1 << 8) /* wait_for doesn't give terminal away */ + +/* The max time to sleep while retrying fork() on EAGAIN failure */ +#define FORKSLEEP_MAX 16 + +/* We keep an array of jobs. Each entry in the array is a linked list + of processes that are piped together. The first process encountered is + the group leader. */ + +/* Values for the `running' field of a struct process. */ +#define PS_DONE 0 +#define PS_RUNNING 1 +#define PS_STOPPED 2 +#define PS_RECYCLED 4 + +/* Each child of the shell is remembered in a STRUCT PROCESS. A circular + chain of such structures is a pipeline. */ +typedef struct process { + struct process *next; /* Next process in the pipeline. A circular chain. */ + pid_t pid; /* Process ID. */ + WAIT status; /* The status of this command as returned by wait. */ + int running; /* Non-zero if this process is running. */ + char *command; /* The particular program that is running. */ +} PROCESS; + +struct pipeline_saver { + struct process *pipeline; + struct pipeline_saver *next; +}; + +/* PALIVE really means `not exited' */ +#define PSTOPPED(p) (WIFSTOPPED((p)->status)) +#define PRUNNING(p) ((p)->running == PS_RUNNING) +#define PALIVE(p) (PRUNNING(p) || PSTOPPED(p)) + +#define PEXITED(p) ((p)->running == PS_DONE) +#if defined (RECYCLES_PIDS) +# define PRECYCLED(p) ((p)->running == PS_RECYCLED) +#else +# define PRECYCLED(p) (0) +#endif +#define PDEADPROC(p) (PEXITED(p) || PRECYCLED(p)) + +#define get_job_by_jid(ind) (jobs[(ind)]) + +/* A description of a pipeline's state. */ +typedef enum { JNONE = -1, JRUNNING = 1, JSTOPPED = 2, JDEAD = 4, JMIXED = 8 } JOB_STATE; +#define JOBSTATE(job) (jobs[(job)]->state) +#define J_JOBSTATE(j) ((j)->state) + +#define STOPPED(j) (jobs[(j)]->state == JSTOPPED) +#define RUNNING(j) (jobs[(j)]->state == JRUNNING) +#define DEADJOB(j) (jobs[(j)]->state == JDEAD) + +#define INVALID_JOB(j) ((j) < 0 || (j) >= js.j_jobslots || get_job_by_jid(j) == 0) + +/* Values for the FLAGS field in the JOB struct below. */ +#define J_FOREGROUND 0x01 /* Non-zero if this is running in the foreground. */ +#define J_NOTIFIED 0x02 /* Non-zero if already notified about job state. */ +#define J_JOBCONTROL 0x04 /* Non-zero if this job started under job control. */ +#define J_NOHUP 0x08 /* Don't send SIGHUP to job if shell gets SIGHUP. */ +#define J_STATSAVED 0x10 /* A process in this job had status saved via $! */ +#define J_ASYNC 0x20 /* Job was started asynchronously */ +#define J_PIPEFAIL 0x40 /* pipefail set when job was started */ +#define J_WAITING 0x80 /* one of a list of jobs for which we are waiting */ + +#define IS_FOREGROUND(j) ((jobs[j]->flags & J_FOREGROUND) != 0) +#define IS_NOTIFIED(j) ((jobs[j]->flags & J_NOTIFIED) != 0) +#define IS_JOBCONTROL(j) ((jobs[j]->flags & J_JOBCONTROL) != 0) +#define IS_ASYNC(j) ((jobs[j]->flags & J_ASYNC) != 0) +#define IS_WAITING(j) ((jobs[j]->flags & J_WAITING) != 0) + +typedef struct job { + char *wd; /* The working directory at time of invocation. */ + PROCESS *pipe; /* The pipeline of processes that make up this job. */ + pid_t pgrp; /* The process ID of the process group (necessary). */ + JOB_STATE state; /* The state that this job is in. */ + int flags; /* Flags word: J_NOTIFIED, J_FOREGROUND, or J_JOBCONTROL. */ +#if defined (JOB_CONTROL) + COMMAND *deferred; /* Commands that will execute when this job is done. */ + sh_vptrfunc_t *j_cleanup; /* Cleanup function to call when job marked JDEAD */ + PTR_T cleanarg; /* Argument passed to (*j_cleanup)() */ +#endif /* JOB_CONTROL */ +} JOB; + +struct jobstats { + /* limits */ + long c_childmax; + /* child process statistics */ + int c_living; /* running or stopped child processes */ + int c_reaped; /* exited child processes still in jobs list */ + int c_injobs; /* total number of child processes in jobs list */ + /* child process totals */ + int c_totforked; /* total number of children this shell has forked */ + int c_totreaped; /* total number of children this shell has reaped */ + /* job counters and indices */ + int j_jobslots; /* total size of jobs array */ + int j_lastj; /* last (newest) job allocated */ + int j_firstj; /* first (oldest) job allocated */ + int j_njobs; /* number of non-NULL jobs in jobs array */ + int j_ndead; /* number of JDEAD jobs in jobs array */ + /* */ + int j_current; /* current job */ + int j_previous; /* previous job */ + /* */ + JOB *j_lastmade; /* last job allocated by stop_pipeline */ + JOB *j_lastasync; /* last async job allocated by stop_pipeline */ +}; + +/* Revised to accommodate new hash table bgpids implementation. */ +typedef pid_t ps_index_t; + +struct pidstat { + ps_index_t bucket_next; + ps_index_t bucket_prev; + + pid_t pid; + bits16_t status; /* only 8 bits really needed */ +}; + +struct bgpids { + struct pidstat *storage; /* storage arena */ + + ps_index_t head; + ps_index_t nalloc; + + int npid; +}; + +#define NO_PIDSTAT (ps_index_t)-1 + +/* standalone process status struct, without bgpids indexes */ +struct procstat { + pid_t pid; + bits16_t status; +}; + +/* A standalone singly-linked list of PROCESS *, used in various places + including keeping track of process substitutions. */ +struct procchain { + PROCESS *head; + PROCESS *end; + int nproc; +}; + +#define NO_JOB -1 /* An impossible job array index. */ +#define DUP_JOB -2 /* A possible return value for get_job_spec (). */ +#define BAD_JOBSPEC -3 /* Bad syntax for job spec. */ + +/* A value which cannot be a process ID. */ +#define NO_PID (pid_t)-1 + +#define ANY_PID (pid_t)-1 + +/* flags for make_child () */ +#define FORK_SYNC 0 /* normal synchronous process */ +#define FORK_ASYNC 1 /* background process */ +#define FORK_NOJOB 2 /* don't put process in separate pgrp */ +#define FORK_NOTERM 4 /* don't give terminal to any pgrp */ + +/* System calls. */ +#if !defined (HAVE_UNISTD_H) +extern pid_t fork (), getpid (), getpgrp (); +#endif /* !HAVE_UNISTD_H */ + +/* Stuff from the jobs.c file. */ +extern struct jobstats js; + +extern pid_t original_pgrp, shell_pgrp, pipeline_pgrp; +extern volatile pid_t last_made_pid, last_asynchronous_pid; +extern int asynchronous_notification; + +extern int already_making_children; +extern int running_in_background; + +extern PROCESS *last_procsub_child; + +extern JOB **jobs; + +extern void making_children PARAMS((void)); +extern void stop_making_children PARAMS((void)); +extern void cleanup_the_pipeline PARAMS((void)); +extern void discard_last_procsub_child PARAMS((void)); +extern void save_pipeline PARAMS((int)); +extern PROCESS *restore_pipeline PARAMS((int)); +extern void start_pipeline PARAMS((void)); +extern int stop_pipeline PARAMS((int, COMMAND *)); +extern int discard_pipeline PARAMS((PROCESS *)); +extern void append_process PARAMS((char *, pid_t, int, int)); + +extern void save_proc_status PARAMS((pid_t, int)); + +extern PROCESS *procsub_add PARAMS((PROCESS *)); +extern PROCESS *procsub_search PARAMS((pid_t)); +extern PROCESS *procsub_delete PARAMS((pid_t)); +extern int procsub_waitpid PARAMS((pid_t)); +extern void procsub_waitall PARAMS((void)); +extern void procsub_clear PARAMS((void)); +extern void procsub_prune PARAMS((void)); + +extern void delete_job PARAMS((int, int)); +extern void nohup_job PARAMS((int)); +extern void delete_all_jobs PARAMS((int)); +extern void nohup_all_jobs PARAMS((int)); + +extern int count_all_jobs PARAMS((void)); + +extern void terminate_current_pipeline PARAMS((void)); +extern void terminate_stopped_jobs PARAMS((void)); +extern void hangup_all_jobs PARAMS((void)); +extern void kill_current_pipeline PARAMS((void)); + +#if defined (__STDC__) && defined (pid_t) +extern int get_job_by_pid PARAMS((int, int, PROCESS **)); +extern void describe_pid PARAMS((int)); +#else +extern int get_job_by_pid PARAMS((pid_t, int, PROCESS **)); +extern void describe_pid PARAMS((pid_t)); +#endif + +extern void list_one_job PARAMS((JOB *, int, int, int)); +extern void list_all_jobs PARAMS((int)); +extern void list_stopped_jobs PARAMS((int)); +extern void list_running_jobs PARAMS((int)); + +extern pid_t make_child PARAMS((char *, int)); + +extern int get_tty_state PARAMS((void)); +extern int set_tty_state PARAMS((void)); + +extern int job_exit_status PARAMS((int)); +extern int job_exit_signal PARAMS((int)); + +extern int wait_for_single_pid PARAMS((pid_t, int)); +extern int wait_for_background_pids PARAMS((struct procstat *)); +extern int wait_for PARAMS((pid_t, int)); +extern int wait_for_job PARAMS((int, int, struct procstat *)); +extern int wait_for_any_job PARAMS((int, struct procstat *)); + +extern void wait_sigint_cleanup PARAMS((void)); + +extern void notify_and_cleanup PARAMS((void)); +extern void reap_dead_jobs PARAMS((void)); +extern int start_job PARAMS((int, int)); +extern int kill_pid PARAMS((pid_t, int, int)); +extern int initialize_job_control PARAMS((int)); +extern void initialize_job_signals PARAMS((void)); +extern int give_terminal_to PARAMS((pid_t, int)); + +extern void run_sigchld_trap PARAMS((int)); + +extern int freeze_jobs_list PARAMS((void)); +extern void unfreeze_jobs_list PARAMS((void)); +extern void set_jobs_list_frozen PARAMS((int)); +extern int set_job_control PARAMS((int)); +extern void without_job_control PARAMS((void)); +extern void end_job_control PARAMS((void)); +extern void restart_job_control PARAMS((void)); +extern void set_sigchld_handler PARAMS((void)); +extern void ignore_tty_job_signals PARAMS((void)); +extern void default_tty_job_signals PARAMS((void)); +extern void get_original_tty_job_signals PARAMS((void)); + +extern void init_job_stats PARAMS((void)); + +extern void close_pgrp_pipe PARAMS((void)); +extern void save_pgrp_pipe PARAMS((int *, int)); +extern void restore_pgrp_pipe PARAMS((int *)); + +extern void set_maxchild PARAMS((int)); + +#ifdef DEBUG +extern void debug_print_pgrps (void); +#endif + +extern int job_control; /* set to 0 in nojobs.c */ + +#endif /* _JOBS_H_ */ diff --git a/third_party/bash/list.c b/third_party/bash/list.c new file mode 100644 index 000000000..88835f58e --- /dev/null +++ b/third_party/bash/list.c @@ -0,0 +1,136 @@ +/* list.c - Functions for manipulating linked lists of objects. */ + +/* Copyright (C) 1996-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "shell.h" + +/* A global variable which acts as a sentinel for an `error' list return. */ +GENERIC_LIST global_error_list; + +#ifdef INCLUDE_UNUSED +/* Call FUNCTION on every member of LIST, a generic list. */ +void +list_walk (list, function) + GENERIC_LIST *list; + sh_glist_func_t *function; +{ + for ( ; list; list = list->next) + if ((*function) (list) < 0) + return; +} + +/* Call FUNCTION on every string in WORDS. */ +void +wlist_walk (words, function) + WORD_LIST *words; + sh_icpfunc_t *function; +{ + for ( ; words; words = words->next) + if ((*function) (words->word->word) < 0) + return; +} +#endif /* INCLUDE_UNUSED */ + +/* Reverse the chain of structures in LIST. Output the new head + of the chain. You should always assign the output value of this + function to something, or you will lose the chain. */ +GENERIC_LIST * +list_reverse (list) + GENERIC_LIST *list; +{ + register GENERIC_LIST *next, *prev; + + for (prev = (GENERIC_LIST *)NULL; list; ) + { + next = list->next; + list->next = prev; + prev = list; + list = next; + } + return (prev); +} + +/* Return the number of elements in LIST, a generic list. */ +int +list_length (list) + GENERIC_LIST *list; +{ + register int i; + + for (i = 0; list; list = list->next, i++); + return (i); +} + +/* Append TAIL to HEAD. Return the header of the list. */ +GENERIC_LIST * +list_append (head, tail) + GENERIC_LIST *head, *tail; +{ + register GENERIC_LIST *t_head; + + if (head == 0) + return (tail); + + for (t_head = head; t_head->next; t_head = t_head->next) + ; + t_head->next = tail; + return (head); +} + +#ifdef INCLUDE_UNUSED +/* Delete the element of LIST which satisfies the predicate function COMPARER. + Returns the element that was deleted, so you can dispose of it, or -1 if + the element wasn't found. COMPARER is called with the list element and + then ARG. Note that LIST contains the address of a variable which points + to the list. You might call this function like this: + + SHELL_VAR *elt = list_remove (&variable_list, check_var_has_name, "foo"); + dispose_variable (elt); +*/ +GENERIC_LIST * +list_remove (list, comparer, arg) + GENERIC_LIST **list; + Function *comparer; + char *arg; +{ + register GENERIC_LIST *prev, *temp; + + for (prev = (GENERIC_LIST *)NULL, temp = *list; temp; prev = temp, temp = temp->next) + { + if ((*comparer) (temp, arg)) + { + if (prev) + prev->next = temp->next; + else + *list = temp->next; + return (temp); + } + } + return ((GENERIC_LIST *)&global_error_list); +} +#endif diff --git a/third_party/bash/locale.c b/third_party/bash/locale.c new file mode 100644 index 000000000..fabf7b125 --- /dev/null +++ b/third_party/bash/locale.c @@ -0,0 +1,645 @@ +/* locale.c - Miscellaneous internationalization functions. */ + +/* Copyright (C) 1996-2009,2012,2016-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if HAVE_LANGINFO_CODESET +# include +#endif + +#include "bashintl.h" +#include "bashansi.h" +#include +#include "chartypes.h" +#include + +#include "shell.h" +#include "input.h" /* For bash_input */ + +#ifndef errno +extern int errno; +#endif + +int locale_utf8locale; +int locale_mb_cur_max; /* value of MB_CUR_MAX for current locale (LC_CTYPE) */ +int locale_shiftstates = 0; + +int singlequote_translations = 0; /* single-quote output of $"..." */ + +extern int dump_translatable_strings, dump_po_strings; + +/* The current locale when the program begins */ +static char *default_locale; + +/* The current domain for textdomain(3). */ +static char *default_domain; +static char *default_dir; + +/* tracks the value of LC_ALL; used to override values for other locale + categories */ +static char *lc_all; + +/* tracks the value of LC_ALL; used to provide defaults for locale + categories */ +static char *lang; + +/* Called to reset all of the locale variables to their appropriate values + if (and only if) LC_ALL has not been assigned a value. */ +static int reset_locale_vars PARAMS((void)); + +static void locale_setblanks PARAMS((void)); +static int locale_isutf8 PARAMS((char *)); + +/* Set the value of default_locale and make the current locale the + system default locale. This should be called very early in main(). */ +void +set_default_locale () +{ +#if defined (HAVE_SETLOCALE) + default_locale = setlocale (LC_ALL, ""); + if (default_locale) + default_locale = savestring (default_locale); +#else + default_locale = savestring ("C"); +#endif /* HAVE_SETLOCALE */ + bindtextdomain (PACKAGE, LOCALEDIR); + textdomain (PACKAGE); + + locale_mb_cur_max = MB_CUR_MAX; + locale_utf8locale = locale_isutf8 (default_locale); +#if defined (HANDLE_MULTIBYTE) + locale_shiftstates = mblen ((char *)NULL, 0); +#else + locale_shiftstates = 0; +#endif +} + +/* Set default values for LC_CTYPE, LC_COLLATE, LC_MESSAGES, LC_NUMERIC and + LC_TIME if they are not specified in the environment, but LC_ALL is. This + should be called from main() after parsing the environment. */ +void +set_default_locale_vars () +{ + char *val; + +#if defined (HAVE_SETLOCALE) + +# if defined (LC_CTYPE) + val = get_string_value ("LC_CTYPE"); + if (val == 0 && lc_all && *lc_all) + { + setlocale (LC_CTYPE, lc_all); + locale_setblanks (); + locale_mb_cur_max = MB_CUR_MAX; + locale_utf8locale = locale_isutf8 (lc_all); + +# if defined (HANDLE_MULTIBYTE) + locale_shiftstates = mblen ((char *)NULL, 0); +# else + locale_shiftstates = 0; +# endif + + u32reset (); + } +# endif + +# if defined (LC_COLLATE) + val = get_string_value ("LC_COLLATE"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_COLLATE, lc_all); +# endif /* LC_COLLATE */ + +# if defined (LC_MESSAGES) + val = get_string_value ("LC_MESSAGES"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_MESSAGES, lc_all); +# endif /* LC_MESSAGES */ + +# if defined (LC_NUMERIC) + val = get_string_value ("LC_NUMERIC"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_NUMERIC, lc_all); +# endif /* LC_NUMERIC */ + +# if defined (LC_TIME) + val = get_string_value ("LC_TIME"); + if (val == 0 && lc_all && *lc_all) + setlocale (LC_TIME, lc_all); +# endif /* LC_TIME */ + +#endif /* HAVE_SETLOCALE */ + + val = get_string_value ("TEXTDOMAIN"); + if (val && *val) + { + FREE (default_domain); + default_domain = savestring (val); + if (default_dir && *default_dir) + bindtextdomain (default_domain, default_dir); + } + + val = get_string_value ("TEXTDOMAINDIR"); + if (val && *val) + { + FREE (default_dir); + default_dir = savestring (val); + if (default_domain && *default_domain) + bindtextdomain (default_domain, default_dir); + } +} + +/* Set one of the locale categories (specified by VAR) to VALUE. Returns 1 + if successful, 0 otherwise. */ +int +set_locale_var (var, value) + char *var, *value; +{ + int r; + char *x; + + x = ""; + errno = 0; + if (var[0] == 'T' && var[10] == 0) /* TEXTDOMAIN */ + { + FREE (default_domain); + default_domain = value ? savestring (value) : (char *)NULL; + if (default_dir && *default_dir) + bindtextdomain (default_domain, default_dir); + return (1); + } + else if (var[0] == 'T') /* TEXTDOMAINDIR */ + { + FREE (default_dir); + default_dir = value ? savestring (value) : (char *)NULL; + if (default_domain && *default_domain) + bindtextdomain (default_domain, default_dir); + return (1); + } + + /* var[0] == 'L' && var[1] == 'C' && var[2] == '_' */ + + else if (var[3] == 'A') /* LC_ALL */ + { + FREE (lc_all); + if (value) + lc_all = savestring (value); + else + { + lc_all = (char *)xmalloc (1); + lc_all[0] = '\0'; + } +#if defined (HAVE_SETLOCALE) + r = *lc_all ? ((x = setlocale (LC_ALL, lc_all)) != 0) : reset_locale_vars (); + if (x == 0) + { + if (errno == 0) + internal_warning(_("setlocale: LC_ALL: cannot change locale (%s)"), lc_all); + else + internal_warning(_("setlocale: LC_ALL: cannot change locale (%s): %s"), lc_all, strerror (errno)); + } + locale_setblanks (); + locale_mb_cur_max = MB_CUR_MAX; + /* if LC_ALL == "", reset_locale_vars has already called this */ + if (*lc_all && x) + locale_utf8locale = locale_isutf8 (lc_all); +# if defined (HANDLE_MULTIBYTE) + locale_shiftstates = mblen ((char *)NULL, 0); +# else + locale_shiftstates = 0; +# endif + u32reset (); + return r; +#else + return (1); +#endif + } + +#if defined (HAVE_SETLOCALE) + else if (var[3] == 'C' && var[4] == 'T') /* LC_CTYPE */ + { +# if defined (LC_CTYPE) + if (lc_all == 0 || *lc_all == '\0') + { + x = setlocale (LC_CTYPE, get_locale_var ("LC_CTYPE")); + locale_setblanks (); + locale_mb_cur_max = MB_CUR_MAX; + /* if setlocale() returns NULL, the locale is not changed */ + if (x) + locale_utf8locale = locale_isutf8 (x); +#if defined (HANDLE_MULTIBYTE) + locale_shiftstates = mblen ((char *)NULL, 0); +#else + locale_shiftstates = 0; +#endif + u32reset (); + } +# endif + } + else if (var[3] == 'C' && var[4] == 'O') /* LC_COLLATE */ + { +# if defined (LC_COLLATE) + if (lc_all == 0 || *lc_all == '\0') + x = setlocale (LC_COLLATE, get_locale_var ("LC_COLLATE")); +# endif /* LC_COLLATE */ + } + else if (var[3] == 'M' && var[4] == 'E') /* LC_MESSAGES */ + { +# if defined (LC_MESSAGES) + if (lc_all == 0 || *lc_all == '\0') + x = setlocale (LC_MESSAGES, get_locale_var ("LC_MESSAGES")); +# endif /* LC_MESSAGES */ + } + else if (var[3] == 'N' && var[4] == 'U') /* LC_NUMERIC */ + { +# if defined (LC_NUMERIC) + if (lc_all == 0 || *lc_all == '\0') + x = setlocale (LC_NUMERIC, get_locale_var ("LC_NUMERIC")); +# endif /* LC_NUMERIC */ + } + else if (var[3] == 'T' && var[4] == 'I') /* LC_TIME */ + { +# if defined (LC_TIME) + if (lc_all == 0 || *lc_all == '\0') + x = setlocale (LC_TIME, get_locale_var ("LC_TIME")); +# endif /* LC_TIME */ + } +#endif /* HAVE_SETLOCALE */ + + if (x == 0) + { + if (errno == 0) + internal_warning(_("setlocale: %s: cannot change locale (%s)"), var, get_locale_var (var)); + else + internal_warning(_("setlocale: %s: cannot change locale (%s): %s"), var, get_locale_var (var), strerror (errno)); + } + + return (x != 0); +} + +/* Called when LANG is assigned a value. Tracks value in `lang'. Calls + reset_locale_vars() to reset any default values if LC_ALL is unset or + null. */ +int +set_lang (var, value) + char *var, *value; +{ + FREE (lang); + if (value) + lang = savestring (value); + else + { + lang = (char *)xmalloc (1); + lang[0] = '\0'; + } + + return ((lc_all == 0 || *lc_all == 0) ? reset_locale_vars () : 0); +} + +/* Set default values for LANG and LC_ALL. Default values for all other + locale-related variables depend on these. */ +void +set_default_lang () +{ + char *v; + + v = get_string_value ("LC_ALL"); + set_locale_var ("LC_ALL", v); + + v = get_string_value ("LANG"); + set_lang ("LANG", v); +} + +/* Get the value of one of the locale variables (LC_MESSAGES, LC_CTYPE). + The precedence is as POSIX.2 specifies: LC_ALL has precedence over + the specific locale variables, and LANG, if set, is used as the default. */ +char * +get_locale_var (var) + char *var; +{ + char *locale; + + locale = lc_all; + + if (locale == 0 || *locale == 0) + locale = get_string_value (var); /* XXX - no mem leak */ + if (locale == 0 || *locale == 0) + locale = lang; + if (locale == 0 || *locale == 0) +#if 0 + locale = default_locale; /* system-dependent; not really portable. should it be "C"? */ +#else + locale = ""; +#endif + return (locale); +} + +/* Called to reset all of the locale variables to their appropriate values + if (and only if) LC_ALL has not been assigned a value. DO NOT CALL THIS + IF LC_ALL HAS BEEN ASSIGNED A VALUE. */ +static int +reset_locale_vars () +{ + char *t, *x; +#if defined (HAVE_SETLOCALE) + if (lang == 0 || *lang == '\0') + maybe_make_export_env (); /* trust that this will change environment for setlocale */ + if (setlocale (LC_ALL, lang ? lang : "") == 0) + return 0; + + x = 0; +# if defined (LC_CTYPE) + x = setlocale (LC_CTYPE, get_locale_var ("LC_CTYPE")); +# endif +# if defined (LC_COLLATE) + t = setlocale (LC_COLLATE, get_locale_var ("LC_COLLATE")); +# endif +# if defined (LC_MESSAGES) + t = setlocale (LC_MESSAGES, get_locale_var ("LC_MESSAGES")); +# endif +# if defined (LC_NUMERIC) + t = setlocale (LC_NUMERIC, get_locale_var ("LC_NUMERIC")); +# endif +# if defined (LC_TIME) + t = setlocale (LC_TIME, get_locale_var ("LC_TIME")); +# endif + + locale_setblanks (); + locale_mb_cur_max = MB_CUR_MAX; + if (x) + locale_utf8locale = locale_isutf8 (x); +# if defined (HANDLE_MULTIBYTE) + locale_shiftstates = mblen ((char *)NULL, 0); +# else + locale_shiftstates = 0; +# endif + u32reset (); +#endif + return 1; +} + +#if defined (TRANSLATABLE_STRINGS) +/* Translate the contents of STRING, a $"..." quoted string, according + to the current locale. In the `C' or `POSIX' locale, or if gettext() + is not available, the passed string is returned unchanged. The + length of the translated string is returned in LENP, if non-null. */ +char * +localetrans (string, len, lenp) + char *string; + int len, *lenp; +{ + char *locale, *t; + char *translated; + int tlen; + + /* Don't try to translate null strings. */ + if (string == 0 || *string == 0) + { + if (lenp) + *lenp = 0; + return ((char *)NULL); + } + + locale = get_locale_var ("LC_MESSAGES"); + + /* If we don't have setlocale() or the current locale is `C' or `POSIX', + just return the string. If we don't have gettext(), there's no use + doing anything else. */ + if (locale == 0 || locale[0] == '\0' || + (locale[0] == 'C' && locale[1] == '\0') || STREQ (locale, "POSIX")) + { + t = (char *)xmalloc (len + 1); + strcpy (t, string); + if (lenp) + *lenp = len; + return (t); + } + + /* Now try to translate it. */ + if (default_domain && *default_domain) + translated = dgettext (default_domain, string); + else + translated = string; + + if (translated == string) /* gettext returns its argument if untranslatable */ + { + t = (char *)xmalloc (len + 1); + strcpy (t, string); + if (lenp) + *lenp = len; + } + else + { + tlen = strlen (translated); + t = (char *)xmalloc (tlen + 1); + strcpy (t, translated); + if (lenp) + *lenp = tlen; + } + return (t); +} + +/* Change a bash string into a string suitable for inclusion in a `po' file. + This backslash-escapes `"' and `\' and changes newlines into \\\n"\n". */ +char * +mk_msgstr (string, foundnlp) + char *string; + int *foundnlp; +{ + register int c, len; + char *result, *r, *s; + + for (len = 0, s = string; s && *s; s++) + { + len++; + if (*s == '"' || *s == '\\') + len++; + else if (*s == '\n') + len += 5; + } + + r = result = (char *)xmalloc (len + 3); + *r++ = '"'; + + for (s = string; s && (c = *s); s++) + { + if (c == '\n') /* -> \n"" */ + { + *r++ = '\\'; + *r++ = 'n'; + *r++ = '"'; + *r++ = '\n'; + *r++ = '"'; + if (foundnlp) + *foundnlp = 1; + continue; + } + if (c == '"' || c == '\\') + *r++ = '\\'; + *r++ = c; + } + + *r++ = '"'; + *r++ = '\0'; + + return result; +} + +/* $"..." -- Translate the portion of STRING between START and END + according to current locale using gettext (if available) and return + the result. The caller will take care of leaving the quotes intact. + The string will be left without the leading `$' by the caller. + If translation is performed, the translated string will be double-quoted + by the caller. The length of the translated string is returned in LENP, + if non-null. */ +char * +locale_expand (string, start, end, lineno, lenp) + char *string; + int start, end, lineno, *lenp; +{ + int len, tlen, foundnl; + char *temp, *t, *t2; + + temp = (char *)xmalloc (end - start + 1); + for (tlen = 0, len = start; len < end; ) + temp[tlen++] = string[len++]; + temp[tlen] = '\0'; + + /* If we're just dumping translatable strings, don't do anything with the + string itself, but if we're dumping in `po' file format, convert it into + a form more palatable to gettext(3) and friends by quoting `"' and `\' + with backslashes and converting into `\n""'. If we find a + newline in TEMP, we first output a `msgid ""' line and then the + translated string; otherwise we output the `msgid' and translated + string all on one line. */ + if (dump_translatable_strings) + { + if (dump_po_strings) + { + foundnl = 0; + t = mk_msgstr (temp, &foundnl); + t2 = foundnl ? "\"\"\n" : ""; + + printf ("#: %s:%d\nmsgid %s%s\nmsgstr \"\"\n", + yy_input_name (), lineno, t2, t); + free (t); + } + else + printf ("\"%s\"\n", temp); + + if (lenp) + *lenp = tlen; + return (temp); + } + else if (*temp) + { + t = localetrans (temp, tlen, &len); + free (temp); + if (lenp) + *lenp = len; + return (t); + } + else + { + if (lenp) + *lenp = 0; + return (temp); + } +} +#endif + +/* Set every character in the character class to be a shell break + character for the lexical analyzer when the locale changes. */ +static void +locale_setblanks () +{ + int x; + + for (x = 0; x < sh_syntabsiz; x++) + { + if (isblank ((unsigned char)x)) + sh_syntaxtab[x] |= CSHBRK|CBLANK; + else if (member (x, shell_break_chars)) + { + sh_syntaxtab[x] |= CSHBRK; + sh_syntaxtab[x] &= ~CBLANK; + } + else + sh_syntaxtab[x] &= ~(CSHBRK|CBLANK); + } +} + +/* Parse a locale specification + language[_territory][.codeset][@modifier][+special][,[sponsor][_revision]] + and return TRUE if the codeset is UTF-8 or utf8 */ +static int +locale_isutf8 (lspec) + char *lspec; +{ + char *cp, *encoding; + +#if HAVE_LANGINFO_CODESET + cp = nl_langinfo (CODESET); + return (STREQ (cp, "UTF-8") || STREQ (cp, "utf8")); +#elif HAVE_LOCALE_CHARSET + cp = locale_charset (); + return (STREQ (cp, "UTF-8") || STREQ (cp, "utf8")); +#else + /* Take a shot */ + for (cp = lspec; *cp && *cp != '@' && *cp != '+' && *cp != ','; cp++) + { + if (*cp == '.') + { + for (encoding = ++cp; *cp && *cp != '@' && *cp != '+' && *cp != ','; cp++) + ; + /* The encoding (codeset) is the substring between encoding and cp */ + if ((cp - encoding == 5 && STREQN (encoding, "UTF-8", 5)) || + (cp - encoding == 4 && STREQN (encoding, "utf8", 4))) + return 1; + else + return 0; + } + } + return 0; +#endif +} + +#if defined (HAVE_LOCALECONV) +int +locale_decpoint () +{ + struct lconv *lv; + + lv = localeconv (); + return (lv && lv->decimal_point && lv->decimal_point[0]) ? lv->decimal_point[0] : '.'; +} +#else +# undef locale_decpoint +int +locale_decpoint () +{ + return '.'; +} +#endif diff --git a/third_party/bash/mailcheck.c b/third_party/bash/mailcheck.c new file mode 100644 index 000000000..f5bfe8e1a --- /dev/null +++ b/third_party/bash/mailcheck.c @@ -0,0 +1,491 @@ +/* mailcheck.c -- The check is in the mail... */ + +/* Copyright (C) 1987-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include +#include "bashtypes.h" +#include "posixstat.h" +#if defined (HAVE_SYS_PARAM_H) +# include +#endif +#if defined (HAVE_UNISTD_H) +# include +#endif +#include "posixtime.h" +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "mailcheck.h" +#include "tilde.h" + +/* Values for flags word in struct _fileinfo */ +#define MBOX_INITIALIZED 0x01 + +extern time_t shell_start_time; + +extern int mailstat PARAMS((const char *, struct stat *)); + +typedef struct _fileinfo { + char *name; + char *msg; + time_t access_time; + time_t mod_time; + off_t file_size; + int flags; +} FILEINFO; + +/* The list of remembered mail files. */ +static FILEINFO **mailfiles = (FILEINFO **)NULL; + +/* Number of mail files that we have. */ +static int mailfiles_count; + +/* The last known time that mail was checked. */ +static time_t last_time_mail_checked = 0; + +/* Non-zero means warn if a mail file has been read since last checked. */ +int mail_warning; + +static int find_mail_file PARAMS((char *)); +static void init_mail_file PARAMS((int)); +static void update_mail_file PARAMS((int)); +static int add_mail_file PARAMS((char *, char *)); + +static FILEINFO *alloc_mail_file PARAMS((char *, char *)); +static void dispose_mail_file PARAMS((FILEINFO *)); + +static int file_mod_date_changed PARAMS((int)); +static int file_access_date_changed PARAMS((int)); +static int file_has_grown PARAMS((int)); + +static char *parse_mailpath_spec PARAMS((char *)); + +/* Returns non-zero if it is time to check mail. */ +int +time_to_check_mail () +{ + char *temp; + time_t now; + intmax_t seconds; + + temp = get_string_value ("MAILCHECK"); + + /* Negative number, or non-numbers (such as empty string) cause no + checking to take place. */ + if (temp == 0 || legal_number (temp, &seconds) == 0 || seconds < 0) + return (0); + + now = NOW; + /* Time to check if MAILCHECK is explicitly set to zero, or if enough + time has passed since the last check. */ + return (seconds == 0 || ((now - last_time_mail_checked) >= seconds)); +} + +/* Okay, we have checked the mail. Perhaps I should make this function + go away. */ +void +reset_mail_timer () +{ + last_time_mail_checked = NOW; +} + +/* Locate a file in the list. Return index of + entry, or -1 if not found. */ +static int +find_mail_file (file) + char *file; +{ + register int i; + + for (i = 0; i < mailfiles_count; i++) + if (STREQ (mailfiles[i]->name, file)) + return i; + + return -1; +} + +#define RESET_MAIL_FILE(i) \ + do \ + { \ + mailfiles[i]->access_time = mailfiles[i]->mod_time = 0; \ + mailfiles[i]->file_size = 0; \ + mailfiles[i]->flags = 0; \ + } \ + while (0) + +#define UPDATE_MAIL_FILE(i, finfo) \ + do \ + { \ + mailfiles[i]->access_time = finfo.st_atime; \ + mailfiles[i]->mod_time = finfo.st_mtime; \ + mailfiles[i]->file_size = finfo.st_size; \ + mailfiles[i]->flags |= MBOX_INITIALIZED; \ + } \ + while (0) + +static void +init_mail_file (i) + int i; +{ + mailfiles[i]->access_time = mailfiles[i]->mod_time = last_time_mail_checked ? last_time_mail_checked : shell_start_time; + mailfiles[i]->file_size = 0; + mailfiles[i]->flags = 0; +} + +static void +update_mail_file (i) + int i; +{ + char *file; + struct stat finfo; + + file = mailfiles[i]->name; + if (mailstat (file, &finfo) == 0) + UPDATE_MAIL_FILE (i, finfo); + else + RESET_MAIL_FILE (i); +} + +/* Add this file to the list of remembered files and return its index + in the list of mail files. */ +static int +add_mail_file (file, msg) + char *file, *msg; +{ + struct stat finfo; + char *filename; + int i; + + filename = full_pathname (file); + i = find_mail_file (filename); + if (i >= 0) + { + if (mailstat (filename, &finfo) == 0) + UPDATE_MAIL_FILE (i, finfo); + + free (filename); + return i; + } + + i = mailfiles_count++; + mailfiles = (FILEINFO **)xrealloc + (mailfiles, mailfiles_count * sizeof (FILEINFO *)); + + mailfiles[i] = alloc_mail_file (filename, msg); + init_mail_file (i); + + return i; +} + +/* Reset the existing mail files access and modification times to zero. */ +void +reset_mail_files () +{ + register int i; + + for (i = 0; i < mailfiles_count; i++) + RESET_MAIL_FILE (i); +} + +static FILEINFO * +alloc_mail_file (filename, msg) + char *filename, *msg; +{ + FILEINFO *mf; + + mf = (FILEINFO *)xmalloc (sizeof (FILEINFO)); + mf->name = filename; + mf->msg = msg ? savestring (msg) : (char *)NULL; + mf->flags = 0; + + return mf; +} + +static void +dispose_mail_file (mf) + FILEINFO *mf; +{ + free (mf->name); + FREE (mf->msg); + free (mf); +} + +/* Free the information that we have about the remembered mail files. */ +void +free_mail_files () +{ + register int i; + + for (i = 0; i < mailfiles_count; i++) + dispose_mail_file (mailfiles[i]); + + if (mailfiles) + free (mailfiles); + + mailfiles_count = 0; + mailfiles = (FILEINFO **)NULL; +} + +void +init_mail_dates () +{ + if (mailfiles == 0) + remember_mail_dates (); +} + +/* Return non-zero if FILE's mod date has changed and it has not been + accessed since modified. If the size has dropped to zero, reset + the cached mail file info. */ +static int +file_mod_date_changed (i) + int i; +{ + time_t mtime; + struct stat finfo; + char *file; + + file = mailfiles[i]->name; + mtime = mailfiles[i]->mod_time; + + if (mailstat (file, &finfo) != 0) + return (0); + + if (finfo.st_size > 0) + return (mtime < finfo.st_mtime); + + if (finfo.st_size == 0 && mailfiles[i]->file_size > 0) + UPDATE_MAIL_FILE (i, finfo); + + return (0); +} + +/* Return non-zero if FILE's access date has changed. */ +static int +file_access_date_changed (i) + int i; +{ + time_t atime; + struct stat finfo; + char *file; + + file = mailfiles[i]->name; + atime = mailfiles[i]->access_time; + + if (mailstat (file, &finfo) != 0) + return (0); + + if (finfo.st_size > 0) + return (atime < finfo.st_atime); + + return (0); +} + +/* Return non-zero if FILE's size has increased. */ +static int +file_has_grown (i) + int i; +{ + off_t size; + struct stat finfo; + char *file; + + file = mailfiles[i]->name; + size = mailfiles[i]->file_size; + + return ((mailstat (file, &finfo) == 0) && (finfo.st_size > size)); +} + +/* Take an element from $MAILPATH and return the portion from + the first unquoted `?' or `%' to the end of the string. This is the + message to be printed when the file contents change. */ +static char * +parse_mailpath_spec (str) + char *str; +{ + char *s; + int pass_next; + + for (s = str, pass_next = 0; s && *s; s++) + { + if (pass_next) + { + pass_next = 0; + continue; + } + if (*s == '\\') + { + pass_next++; + continue; + } + if (*s == '?' || *s == '%') + return s; + } + return ((char *)NULL); +} + +char * +make_default_mailpath () +{ +#if defined (DEFAULT_MAIL_DIRECTORY) + char *mp; + + get_current_user_info (); + mp = (char *)xmalloc (2 + sizeof (DEFAULT_MAIL_DIRECTORY) + strlen (current_user.user_name)); + strcpy (mp, DEFAULT_MAIL_DIRECTORY); + mp[sizeof(DEFAULT_MAIL_DIRECTORY) - 1] = '/'; + strcpy (mp + sizeof (DEFAULT_MAIL_DIRECTORY), current_user.user_name); + return (mp); +#else + return ((char *)NULL); +#endif +} + +/* Remember the dates of the files specified by MAILPATH, or if there is + no MAILPATH, by the file specified in MAIL. If neither exists, use a + default value, which we randomly concoct from using Unix. */ + +void +remember_mail_dates () +{ + char *mailpaths; + char *mailfile, *mp; + int i = 0; + + mailpaths = get_string_value ("MAILPATH"); + + /* If no $MAILPATH, but $MAIL, use that as a single filename to check. */ + if (mailpaths == 0 && (mailpaths = get_string_value ("MAIL"))) + { + add_mail_file (mailpaths, (char *)NULL); + return; + } + + if (mailpaths == 0) + { + mailpaths = make_default_mailpath (); + if (mailpaths) + { + add_mail_file (mailpaths, (char *)NULL); + free (mailpaths); + } + return; + } + + while (mailfile = extract_colon_unit (mailpaths, &i)) + { + mp = parse_mailpath_spec (mailfile); + if (mp && *mp) + *mp++ = '\0'; + add_mail_file (mailfile, mp); + free (mailfile); + } +} + +/* check_mail () is useful for more than just checking mail. Since it has + the paranoids dream ability of telling you when someone has read your + mail, it can just as easily be used to tell you when someones .profile + file has been read, thus letting one know when someone else has logged + in. Pretty good, huh? */ + +/* Check for mail in some files. If the modification date of any + of the files in MAILPATH has changed since we last did a + remember_mail_dates () then mention that the user has mail. + Special hack: If the variable MAIL_WARNING is non-zero and the + mail file has been accessed since the last time we remembered, then + the message "The mail in has been read" is printed. */ +void +check_mail () +{ + char *current_mail_file, *message; + int i, use_user_notification; + char *dollar_underscore, *temp; + + dollar_underscore = get_string_value ("_"); + if (dollar_underscore) + dollar_underscore = savestring (dollar_underscore); + + for (i = 0; i < mailfiles_count; i++) + { + current_mail_file = mailfiles[i]->name; + + if (*current_mail_file == '\0') + continue; + + if (file_mod_date_changed (i)) + { + int file_is_bigger; + + use_user_notification = mailfiles[i]->msg != (char *)NULL; + message = mailfiles[i]->msg ? mailfiles[i]->msg : _("You have mail in $_"); + + bind_variable ("_", current_mail_file, 0); + +#define atime mailfiles[i]->access_time +#define mtime mailfiles[i]->mod_time + + /* Have to compute this before the call to update_mail_file, which + resets all the information. */ + file_is_bigger = file_has_grown (i); + + update_mail_file (i); + + /* If the user has just run a program which manipulates the + mail file, then don't bother explaining that the mail + file has been manipulated. Since some systems don't change + the access time to be equal to the modification time when + the mail in the file is manipulated, check the size also. If + the file has not grown, continue. */ + if ((atime >= mtime) && !file_is_bigger) + continue; + + /* If the mod time is later than the access time and the file + has grown, note the fact that this is *new* mail. */ + if (use_user_notification == 0 && (atime < mtime) && file_is_bigger) + message = _("You have new mail in $_"); +#undef atime +#undef mtime + + if (temp = expand_string_to_string (message, Q_DOUBLE_QUOTES)) + { + puts (temp); + free (temp); + } + else + putchar ('\n'); + } + + if (mail_warning && file_access_date_changed (i)) + { + update_mail_file (i); + printf (_("The mail in %s has been read\n"), current_mail_file); + } + } + + if (dollar_underscore) + { + bind_variable ("_", dollar_underscore, 0); + free (dollar_underscore); + } + else + unbind_variable ("_"); +} diff --git a/third_party/bash/mailcheck.h b/third_party/bash/mailcheck.h new file mode 100644 index 000000000..e930124c3 --- /dev/null +++ b/third_party/bash/mailcheck.h @@ -0,0 +1,34 @@ +/* mailcheck.h -- variables and function declarations for mail checking. */ + +/* Copyright (C) 1987-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_MAILCHECK_H_) +#define _MAILCHECK_H_ + +/* Functions from mailcheck.c */ +extern int time_to_check_mail PARAMS((void)); +extern void reset_mail_timer PARAMS((void)); +extern void reset_mail_files PARAMS((void)); +extern void free_mail_files PARAMS((void)); +extern char *make_default_mailpath PARAMS((void)); +extern void remember_mail_dates PARAMS((void)); +extern void init_mail_dates PARAMS((void)); +extern void check_mail PARAMS((void)); + +#endif /* _MAILCHECK_H */ diff --git a/third_party/bash/mailstat.c b/third_party/bash/mailstat.c new file mode 100644 index 000000000..ba971b205 --- /dev/null +++ b/third_party/bash/mailstat.c @@ -0,0 +1,159 @@ +/* mailstat.c -- stat a mailbox file, handling maildir-type mail directories */ + +/* Copyright (C) 2001 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include +#include + +#include "bashtypes.h" +#include "posixstat.h" +#include "posixdir.h" +#include "bashansi.h" + +#if defined (HAVE_SYS_PARAM_H) +# include +#endif + +#include "maxpath.h" + +/* + * Stat a file. If it's a maildir, check all messages + * in the maildir and present the grand total as a file. + * The fields in the 'struct stat' are from the mail directory. + * The following fields are emulated: + * + * st_nlink always 1, unless st_blocks is not present, in which case it's + * the total number of messages + * st_size total number of bytes in all files + * st_blocks total number of messages, if present in struct stat + * st_atime access time of newest file in maildir + * st_mtime modify time of newest file in maildir + * st_mode S_IFDIR changed to S_IFREG + * + * This is good enough for most mail-checking applications. + */ + +int +mailstat(path, st) + const char *path; + struct stat *st; +{ + static struct stat st_new_last, st_ret_last; + struct stat st_ret, st_tmp; + DIR *dd; + struct dirent *fn; + char dir[PATH_MAX * 2], file[PATH_MAX * 2 + 1]; + int i, l; + time_t atime, mtime; + + atime = mtime = 0; + + /* First see if it's a directory. */ + if ((i = stat(path, st)) != 0 || S_ISDIR(st->st_mode) == 0) + return i; + + if (strlen(path) > sizeof(dir) - 5) + { +#ifdef ENAMETOOLONG + errno = ENAMETOOLONG; +#else + errno = EINVAL; +#endif + return -1; + } + + st_ret = *st; + st_ret.st_nlink = 1; + st_ret.st_size = 0; +#ifdef HAVE_STRUCT_STAT_ST_BLOCKS + st_ret.st_blocks = 0; +#else + st_ret.st_nlink = 0; +#endif + st_ret.st_mode &= ~S_IFDIR; + st_ret.st_mode |= S_IFREG; + + /* See if cur/ is present */ + sprintf(dir, "%s/cur", path); + if (stat(dir, &st_tmp) || S_ISDIR(st_tmp.st_mode) == 0) + return 0; + st_ret.st_atime = st_tmp.st_atime; + + /* See if tmp/ is present */ + sprintf(dir, "%s/tmp", path); + if (stat(dir, &st_tmp) || S_ISDIR(st_tmp.st_mode) == 0) + return 0; + st_ret.st_mtime = st_tmp.st_mtime; + + /* And new/ */ + sprintf(dir, "%s/new", path); + if (stat(dir, &st_tmp) || S_ISDIR(st_tmp.st_mode) == 0) + return 0; + st_ret.st_mtime = st_tmp.st_mtime; + + /* Optimization - if new/ didn't change, nothing else did. */ + if (st_tmp.st_dev == st_new_last.st_dev && + st_tmp.st_ino == st_new_last.st_ino && + st_tmp.st_atime == st_new_last.st_atime && + st_tmp.st_mtime == st_new_last.st_mtime) + { + *st = st_ret_last; + return 0; + } + st_new_last = st_tmp; + + /* Loop over new/ and cur/ */ + for (i = 0; i < 2; i++) + { + sprintf(dir, "%s/%s", path, i ? "cur" : "new"); + sprintf(file, "%s/", dir); + l = strlen(file); + if ((dd = opendir(dir)) == NULL) + return 0; + while ((fn = readdir(dd)) != NULL) + { + if (fn->d_name[0] == '.' || strlen(fn->d_name) + l >= sizeof(file)) + continue; + strcpy(file + l, fn->d_name); + if (stat(file, &st_tmp) != 0) + continue; + st_ret.st_size += st_tmp.st_size; +#ifdef HAVE_STRUCT_STAT_ST_BLOCKS + st_ret.st_blocks++; +#else + st_ret.st_nlink++; +#endif + if (st_tmp.st_atime != st_tmp.st_mtime && st_tmp.st_atime > atime) + atime = st_tmp.st_atime; + if (st_tmp.st_mtime > mtime) + mtime = st_tmp.st_mtime; + } + closedir(dd); + } + +/* if (atime) */ /* Set atime even if cur/ is empty */ + st_ret.st_atime = atime; + if (mtime) + st_ret.st_mtime = mtime; + + *st = st_ret_last = st_ret; + return 0; +} diff --git a/third_party/bash/make_cmd.c b/third_party/bash/make_cmd.c new file mode 100644 index 000000000..98151a41a --- /dev/null +++ b/third_party/bash/make_cmd.c @@ -0,0 +1,907 @@ +/* make_cmd.c -- Functions for making instances of the various + parser constructs. */ + +/* Copyright (C) 1989-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "filecntl.h" +#include "bashansi.h" +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "parser.h" +#include "flags.h" +#include "input.h" + +#if defined (JOB_CONTROL) +#include "jobs.h" +#endif + +#include "shmbutil.h" + +int here_doc_first_line = 0; + +/* Object caching */ +sh_obj_cache_t wdcache = {0, 0, 0}; +sh_obj_cache_t wlcache = {0, 0, 0}; + +#define WDCACHESIZE 128 +#define WLCACHESIZE 128 + +static COMMAND *make_for_or_select PARAMS((enum command_type, WORD_DESC *, WORD_LIST *, COMMAND *, int)); +#if defined (ARITH_FOR_COMMAND) +static WORD_LIST *make_arith_for_expr PARAMS((char *)); +#endif +static COMMAND *make_until_or_while PARAMS((enum command_type, COMMAND *, COMMAND *)); + +void +cmd_init () +{ + ocache_create (wdcache, WORD_DESC, WDCACHESIZE); + ocache_create (wlcache, WORD_LIST, WLCACHESIZE); +} + +WORD_DESC * +alloc_word_desc () +{ + WORD_DESC *temp; + + ocache_alloc (wdcache, WORD_DESC, temp); + temp->flags = 0; + temp->word = 0; + return temp; +} + +WORD_DESC * +make_bare_word (string) + const char *string; +{ + WORD_DESC *temp; + + temp = alloc_word_desc (); + + if (*string) + temp->word = savestring (string); + else + { + temp->word = (char *)xmalloc (1); + temp->word[0] = '\0'; + } + + return (temp); +} + +WORD_DESC * +make_word_flags (w, string) + WORD_DESC *w; + const char *string; +{ + register int i; + size_t slen; + DECLARE_MBSTATE; + + i = 0; + slen = strlen (string); + while (i < slen) + { + switch (string[i]) + { + case '$': + w->flags |= W_HASDOLLAR; + break; + case '\\': + break; /* continue the loop */ + case '\'': + case '`': + case '"': + w->flags |= W_QUOTED; + break; + } + + ADVANCE_CHAR (string, slen, i); + } + + return (w); +} + +WORD_DESC * +make_word (string) + const char *string; +{ + WORD_DESC *temp; + + temp = make_bare_word (string); + return (make_word_flags (temp, string)); +} + +WORD_DESC * +make_word_from_token (token) + int token; +{ + char tokenizer[2]; + + tokenizer[0] = token; + tokenizer[1] = '\0'; + + return (make_word (tokenizer)); +} + +WORD_LIST * +make_word_list (word, wlink) + WORD_DESC *word; + WORD_LIST *wlink; +{ + WORD_LIST *temp; + + ocache_alloc (wlcache, WORD_LIST, temp); + + temp->word = word; + temp->next = wlink; + return (temp); +} + +COMMAND * +make_command (type, pointer) + enum command_type type; + SIMPLE_COM *pointer; +{ + COMMAND *temp; + + temp = (COMMAND *)xmalloc (sizeof (COMMAND)); + temp->type = type; + temp->value.Simple = pointer; + temp->value.Simple->flags = temp->flags = 0; + temp->redirects = (REDIRECT *)NULL; + return (temp); +} + +COMMAND * +command_connect (com1, com2, connector) + COMMAND *com1, *com2; + int connector; +{ + CONNECTION *temp; + + temp = (CONNECTION *)xmalloc (sizeof (CONNECTION)); + temp->connector = connector; + temp->first = com1; + temp->second = com2; + return (make_command (cm_connection, (SIMPLE_COM *)temp)); +} + +static COMMAND * +make_for_or_select (type, name, map_list, action, lineno) + enum command_type type; + WORD_DESC *name; + WORD_LIST *map_list; + COMMAND *action; + int lineno; +{ + FOR_COM *temp; + + temp = (FOR_COM *)xmalloc (sizeof (FOR_COM)); + temp->flags = 0; + temp->name = name; + temp->line = lineno; + temp->map_list = map_list; + temp->action = action; + return (make_command (type, (SIMPLE_COM *)temp)); +} + +COMMAND * +make_for_command (name, map_list, action, lineno) + WORD_DESC *name; + WORD_LIST *map_list; + COMMAND *action; + int lineno; +{ + return (make_for_or_select (cm_for, name, map_list, action, lineno)); +} + +COMMAND * +make_select_command (name, map_list, action, lineno) + WORD_DESC *name; + WORD_LIST *map_list; + COMMAND *action; + int lineno; +{ +#if defined (SELECT_COMMAND) + return (make_for_or_select (cm_select, name, map_list, action, lineno)); +#else + set_exit_status (2); + return ((COMMAND *)NULL); +#endif +} + +#if defined (ARITH_FOR_COMMAND) +static WORD_LIST * +make_arith_for_expr (s) + char *s; +{ + WORD_LIST *result; + WORD_DESC *wd; + + if (s == 0 || *s == '\0') + return ((WORD_LIST *)NULL); + wd = make_word (s); + wd->flags |= W_NOGLOB|W_NOSPLIT|W_QUOTED|W_NOTILDE|W_NOPROCSUB; /* no word splitting or globbing */ + result = make_word_list (wd, (WORD_LIST *)NULL); + return result; +} +#endif + +/* Note that this function calls dispose_words on EXPRS, since it doesn't + use the word list directly. We free it here rather than at the caller + because no other function in this file requires that the caller free + any arguments. */ +COMMAND * +make_arith_for_command (exprs, action, lineno) + WORD_LIST *exprs; + COMMAND *action; + int lineno; +{ +#if defined (ARITH_FOR_COMMAND) + ARITH_FOR_COM *temp; + WORD_LIST *init, *test, *step; + char *s, *t, *start; + int nsemi, i; + + init = test = step = (WORD_LIST *)NULL; + /* Parse the string into the three component sub-expressions. */ + start = t = s = exprs->word->word; + for (nsemi = 0; ;) + { + /* skip whitespace at the start of each sub-expression. */ + while (whitespace (*s)) + s++; + start = s; + /* skip to the semicolon or EOS */ + i = skip_to_delim (start, 0, ";", SD_NOJMP|SD_NOPROCSUB); + s = start + i; + + t = (i > 0) ? substring (start, 0, i) : (char *)NULL; + + nsemi++; + switch (nsemi) + { + case 1: + init = make_arith_for_expr (t); + break; + case 2: + test = make_arith_for_expr (t); + break; + case 3: + step = make_arith_for_expr (t); + break; + } + + FREE (t); + if (*s == '\0') + break; + s++; /* skip over semicolon */ + } + + if (nsemi != 3) + { + if (nsemi < 3) + parser_error (lineno, _("syntax error: arithmetic expression required")); + else + parser_error (lineno, _("syntax error: `;' unexpected")); + parser_error (lineno, _("syntax error: `((%s))'"), exprs->word->word); + free (init); + free (test); + free (step); + set_exit_status (2); + return ((COMMAND *)NULL); + } + + temp = (ARITH_FOR_COM *)xmalloc (sizeof (ARITH_FOR_COM)); + temp->flags = 0; + temp->line = lineno; + temp->init = init ? init : make_arith_for_expr ("1"); + temp->test = test ? test : make_arith_for_expr ("1"); + temp->step = step ? step : make_arith_for_expr ("1"); + temp->action = action; + + dispose_words (exprs); + return (make_command (cm_arith_for, (SIMPLE_COM *)temp)); +#else + dispose_words (exprs); + set_exit_status (2); + return ((COMMAND *)NULL); +#endif /* ARITH_FOR_COMMAND */ +} + +COMMAND * +make_group_command (command) + COMMAND *command; +{ + GROUP_COM *temp; + + temp = (GROUP_COM *)xmalloc (sizeof (GROUP_COM)); + temp->command = command; + return (make_command (cm_group, (SIMPLE_COM *)temp)); +} + +COMMAND * +make_case_command (word, clauses, lineno) + WORD_DESC *word; + PATTERN_LIST *clauses; + int lineno; +{ + CASE_COM *temp; + + temp = (CASE_COM *)xmalloc (sizeof (CASE_COM)); + temp->flags = 0; + temp->line = lineno; + temp->word = word; + temp->clauses = REVERSE_LIST (clauses, PATTERN_LIST *); + return (make_command (cm_case, (SIMPLE_COM *)temp)); +} + +PATTERN_LIST * +make_pattern_list (patterns, action) + WORD_LIST *patterns; + COMMAND *action; +{ + PATTERN_LIST *temp; + + temp = (PATTERN_LIST *)xmalloc (sizeof (PATTERN_LIST)); + temp->patterns = REVERSE_LIST (patterns, WORD_LIST *); + temp->action = action; + temp->next = NULL; + temp->flags = 0; + return (temp); +} + +COMMAND * +make_if_command (test, true_case, false_case) + COMMAND *test, *true_case, *false_case; +{ + IF_COM *temp; + + temp = (IF_COM *)xmalloc (sizeof (IF_COM)); + temp->flags = 0; + temp->test = test; + temp->true_case = true_case; + temp->false_case = false_case; + return (make_command (cm_if, (SIMPLE_COM *)temp)); +} + +static COMMAND * +make_until_or_while (which, test, action) + enum command_type which; + COMMAND *test, *action; +{ + WHILE_COM *temp; + + temp = (WHILE_COM *)xmalloc (sizeof (WHILE_COM)); + temp->flags = 0; + temp->test = test; + temp->action = action; + return (make_command (which, (SIMPLE_COM *)temp)); +} + +COMMAND * +make_while_command (test, action) + COMMAND *test, *action; +{ + return (make_until_or_while (cm_while, test, action)); +} + +COMMAND * +make_until_command (test, action) + COMMAND *test, *action; +{ + return (make_until_or_while (cm_until, test, action)); +} + +COMMAND * +make_arith_command (exp) + WORD_LIST *exp; +{ +#if defined (DPAREN_ARITHMETIC) + COMMAND *command; + ARITH_COM *temp; + + command = (COMMAND *)xmalloc (sizeof (COMMAND)); + command->value.Arith = temp = (ARITH_COM *)xmalloc (sizeof (ARITH_COM)); + + temp->flags = 0; + temp->line = line_number; + temp->exp = exp; + + command->type = cm_arith; + command->redirects = (REDIRECT *)NULL; + command->flags = 0; + + return (command); +#else + set_exit_status (2); + return ((COMMAND *)NULL); +#endif +} + +#if defined (COND_COMMAND) +struct cond_com * +make_cond_node (type, op, left, right) + int type; + WORD_DESC *op; + struct cond_com *left, *right; +{ + COND_COM *temp; + + temp = (COND_COM *)xmalloc (sizeof (COND_COM)); + temp->flags = 0; + temp->line = line_number; + temp->type = type; + temp->op = op; + temp->left = left; + temp->right = right; + + return (temp); +} +#endif + +COMMAND * +make_cond_command (cond_node) + COND_COM *cond_node; +{ +#if defined (COND_COMMAND) + COMMAND *command; + + command = (COMMAND *)xmalloc (sizeof (COMMAND)); + command->value.Cond = cond_node; + + command->type = cm_cond; + command->redirects = (REDIRECT *)NULL; + command->flags = 0; + command->line = cond_node ? cond_node->line : 0; + + return (command); +#else + set_exit_status (2); + return ((COMMAND *)NULL); +#endif +} + +COMMAND * +make_bare_simple_command () +{ + COMMAND *command; + SIMPLE_COM *temp; + + command = (COMMAND *)xmalloc (sizeof (COMMAND)); + command->value.Simple = temp = (SIMPLE_COM *)xmalloc (sizeof (SIMPLE_COM)); + + temp->flags = 0; + temp->line = line_number; + temp->words = (WORD_LIST *)NULL; + temp->redirects = (REDIRECT *)NULL; + + command->type = cm_simple; + command->redirects = (REDIRECT *)NULL; + command->flags = 0; + + return (command); +} + +/* Return a command which is the connection of the word or redirection + in ELEMENT, and the command * or NULL in COMMAND. */ +COMMAND * +make_simple_command (element, command) + ELEMENT element; + COMMAND *command; +{ + /* If we are starting from scratch, then make the initial command + structure. Also note that we have to fill in all the slots, since + malloc doesn't return zeroed space. */ + if (command == 0) + { + command = make_bare_simple_command (); + parser_state |= PST_REDIRLIST; + } + + if (element.word) + { + command->value.Simple->words = make_word_list (element.word, command->value.Simple->words); + parser_state &= ~PST_REDIRLIST; + } + else if (element.redirect) + { + REDIRECT *r = element.redirect; + /* Due to the way <> is implemented, there may be more than a single + redirection in element.redirect. We just follow the chain as far + as it goes, and hook onto the end. */ + while (r->next) + r = r->next; + r->next = command->value.Simple->redirects; + command->value.Simple->redirects = element.redirect; + } + + return (command); +} + +/* Because we are Bourne compatible, we read the input for this + << or <<- redirection now, from wherever input is coming from. + We store the input read into a WORD_DESC. Replace the text of + the redirectee.word with the new input text. If <<- is on, + then remove leading TABS from each line. */ +void +make_here_document (temp, lineno) + REDIRECT *temp; + int lineno; +{ + int kill_leading, redir_len; + char *redir_word, *document, *full_line; + int document_index, document_size, delim_unquoted; + + if (temp->instruction != r_deblank_reading_until && + temp->instruction != r_reading_until) + { + internal_error (_("make_here_document: bad instruction type %d"), temp->instruction); + return; + } + + kill_leading = temp->instruction == r_deblank_reading_until; + + full_line = document = (char *)NULL; + document_index = document_size = 0; + + delim_unquoted = (temp->redirectee.filename->flags & W_QUOTED) == 0; + + /* Quote removal is the only expansion performed on the delimiter + for here documents, making it an extremely special case. */ + /* "If any part of word is quoted, the delimiter shall be formed by + performing quote removal on word." */ + if (delim_unquoted == 0) + redir_word = string_quote_removal (temp->redirectee.filename->word, 0); + else + redir_word = savestring (temp->redirectee.filename->word); + + /* redirection_expand will return NULL if the expansion results in + multiple words or no words. Check for that here, and just abort + this here document if it does. */ + if (redir_word) + redir_len = strlen (redir_word); + else + { + temp->here_doc_eof = (char *)xmalloc (1); + temp->here_doc_eof[0] = '\0'; + goto document_done; + } + + free (temp->redirectee.filename->word); + temp->here_doc_eof = redir_word; + + /* Read lines from wherever lines are coming from. + For each line read, if kill_leading, then kill the + leading tab characters. + If the line matches redir_word exactly, then we have + manufactured the document. Otherwise, add the line to the + list of lines in the document. */ + + /* If the here-document delimiter was quoted, the lines should + be read verbatim from the input. If it was not quoted, we + need to perform backslash-quoted newline removal. */ + while (full_line = read_secondary_line (delim_unquoted)) + { + register char *line; + int len; + + here_doc_first_line = 0; + line = full_line; + line_number++; + + /* If set -v is in effect, echo the line read. read_secondary_line/ + read_a_line leaves the newline at the end, so don't print another. */ + if (echo_input_at_read) + fprintf (stderr, "%s", line); + + if (kill_leading && *line) + { + /* Hack: To be compatible with some Bourne shells, we + check the word before stripping the whitespace. This + is a hack, though. */ + if (STREQN (line, redir_word, redir_len) && line[redir_len] == '\n') + break; + + while (*line == '\t') + line++; + } + + if (*line == 0) + continue; + + if (STREQN (line, redir_word, redir_len) && line[redir_len] == '\n') + break; + + /* Backwards compatibility here */ + if (STREQN (line, redir_word, redir_len) && (parser_state & PST_EOFTOKEN) && shell_eof_token && strchr (line+redir_len, shell_eof_token)) + { + shell_ungets (line + redir_len); + full_line = 0; + break; + } + + len = strlen (line); + if (len + document_index >= document_size) + { + document_size = document_size ? 2 * (document_size + len) : len + 2; + document = (char *)xrealloc (document, document_size); + } + + /* len is guaranteed to be > 0 because of the check for line + being an empty string before the call to strlen. */ + FASTCOPY (line, document + document_index, len); + document_index += len; + } + + if (full_line == 0) + internal_warning (_("here-document at line %d delimited by end-of-file (wanted `%s')"), lineno, redir_word); + +document_done: + if (document) + document[document_index] = '\0'; + else + { + document = (char *)xmalloc (1); + document[0] = '\0'; + } + temp->redirectee.filename->word = document; + here_doc_first_line = 0; +} + +/* Generate a REDIRECT from SOURCE, DEST, and INSTRUCTION. + INSTRUCTION is the instruction type, SOURCE is a file descriptor, + and DEST is a file descriptor or a WORD_DESC *. */ +REDIRECT * +make_redirection (source, instruction, dest_and_filename, flags) + REDIRECTEE source; + enum r_instruction instruction; + REDIRECTEE dest_and_filename; + int flags; +{ + REDIRECT *temp; + WORD_DESC *w; + int wlen; + intmax_t lfd; + + temp = (REDIRECT *)xmalloc (sizeof (REDIRECT)); + + /* First do the common cases. */ + temp->redirector = source; + temp->redirectee = dest_and_filename; + temp->here_doc_eof = 0; + temp->instruction = instruction; + temp->flags = 0; + temp->rflags = flags; + temp->next = (REDIRECT *)NULL; + + switch (instruction) + { + + case r_output_direction: /* >foo */ + case r_output_force: /* >| foo */ + case r_err_and_out: /* &>filename */ + temp->flags = O_TRUNC | O_WRONLY | O_CREAT; + break; + + case r_appending_to: /* >>foo */ + case r_append_err_and_out: /* &>> filename */ + temp->flags = O_APPEND | O_WRONLY | O_CREAT; + break; + + case r_input_direction: /* flags = O_RDONLY; + break; + + case r_input_output: /* <>foo */ + temp->flags = O_RDWR | O_CREAT; + break; + + case r_deblank_reading_until: /* <<-foo */ + case r_reading_until: /* << foo */ + case r_reading_string: /* <<< foo */ + case r_close_this: /* <&- */ + case r_duplicating_input: /* 1<&2 */ + case r_duplicating_output: /* 1>&2 */ + break; + + /* the parser doesn't pass these. */ + case r_move_input: /* 1<&2- */ + case r_move_output: /* 1>&2- */ + case r_move_input_word: /* 1<&$foo- */ + case r_move_output_word: /* 1>&$foo- */ + break; + + /* The way the lexer works we have to do this here. */ + case r_duplicating_input_word: /* 1<&$foo */ + case r_duplicating_output_word: /* 1>&$foo */ + w = dest_and_filename.filename; + wlen = strlen (w->word) - 1; + if (w->word[wlen] == '-') /* Yuck */ + { + w->word[wlen] = '\0'; + if (all_digits (w->word) && legal_number (w->word, &lfd) && lfd == (int)lfd) + { + dispose_word (w); + temp->instruction = (instruction == r_duplicating_input_word) ? r_move_input : r_move_output; + temp->redirectee.dest = lfd; + } + else + temp->instruction = (instruction == r_duplicating_input_word) ? r_move_input_word : r_move_output_word; + } + + break; + + default: + programming_error (_("make_redirection: redirection instruction `%d' out of range"), instruction); + abort (); + break; + } + return (temp); +} + +COMMAND * +make_function_def (name, command, lineno, lstart) + WORD_DESC *name; + COMMAND *command; + int lineno, lstart; +{ + FUNCTION_DEF *temp; +#if defined (ARRAY_VARS) + SHELL_VAR *bash_source_v; + ARRAY *bash_source_a; +#endif + + temp = (FUNCTION_DEF *)xmalloc (sizeof (FUNCTION_DEF)); + temp->command = command; + temp->name = name; + temp->line = lineno; + temp->flags = 0; + command->line = lstart; + + /* Information used primarily for debugging. */ + temp->source_file = 0; +#if defined (ARRAY_VARS) + GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a); + if (bash_source_a && array_num_elements (bash_source_a) > 0) + temp->source_file = array_reference (bash_source_a, 0); +#endif + /* Assume that shell functions without a source file before the shell is + initialized come from the environment. Otherwise default to "main" + (usually functions being defined interactively) */ + if (temp->source_file == 0) + temp->source_file = shell_initialized ? "main" : "environment"; + +#if defined (DEBUGGER) + bind_function_def (name->word, temp, 0); +#endif + + temp->source_file = temp->source_file ? savestring (temp->source_file) : 0; + + return (make_command (cm_function_def, (SIMPLE_COM *)temp)); +} + +COMMAND * +make_subshell_command (command) + COMMAND *command; +{ + SUBSHELL_COM *temp; + + temp = (SUBSHELL_COM *)xmalloc (sizeof (SUBSHELL_COM)); + temp->command = command; + temp->flags = CMD_WANT_SUBSHELL; + temp->line = line_number; + return (make_command (cm_subshell, (SIMPLE_COM *)temp)); +} + +COMMAND * +make_coproc_command (name, command) + char *name; + COMMAND *command; +{ + COPROC_COM *temp; + + temp = (COPROC_COM *)xmalloc (sizeof (COPROC_COM)); + temp->name = savestring (name); + temp->command = command; + temp->flags = CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL; + return (make_command (cm_coproc, (SIMPLE_COM *)temp)); +} + +/* Reverse the word list and redirection list in the simple command + has just been parsed. It seems simpler to do this here the one + time then by any other method that I can think of. */ +COMMAND * +clean_simple_command (command) + COMMAND *command; +{ + if (command->type != cm_simple) + command_error ("clean_simple_command", CMDERR_BADTYPE, command->type, 0); + else + { + command->value.Simple->words = + REVERSE_LIST (command->value.Simple->words, WORD_LIST *); + command->value.Simple->redirects = + REVERSE_LIST (command->value.Simple->redirects, REDIRECT *); + } + + parser_state &= ~PST_REDIRLIST; + return (command); +} + +/* The Yacc grammar productions have a problem, in that they take a + list followed by an ampersand (`&') and do a simple command connection, + making the entire list effectively asynchronous, instead of just + the last command. This means that when the list is executed, all + the commands have stdin set to /dev/null when job control is not + active, instead of just the last. This is wrong, and needs fixing + up. This function takes the `&' and applies it to the last command + in the list. This is done only for lists connected by `;'; it makes + `;' bind `tighter' than `&'. */ +COMMAND * +connect_async_list (command, command2, connector) + COMMAND *command, *command2; + int connector; +{ + COMMAND *t, *t1, *t2; + + t1 = command; + t = command->value.Connection->second; + + if (!t || (command->flags & CMD_WANT_SUBSHELL) || + command->value.Connection->connector != ';') + { + t = command_connect (command, command2, connector); + return t; + } + + /* This is just defensive programming. The Yacc precedence rules + will generally hand this function a command where t points directly + to the command we want (e.g. given a ; b ; c ; d &, t1 will point + to the `a ; b ; c' list and t will be the `d'). We only want to do + this if the list is not being executed as a unit in the background + with `( ... )', so we have to check for CMD_WANT_SUBSHELL. That's + the only way to tell. */ + while (((t->flags & CMD_WANT_SUBSHELL) == 0) && t->type == cm_connection && + t->value.Connection->connector == ';') + { + t1 = t; + t = t->value.Connection->second; + } + /* Now we have t pointing to the last command in the list, and + t1->value.Connection->second == t. */ + t2 = command_connect (t, command2, connector); + t1->value.Connection->second = t2; + return command; +} diff --git a/third_party/bash/make_cmd.h b/third_party/bash/make_cmd.h new file mode 100644 index 000000000..bf1fb008d --- /dev/null +++ b/third_party/bash/make_cmd.h @@ -0,0 +1,72 @@ +/* make_cmd.h -- Declarations of functions found in make_cmd.c */ + +/* Copyright (C) 1993-2009,2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_MAKE_CMD_H_) +#define _MAKE_CMD_H_ + +#include "stdc.h" + +extern int here_doc_first_line; + +extern void cmd_init PARAMS((void)); + +extern WORD_DESC *alloc_word_desc PARAMS((void)); +extern WORD_DESC *make_bare_word PARAMS((const char *)); +extern WORD_DESC *make_word_flags PARAMS((WORD_DESC *, const char *)); +extern WORD_DESC *make_word PARAMS((const char *)); +extern WORD_DESC *make_word_from_token PARAMS((int)); + +extern WORD_LIST *make_word_list PARAMS((WORD_DESC *, WORD_LIST *)); + +#define add_string_to_list(s, l) make_word_list (make_word(s), (l)) + +extern COMMAND *make_command PARAMS((enum command_type, SIMPLE_COM *)); +extern COMMAND *command_connect PARAMS((COMMAND *, COMMAND *, int)); +extern COMMAND *make_for_command PARAMS((WORD_DESC *, WORD_LIST *, COMMAND *, int)); +extern COMMAND *make_group_command PARAMS((COMMAND *)); +extern COMMAND *make_case_command PARAMS((WORD_DESC *, PATTERN_LIST *, int)); +extern PATTERN_LIST *make_pattern_list PARAMS((WORD_LIST *, COMMAND *)); +extern COMMAND *make_if_command PARAMS((COMMAND *, COMMAND *, COMMAND *)); +extern COMMAND *make_while_command PARAMS((COMMAND *, COMMAND *)); +extern COMMAND *make_until_command PARAMS((COMMAND *, COMMAND *)); +extern COMMAND *make_bare_simple_command PARAMS((void)); +extern COMMAND *make_simple_command PARAMS((ELEMENT, COMMAND *)); +extern void make_here_document PARAMS((REDIRECT *, int)); +extern REDIRECT *make_redirection PARAMS((REDIRECTEE, enum r_instruction, REDIRECTEE, int)); +extern COMMAND *make_function_def PARAMS((WORD_DESC *, COMMAND *, int, int)); +extern COMMAND *clean_simple_command PARAMS((COMMAND *)); + +extern COMMAND *make_arith_command PARAMS((WORD_LIST *)); + +extern COMMAND *make_select_command PARAMS((WORD_DESC *, WORD_LIST *, COMMAND *, int)); + +#if defined (COND_COMMAND) +extern COND_COM *make_cond_node PARAMS((int, WORD_DESC *, COND_COM *, COND_COM *)); +extern COMMAND *make_cond_command PARAMS((COND_COM *)); +#endif + +extern COMMAND *make_arith_for_command PARAMS((WORD_LIST *, COMMAND *, int)); + +extern COMMAND *make_subshell_command PARAMS((COMMAND *)); +extern COMMAND *make_coproc_command PARAMS((char *, COMMAND *)); + +extern COMMAND *connect_async_list PARAMS((COMMAND *, COMMAND *, int)); + +#endif /* !_MAKE_CMD_H */ diff --git a/third_party/bash/makepath.c b/third_party/bash/makepath.c new file mode 100644 index 000000000..b0d661671 --- /dev/null +++ b/third_party/bash/makepath.c @@ -0,0 +1,128 @@ +/* makepath.c - glue PATH and DIR together into a full pathname. */ + +/* Copyright (C) 1987-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashansi.h" +#include "shell.h" + +#include "tilde.h" + +#ifndef NULL +# define NULL 0 +#endif + +/* MAKE SURE THESE AGREE WITH ../../externs.h. */ + +#ifndef MP_DOTILDE +# define MP_DOTILDE 0x01 +# define MP_DOCWD 0x02 +# define MP_RMDOT 0x04 +# define MP_IGNDOT 0x08 +#endif + +extern char *get_working_directory PARAMS((char *)); + +static char *nullpath = ""; + +/* Take PATH, an element from, e.g., $CDPATH, and DIR, a directory name, + and paste them together into PATH/DIR. Tilde expansion is performed on + PATH if (flags & MP_DOTILDE) is non-zero. If PATH is NULL or the empty + string, it is converted to the current directory. A full pathname is + used if (flags & MP_DOCWD) is non-zero, otherwise `./' is used. If + (flags & MP_RMDOT) is non-zero, any `./' is removed from the beginning + of DIR. If (flags & MP_IGNDOT) is non-zero, a PATH that is "." or "./" + is ignored. */ + +#define MAKEDOT() \ + do { \ + xpath = (char *)xmalloc (2); \ + xpath[0] = '.'; \ + xpath[1] = '\0'; \ + pathlen = 1; \ + } while (0) + +char * +sh_makepath (path, dir, flags) + const char *path, *dir; + int flags; +{ + int dirlen, pathlen; + char *ret, *xpath, *xdir, *r, *s; + + if (path == 0 || *path == '\0') + { + if (flags & MP_DOCWD) + { + xpath = get_working_directory ("sh_makepath"); + if (xpath == 0) + { + ret = get_string_value ("PWD"); + if (ret) + xpath = savestring (ret); + } + if (xpath == 0) + MAKEDOT(); + else + pathlen = strlen (xpath); + } + else + MAKEDOT(); + } + else if ((flags & MP_IGNDOT) && path[0] == '.' && (path[1] == '\0' || + (path[1] == '/' && path[2] == '\0'))) + { + xpath = nullpath; + pathlen = 0; + } + else + { + xpath = ((flags & MP_DOTILDE) && *path == '~') ? bash_tilde_expand (path, 0) : (char *)path; + pathlen = strlen (xpath); + } + + xdir = (char *)dir; + dirlen = strlen (xdir); + if ((flags & MP_RMDOT) && dir[0] == '.' && dir[1] == '/') + { + xdir += 2; + dirlen -= 2; + } + + r = ret = (char *)xmalloc (2 + dirlen + pathlen); + s = xpath; + while (*s) + *r++ = *s++; + if (s > xpath && s[-1] != '/') + *r++ = '/'; + s = xdir; + while (*r++ = *s++) + ; + if (xpath != path && xpath != nullpath) + free (xpath); + return (ret); +} diff --git a/third_party/bash/maxpath.h b/third_party/bash/maxpath.h new file mode 100644 index 000000000..db2e1fb42 --- /dev/null +++ b/third_party/bash/maxpath.h @@ -0,0 +1,75 @@ +/* maxpath.h - Find out what this system thinks PATH_MAX and NAME_MAX are. */ + +/* Copyright (C) 1993 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_MAXPATH_H_) +#define _MAXPATH_H_ + +/* These values are supposed to be in or one of the files + it includes. */ +#if defined (HAVE_LIMITS_H) +# include +#endif /* !HAVE_LIMITS_H */ + +/* If PATH_MAX is not defined, look for MAXPATHLEN */ +#if !defined (PATH_MAX) +# if defined (HAVE_SYS_PARAM_H) +# include +# define maxpath_param_h +# endif +# if defined (MAXPATHLEN) && !defined (PATH_MAX) +# define PATH_MAX MAXPATHLEN +# endif /* MAXPATHLEN && !PATH_MAX */ +#endif /* !PATH_MAX */ + +/* If NAME_MAX is not defined, look for MAXNAMLEN */ +#if !defined (NAME_MAX) +# if defined (HAVE_SYS_PARAM_H) && !defined (maxpath_param_h) +# include +# endif +# if defined (MAXNAMLEN) && !defined (NAME_MAX) +# define NAME_MAX MAXNAMLEN +# endif /* MAXNAMLEN && !NAME_MAX */ +#endif /* !NAME_MAX */ + +/* Default POSIX values */ +#if !defined (PATH_MAX) && defined (_POSIX_PATH_MAX) +# define PATH_MAX _POSIX_PATH_MAX +#endif + +#if !defined (NAME_MAX) && defined (_POSIX_NAME_MAX) +# define NAME_MAX _POSIX_NAME_MAX +#endif + + +/* Default values */ +#if !defined (PATH_MAX) +# define PATH_MAX 1024 +#endif + +#if !defined (NAME_MAX) +# define NAME_MAX 14 +#endif + +#if PATH_MAX < 1024 +# undef PATH_MAX +# define PATH_MAX 1024 +#endif + +#endif /* _MAXPATH_H_ */ diff --git a/third_party/bash/mbscasecmp.c b/third_party/bash/mbscasecmp.c new file mode 100644 index 000000000..6f65d79bd --- /dev/null +++ b/third_party/bash/mbscasecmp.c @@ -0,0 +1,79 @@ +/* mbscasecmp - case-insensitive multibyte string comparison. */ + +/* Copyright (C) 2009-2015 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if !defined (HAVE_MBSCASECMP) && defined (HANDLE_MULTIBYTE) + +#include +#include +#include + +#include +#include + +/* Compare MBS1 and MBS2 without regard to case. */ +int +mbscasecmp (mbs1, mbs2) + const char *mbs1; + const char *mbs2; +{ + int len1, len2, mb_cur_max; + wchar_t c1, c2, l1, l2; + + len1 = len2 = 0; + /* Reset multibyte characters to their initial state. */ + (void) mblen ((char *) NULL, 0); + + mb_cur_max = MB_CUR_MAX; + do + { + len1 = mbtowc (&c1, mbs1, mb_cur_max); + len2 = mbtowc (&c2, mbs2, mb_cur_max); + + if (len1 == 0) + return len2 == 0 ? 0 : -1; + else if (len2 == 0) + return 1; + else if (len1 > 0 && len2 < 0) + return -1; + else if (len1 < 0 && len2 > 0) + return 1; + else if (len1 < 0 && len2 < 0) + { + len1 = strlen (mbs1); + len2 = strlen (mbs2); + return (len1 == len2 ? memcmp (mbs1, mbs2, len1) + : ((len1 < len2) ? (memcmp (mbs1, mbs2, len1) > 0 ? 1 : -1) + : (memcmp (mbs1, mbs2, len2) >= 0 ? 1 : -1))); + } + + l1 = towlower (c1); + l2 = towlower (c2); + + mbs1 += len1; + mbs2 += len2; + } + while (l1 == l2); + + return l1 - l2; +} + +#endif diff --git a/third_party/bash/mbschr.c b/third_party/bash/mbschr.c new file mode 100644 index 000000000..dced76089 --- /dev/null +++ b/third_party/bash/mbschr.c @@ -0,0 +1,91 @@ +/* mbschr.c - strchr(3) that handles multibyte characters. */ + +/* Copyright (C) 2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +# include +#endif + +#include "bashansi.h" +#include "shmbutil.h" + +extern int locale_mb_cur_max; +extern int locale_utf8locale; + +#undef mbschr + +extern char *utf8_mbschr (const char *, int); /* XXX */ + +/* In some locales, the non-first byte of some multibyte characters have + the same value as some ascii character. Faced with these strings, a + legacy strchr() might return the wrong value. */ + +char * +#if defined (PROTOTYPES) +mbschr (const char *s, int c) +#else +mbschr (s, c) + const char *s; + int c; +#endif +{ +#if HANDLE_MULTIBYTE + char *pos; + mbstate_t state; + size_t strlength, mblength; + + if (locale_utf8locale && c < 0x80) + return (utf8_mbschr (s, c)); /* XXX */ + + /* The locale encodings with said weird property are BIG5, BIG5-HKSCS, + GBK, GB18030, SHIFT_JIS, and JOHAB. They exhibit the problem only + when c >= 0x30. We can therefore use the faster bytewise search if + c <= 0x30. */ + if ((unsigned char)c >= '0' && locale_mb_cur_max > 1) + { + pos = (char *)s; + memset (&state, '\0', sizeof(mbstate_t)); + strlength = strlen (s); + + while (strlength > 0) + { + if (is_basic (*pos)) + mblength = 1; + else + { + mblength = mbrlen (pos, strlength, &state); + if (mblength == (size_t)-2 || mblength == (size_t)-1 || mblength == (size_t)0) + mblength = 1; + } + + if (mblength == 1 && c == (unsigned char)*pos) + return pos; + + strlength -= mblength; + pos += mblength; + } + + return ((char *)NULL); + } + else +#endif + return (strchr (s, c)); +} diff --git a/third_party/bash/mbscmp.c b/third_party/bash/mbscmp.c new file mode 100644 index 000000000..9be01378a --- /dev/null +++ b/third_party/bash/mbscmp.c @@ -0,0 +1,77 @@ +/* mbscmp - multibyte string comparison. */ + +/* Copyright (C) 1995-2018 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if !defined (HAVE_MBSCMP) && defined (HANDLE_MULTIBYTE) + +#include +#include +#include + +extern int locale_utf8locale; + +extern int utf8_mbscmp (const char *, const char *); + +/* Compare MBS1 and MBS2. */ +int +mbscmp (mbs1, mbs2) + const char *mbs1; + const char *mbs2; +{ + int len1, len2, mb_cur_max; + wchar_t c1, c2; + + len1 = len2 = 0; + /* Reset multibyte characters to their initial state. */ + (void) mblen ((char *) NULL, 0); + + mb_cur_max = MB_CUR_MAX; + do + { + len1 = mbtowc (&c1, mbs1, mb_cur_max); + len2 = mbtowc (&c2, mbs2, mb_cur_max); + + if (len1 == 0) + return len2 == 0 ? 0 : -1; + else if (len2 == 0) + return 1; + else if (len1 > 0 && len2 < 0) + return -1; + else if (len1 < 0 && len2 > 0) + return 1; + else if (len1 < 0 && len2 < 0) + { + len1 = strlen (mbs1); + len2 = strlen (mbs2); + return (len1 == len2 ? memcmp (mbs1, mbs2, len1) + : ((len1 < len2) ? (memcmp (mbs1, mbs2, len1) > 0 ? 1 : -1) + : (memcmp (mbs1, mbs2, len2) >= 0 ? 1 : -1))); + } + + mbs1 += len1; + mbs2 += len2; + } + while (c1 == c2); + + return c1 - c2; +} + +#endif diff --git a/third_party/bash/memalloc.h b/third_party/bash/memalloc.h new file mode 100644 index 000000000..57318b9d3 --- /dev/null +++ b/third_party/bash/memalloc.h @@ -0,0 +1,62 @@ +/* memalloc.h -- consolidate code for including alloca.h or malloc.h and + defining alloca. */ + +/* Copyright (C) 1993 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_MEMALLOC_H_) +# define _MEMALLOC_H_ + +#if defined (sparc) && defined (sun) && !defined (HAVE_ALLOCA_H) +# define HAVE_ALLOCA_H +#endif + +#if defined (__GNUC__) && !defined (HAVE_ALLOCA) +# define HAVE_ALLOCA +#endif + +#if defined (HAVE_ALLOCA_H) && !defined (HAVE_ALLOCA) && !defined (C_ALLOCA) +# define HAVE_ALLOCA +#endif /* HAVE_ALLOCA_H && !HAVE_ALLOCA */ + +#if defined (__GNUC__) && !defined (C_ALLOCA) +# undef alloca +# define alloca __builtin_alloca +#else /* !__GNUC__ || C_ALLOCA */ +# if defined (HAVE_ALLOCA_H) && !defined (C_ALLOCA) +# if defined (IBMESA) +# include +# else /* !IBMESA */ +# include +# endif /* !IBMESA */ +# else /* !HAVE_ALLOCA_H || C_ALLOCA */ +# if defined (__hpux) && defined (__STDC__) && !defined (alloca) +extern void *alloca (); +# else +# if !defined (alloca) +# if defined (__STDC__) +extern void *alloca (size_t); +# else +extern char *alloca (); +# endif /* !__STDC__ */ +# endif /* !alloca */ +# endif /* !__hpux || !__STDC__ && !alloca */ +# endif /* !HAVE_ALLOCA_H || C_ALLOCA */ +#endif /* !__GNUC__ || C_ALLOCA */ + +#endif /* _MEMALLOC_H_ */ diff --git a/third_party/bash/ndir.h b/third_party/bash/ndir.h new file mode 100644 index 000000000..0aeb3a58c --- /dev/null +++ b/third_party/bash/ndir.h @@ -0,0 +1,37 @@ +/* -- definitions for 4.2BSD-compatible directory access. + last edit: 09-Jul-1983 D A Gwyn. */ + +/* Size of directory block. */ +#define DIRBLKSIZ 512 + +/* NOTE: MAXNAMLEN must be one less than a multiple of 4 */ + +#if defined (VMS) +# define MAXNAMLEN (DIR$S_NAME + 7) /* 80 plus room for version #. */ +# define MAXFULLSPEC NAM$C_MAXRSS /* Maximum full spec */ +#else +# define MAXNAMLEN 15 /* Maximum filename length. */ +#endif /* VMS */ + +/* Data from readdir (). */ +struct direct { + long d_ino; /* Inode number of entry. */ + unsigned short d_reclen; /* Length of this record. */ + unsigned short d_namlen; /* Length of string in d_name. */ + char d_name[MAXNAMLEN + 1]; /* Name of file. */ +}; + +/* Stream data from opendir (). */ +typedef struct { + int dd_fd; /* File descriptor. */ + int dd_loc; /* Offset in block. */ + int dd_size; /* Amount of valid data. */ + char dd_buf[DIRBLKSIZ]; /* Directory block. */ +} DIR; + +extern DIR *opendir (); +extern struct direct *readdir (); +extern long telldir (); +extern void seekdir (), closedir (); + +#define rewinddir(dirp) seekdir (dirp, 0L) diff --git a/third_party/bash/netconn.c b/third_party/bash/netconn.c new file mode 100644 index 000000000..a5fc381c1 --- /dev/null +++ b/third_party/bash/netconn.c @@ -0,0 +1,82 @@ +/* netconn.c -- is a particular file descriptor a network connection?. */ + +/* Copyright (C) 2002-2005 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "posixstat.h" +#include "filecntl.h" + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +/* The second and subsequent conditions must match those used to decide + whether or not to call getpeername() in isnetconn(). */ +#if defined (HAVE_SYS_SOCKET_H) && defined (HAVE_GETPEERNAME) && !defined (SVR4_2) +# include +#endif + +/* Is FD a socket or network connection? */ +int +isnetconn (fd) + int fd; +{ +#if defined (HAVE_SYS_SOCKET_H) && defined (HAVE_GETPEERNAME) && !defined (SVR4_2) && !defined (__BEOS__) + int rv; + socklen_t l; + struct sockaddr sa; + + l = sizeof(sa); + rv = getpeername(fd, &sa, &l); + /* Posix.2 says getpeername can return these errors. */ + return ((rv < 0 && (errno == ENOTSOCK || errno == ENOTCONN || errno == EINVAL || errno == EBADF)) ? 0 : 1); +#else /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ +# if defined (SVR4) || defined (SVR4_2) + /* Sockets on SVR4 and SVR4.2 are character special (streams) devices. */ + struct stat sb; + + if (isatty (fd)) + return (0); + if (fstat (fd, &sb) < 0) + return (0); +# if defined (S_ISFIFO) + if (S_ISFIFO (sb.st_mode)) + return (0); +# endif /* S_ISFIFO */ + return (S_ISCHR (sb.st_mode)); +# else /* !SVR4 && !SVR4_2 */ +# if defined (S_ISSOCK) && !defined (__BEOS__) + struct stat sb; + + if (fstat (fd, &sb) < 0) + return (0); + return (S_ISSOCK (sb.st_mode)); +# else /* !S_ISSOCK || __BEOS__ */ + return (0); +# endif /* !S_ISSOCK || __BEOS__ */ +# endif /* !SVR4 && !SVR4_2 */ +#endif /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ +} diff --git a/third_party/bash/netopen.c b/third_party/bash/netopen.c new file mode 100644 index 000000000..a2fd760d4 --- /dev/null +++ b/third_party/bash/netopen.c @@ -0,0 +1,351 @@ +/* + * netopen.c -- functions to make tcp/udp connections + * + * Chet Ramey + * chet@ins.CWRU.Edu + */ + +/* Copyright (C) 1987-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_NETWORK) + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include + +#if defined (HAVE_SYS_SOCKET_H) +# include +#endif + +#if defined (HAVE_NETINET_IN_H) +# include +#endif + +#if defined (HAVE_NETDB_H) +# include +#endif + +#if defined (HAVE_ARPA_INET_H) +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include + +#include "shell.h" +#include "xmalloc.h" + +#ifndef errno +extern int errno; +#endif + +#if !defined (HAVE_INET_ATON) +extern int inet_aton PARAMS((const char *, struct in_addr *)); +#endif + +#ifndef HAVE_GETADDRINFO +static int _getaddr PARAMS((char *, struct in_addr *)); +static int _getserv PARAMS((char *, int, unsigned short *)); +static int _netopen4 PARAMS((char *, char *, int)); +#else /* HAVE_GETADDRINFO */ +static int _netopen6 PARAMS((char *, char *, int)); +#endif + +static int _netopen PARAMS((char *, char *, int)); + +#ifndef HAVE_GETADDRINFO +/* Stuff the internet address corresponding to HOST into AP, in network + byte order. Return 1 on success, 0 on failure. */ + +static int +_getaddr (host, ap) + char *host; + struct in_addr *ap; +{ + struct hostent *h; + int r; + + r = 0; + if (host[0] >= '0' && host[0] <= '9') + { + /* If the first character is a digit, guess that it's an + Internet address and return immediately if inet_aton succeeds. */ + r = inet_aton (host, ap); + if (r) + return r; + } +#if !defined (HAVE_GETHOSTBYNAME) + return 0; +#else + h = gethostbyname (host); + if (h && h->h_addr) + { + bcopy(h->h_addr, (char *)ap, h->h_length); + return 1; + } +#endif + return 0; + +} + +/* Return 1 if SERV is a valid port number and stuff the converted value into + PP in network byte order. */ +static int +_getserv (serv, proto, pp) + char *serv; + int proto; + unsigned short *pp; +{ + intmax_t l; + unsigned short s; + + if (legal_number (serv, &l)) + { + s = (unsigned short)(l & 0xFFFF); + if (s != l) + return (0); + s = htons (s); + if (pp) + *pp = s; + return 1; + } + else +#if defined (HAVE_GETSERVBYNAME) + { + struct servent *se; + + se = getservbyname (serv, (proto == 't') ? "tcp" : "udp"); + if (se == 0) + return 0; + if (pp) + *pp = se->s_port; /* ports returned in network byte order */ + return 1; + } +#else /* !HAVE_GETSERVBYNAME */ + return 0; +#endif /* !HAVE_GETSERVBYNAME */ +} + +/* + * Open a TCP or UDP connection to HOST on port SERV. Uses the + * traditional BSD mechanisms. Returns the connected socket or -1 on error. + */ +static int +_netopen4(host, serv, typ) + char *host, *serv; + int typ; +{ + struct in_addr ina; + struct sockaddr_in sin; + unsigned short p; + int s, e; + + if (_getaddr(host, &ina) == 0) + { + internal_error (_("%s: host unknown"), host); + errno = EINVAL; + return -1; + } + + if (_getserv(serv, typ, &p) == 0) + { + internal_error(_("%s: invalid service"), serv); + errno = EINVAL; + return -1; + } + + memset ((char *)&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = p; + sin.sin_addr = ina; + + s = socket(AF_INET, (typ == 't') ? SOCK_STREAM : SOCK_DGRAM, 0); + if (s < 0) + { + sys_error ("socket"); + return (-1); + } + + if (connect (s, (struct sockaddr *)&sin, sizeof (sin)) < 0) + { + e = errno; + sys_error("connect"); + close(s); + errno = e; + return (-1); + } + + return(s); +} +#endif /* ! HAVE_GETADDRINFO */ + +#ifdef HAVE_GETADDRINFO +/* + * Open a TCP or UDP connection to HOST on port SERV. Uses getaddrinfo(3) + * which provides support for IPv6. Returns the connected socket or -1 + * on error. + */ +static int +_netopen6 (host, serv, typ) + char *host, *serv; + int typ; +{ + int s, e; + struct addrinfo hints, *res, *res0; + int gerr; + + memset ((char *)&hints, 0, sizeof (hints)); + /* XXX -- if problems with IPv6, set to PF_INET for IPv4 only */ +#ifdef DEBUG /* PF_INET is the one that works for me */ + hints.ai_family = PF_INET; +#else + hints.ai_family = PF_UNSPEC; +#endif + hints.ai_socktype = (typ == 't') ? SOCK_STREAM : SOCK_DGRAM; + + gerr = getaddrinfo (host, serv, &hints, &res0); + if (gerr) + { + if (gerr == EAI_SERVICE) + internal_error ("%s: %s", serv, gai_strerror (gerr)); + else + internal_error ("%s: %s", host, gai_strerror (gerr)); + errno = EINVAL; + return -1; + } + + for (res = res0; res; res = res->ai_next) + { + if ((s = socket (res->ai_family, res->ai_socktype, res->ai_protocol)) < 0) + { + if (res->ai_next) + continue; + sys_error ("socket"); + freeaddrinfo (res0); + return -1; + } + if (connect (s, res->ai_addr, res->ai_addrlen) < 0) + { + if (res->ai_next) + { + close (s); + continue; + } + e = errno; + sys_error ("connect"); + close (s); + freeaddrinfo (res0); + errno = e; + return -1; + } + freeaddrinfo (res0); + break; + } + return s; +} +#endif /* HAVE_GETADDRINFO */ + +/* + * Open a TCP or UDP connection to HOST on port SERV. Uses getaddrinfo(3) + * if available, falling back to the traditional BSD mechanisms otherwise. + * Returns the connected socket or -1 on error. + */ +static int +_netopen(host, serv, typ) + char *host, *serv; + int typ; +{ +#ifdef HAVE_GETADDRINFO + return (_netopen6 (host, serv, typ)); +#else + return (_netopen4 (host, serv, typ)); +#endif +} + +/* + * Open a TCP or UDP connection given a path like `/dev/tcp/host/port' to + * host `host' on port `port' and return the connected socket. + */ +int +netopen (path) + char *path; +{ + char *np, *s, *t; + int fd; + + np = (char *)xmalloc (strlen (path) + 1); + strcpy (np, path); + + s = np + 9; + t = strchr (s, '/'); + if (t == 0) + { + internal_error (_("%s: bad network path specification"), path); + free (np); + return -1; + } + *t++ = '\0'; + fd = _netopen (s, t, path[5]); + free (np); + + return fd; +} + +#if 0 +/* + * Open a TCP connection to host `host' on the port defined for service + * `serv' and return the connected socket. + */ +int +tcpopen (host, serv) + char *host, *serv; +{ + return (_netopen (host, serv, 't')); +} + +/* + * Open a UDP connection to host `host' on the port defined for service + * `serv' and return the connected socket. + */ +int +udpopen (host, serv) + char *host, *serv; +{ + return _netopen (host, serv, 'u'); +} +#endif + +#else /* !HAVE_NETWORK */ + +int +netopen (path) + char *path; +{ + internal_error (_("network operations not supported")); + return -1; +} + +#endif /* !HAVE_NETWORK */ diff --git a/third_party/bash/ocache.h b/third_party/bash/ocache.h new file mode 100644 index 000000000..c596c2725 --- /dev/null +++ b/third_party/bash/ocache.h @@ -0,0 +1,133 @@ +/* ocache.h -- a minimal object caching implementation. */ + +/* Copyright (C) 2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_OCACHE_H_) +#define _OCACHE_H_ 1 + +#ifndef PTR_T + +#if defined (__STDC__) +# define PTR_T void * +#else +# define PTR_T char * +#endif + +#endif /* PTR_T */ + +#define OC_MEMSET(memp, xch, nbytes) \ +do { \ + if ((nbytes) <= 32) { \ + register char * mzp = (char *)(memp); \ + unsigned long mctmp = (nbytes); \ + register long mcn; \ + if (mctmp < 8) mcn = 0; else { mcn = (mctmp-1)/8; mctmp &= 7; } \ + switch (mctmp) { \ + case 0: for(;;) { *mzp++ = xch; \ + case 7: *mzp++ = xch; \ + case 6: *mzp++ = xch; \ + case 5: *mzp++ = xch; \ + case 4: *mzp++ = xch; \ + case 3: *mzp++ = xch; \ + case 2: *mzp++ = xch; \ + case 1: *mzp++ = xch; if(mcn <= 0) break; mcn--; } \ + } \ + } else \ + memset ((memp), (xch), (nbytes)); \ +} while(0) + +typedef struct objcache { + PTR_T data; + int cs; /* cache size, number of objects */ + int nc; /* number of cache entries */ +} sh_obj_cache_t; + +/* Create an object cache C of N pointers to OTYPE. */ +#define ocache_create(c, otype, n) \ + do { \ + (c).data = xmalloc((n) * sizeof (otype *)); \ + (c).cs = (n); \ + (c).nc = 0; \ + } while (0) + +/* Destroy an object cache C. */ +#define ocache_destroy(c) \ + do { \ + if ((c).data) \ + xfree ((c).data); \ + (c).data = 0; \ + (c).cs = (c).nc = 0; \ + } while (0) + +/* Free all cached items, which are pointers to OTYPE, in object cache C. */ +#define ocache_flush(c, otype) \ + do { \ + while ((c).nc > 0) \ + xfree (((otype **)((c).data))[--(c).nc]); \ + } while (0) + +/* + * Allocate a new item of type pointer to OTYPE, using data from object + * cache C if any cached items exist, otherwise calling xmalloc. Return + * the object in R. + */ +#define ocache_alloc(c, otype, r) \ + do { \ + if ((c).nc > 0) { \ + (r) = (otype *)((otype **)((c).data))[--(c).nc]; \ + } else \ + (r) = (otype *)xmalloc (sizeof (otype)); \ + } while (0) + +/* + * Free an item R of type pointer to OTYPE, adding to object cache C if + * there is room and calling xfree if the cache is full. If R is added + * to the object cache, the contents are scrambled. + */ +#define ocache_free(c, otype, r) \ + do { \ + if ((c).nc < (c).cs) { \ + OC_MEMSET ((r), 0xdf, sizeof(otype)); \ + ((otype **)((c).data))[(c).nc++] = (r); \ + } else \ + xfree (r); \ + } while (0) + +/* + * One may declare and use an object cache as (for instance): + * + * sh_obj_cache_t wdcache = {0, 0, 0}; + * sh_obj_cache_t wlcache = {0, 0, 0}; + * + * ocache_create(wdcache, WORD_DESC, 30); + * ocache_create(wlcache, WORD_LIST, 30); + * + * WORD_DESC *wd; + * ocache_alloc (wdcache, WORD_DESC, wd); + * + * WORD_LIST *wl; + * ocache_alloc (wlcache, WORD_LIST, wl); + * + * ocache_free(wdcache, WORD_DESC, wd); + * ocache_free(wlcache, WORD_LIST, wl); + * + * The use is almost arbitrary. + */ + +#endif /* _OCACHE_H */ diff --git a/third_party/bash/oslib.c b/third_party/bash/oslib.c new file mode 100644 index 000000000..ab284cb5f --- /dev/null +++ b/third_party/bash/oslib.c @@ -0,0 +1,159 @@ +/* oslib.c - functions present only in some unix versions. */ + +/* Copyright (C) 1995,2010 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#if defined (HAVE_SYS_PARAM_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_LIMITS_H) +# include +#endif + +#include "posixstat.h" +#include "filecntl.h" +#include "bashansi.h" + +#if !defined (HAVE_KILLPG) +# include +#endif + +#include +#include +#include "chartypes.h" + +#include "shell.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* + * Return the total number of available file descriptors. + * + * On some systems, like 4.2BSD and its descendants, there is a system call + * that returns the size of the descriptor table: getdtablesize(). There are + * lots of ways to emulate this on non-BSD systems. + * + * On System V.3, this can be obtained via a call to ulimit: + * return (ulimit(4, 0L)); + * + * On other System V systems, NOFILE is defined in /usr/include/sys/param.h + * (this is what we assume below), so we can simply use it: + * return (NOFILE); + * + * On POSIX systems, there are specific functions for retrieving various + * configuration parameters: + * return (sysconf(_SC_OPEN_MAX)); + * + */ + +#if !defined (HAVE_GETDTABLESIZE) +int +getdtablesize () +{ +# if defined (_POSIX_VERSION) && defined (HAVE_SYSCONF) && defined (_SC_OPEN_MAX) + return (sysconf(_SC_OPEN_MAX)); /* Posix systems use sysconf */ +# else /* ! (_POSIX_VERSION && HAVE_SYSCONF && _SC_OPEN_MAX) */ +# if defined (ULIMIT_MAXFDS) + return (ulimit (4, 0L)); /* System V.3 systems use ulimit(4, 0L) */ +# else /* !ULIMIT_MAXFDS */ +# if defined (NOFILE) /* Other systems use NOFILE */ + return (NOFILE); +# else /* !NOFILE */ + return (20); /* XXX - traditional value is 20 */ +# endif /* !NOFILE */ +# endif /* !ULIMIT_MAXFDS */ +# endif /* ! (_POSIX_VERSION && _SC_OPEN_MAX) */ +} +#endif /* !HAVE_GETDTABLESIZE */ + +#if !defined (HAVE_MKFIFO) && defined (PROCESS_SUBSTITUTION) +int +mkfifo (path, mode) + char *path; + mode_t mode; +{ +#if 0 && defined (S_IFIFO) // [jart] cosmo local change + return (mknod (path, (mode | S_IFIFO), 0)); +#else /* !S_IFIFO */ + return (-1); +#endif /* !S_IFIFO */ +} +#endif /* !HAVE_MKFIFO && PROCESS_SUBSTITUTION */ + +#define DEFAULT_MAXGROUPS 64 + +int +getmaxgroups () +{ + static int maxgroups = -1; + + if (maxgroups > 0) + return maxgroups; + +#if defined (HAVE_SYSCONF) && defined (_SC_NGROUPS_MAX) + maxgroups = sysconf (_SC_NGROUPS_MAX); +#else +# if defined (NGROUPS_MAX) + maxgroups = NGROUPS_MAX; +# else /* !NGROUPS_MAX */ +# if defined (NGROUPS) + maxgroups = NGROUPS; +# else /* !NGROUPS */ + maxgroups = DEFAULT_MAXGROUPS; +# endif /* !NGROUPS */ +# endif /* !NGROUPS_MAX */ +#endif /* !HAVE_SYSCONF || !SC_NGROUPS_MAX */ + + if (maxgroups <= 0) + maxgroups = DEFAULT_MAXGROUPS; + + return maxgroups; +} + +long +getmaxchild () +{ + static long maxchild = -1L; + + if (maxchild > 0) + return maxchild; + +#if defined (HAVE_SYSCONF) && defined (_SC_CHILD_MAX) + maxchild = sysconf (_SC_CHILD_MAX); +#else +# if defined (CHILD_MAX) + maxchild = CHILD_MAX; +# else +# if defined (MAXUPRC) + maxchild = MAXUPRC; +# endif /* MAXUPRC */ +# endif /* CHILD_MAX */ +#endif /* !HAVE_SYSCONF || !_SC_CHILD_MAX */ + + return (maxchild); +} diff --git a/third_party/bash/parser.h b/third_party/bash/parser.h new file mode 100644 index 000000000..59bf0fec4 --- /dev/null +++ b/third_party/bash/parser.h @@ -0,0 +1,102 @@ +/* parser.h -- Everything you wanted to know about the parser, but were + afraid to ask. */ + +/* Copyright (C) 1995-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_PARSER_H_) +# define _PARSER_H_ + +# include "command.h" +# include "input.h" + +/* Possible states for the parser that require it to do special things. */ +#define PST_CASEPAT 0x000001 /* in a case pattern list */ +#define PST_ALEXPNEXT 0x000002 /* expand next word for aliases */ +#define PST_ALLOWOPNBRC 0x000004 /* allow open brace for function def */ +#define PST_NEEDCLOSBRC 0x000008 /* need close brace */ +#define PST_DBLPAREN 0x000010 /* double-paren parsing - unused */ +#define PST_SUBSHELL 0x000020 /* ( ... ) subshell */ +#define PST_CMDSUBST 0x000040 /* $( ... ) command substitution */ +#define PST_CASESTMT 0x000080 /* parsing a case statement */ +#define PST_CONDCMD 0x000100 /* parsing a [[...]] command */ +#define PST_CONDEXPR 0x000200 /* parsing the guts of [[...]] */ +#define PST_ARITHFOR 0x000400 /* parsing an arithmetic for command - unused */ +#define PST_ALEXPAND 0x000800 /* OK to expand aliases - unused */ +#define PST_EXTPAT 0x001000 /* parsing an extended shell pattern */ +#define PST_COMPASSIGN 0x002000 /* parsing x=(...) compound assignment */ +#define PST_ASSIGNOK 0x004000 /* assignment statement ok in this context */ +#define PST_EOFTOKEN 0x008000 /* yylex checks against shell_eof_token */ +#define PST_REGEXP 0x010000 /* parsing an ERE/BRE as a single word */ +#define PST_HEREDOC 0x020000 /* reading body of here-document */ +#define PST_REPARSE 0x040000 /* re-parsing in parse_string_to_word_list */ +#define PST_REDIRLIST 0x080000 /* parsing a list of redirections preceding a simple command name */ +#define PST_COMMENT 0x100000 /* parsing a shell comment; used by aliases */ +#define PST_ENDALIAS 0x200000 /* just finished expanding and consuming an alias */ +#define PST_NOEXPAND 0x400000 /* don't expand anything in read_token_word; for command substitution */ +#define PST_NOERROR 0x800000 /* don't print error messages in yyerror */ + +/* Definition of the delimiter stack. Needed by parse.y and bashhist.c. */ +struct dstack { +/* DELIMITERS is a stack of the nested delimiters that we have + encountered so far. */ + char *delimiters; + +/* Offset into the stack of delimiters. */ + int delimiter_depth; + +/* How many slots are allocated to DELIMITERS. */ + int delimiter_space; +}; + +/* States we can be in while scanning a ${...} expansion. Shared between + parse.y and subst.c */ +#define DOLBRACE_PARAM 0x01 +#define DOLBRACE_OP 0x02 +#define DOLBRACE_WORD 0x04 + +#define DOLBRACE_QUOTE 0x40 /* single quote is special in double quotes */ +#define DOLBRACE_QUOTE2 0x80 /* single quote is semi-special in double quotes */ + +/* variable declarations from parse.y */ +extern struct dstack dstack; + +extern char *primary_prompt; +extern char *secondary_prompt; + +extern char *current_prompt_string; + +extern char *ps1_prompt; +extern char *ps2_prompt; +extern char *ps0_prompt; + +extern int expand_aliases; +extern int current_command_line_count; +extern int saved_command_line_count; +extern int shell_eof_token; +extern int current_token; +extern int parser_state; +extern int need_here_doc; + +extern int ignoreeof; +extern int eof_encountered; +extern int eof_encountered_limit; + +extern int line_number, line_number_base; + +#endif /* _PARSER_H_ */ diff --git a/third_party/bash/patchlevel.h b/third_party/bash/patchlevel.h new file mode 100644 index 000000000..165390c15 --- /dev/null +++ b/third_party/bash/patchlevel.h @@ -0,0 +1,30 @@ +/* patchlevel.h -- current bash patch level */ + +/* Copyright (C) 2001-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_PATCHLEVEL_H_) +#define _PATCHLEVEL_H_ + +/* It's important that there be no other strings in this file that match the + regexp `^#define[ ]*PATCHLEVEL', since that's what support/mkversion.sh + looks for to find the patch level (for the sccs version string). */ + +#define PATCHLEVEL 0 + +#endif /* _PATCHLEVEL_H_ */ diff --git a/third_party/bash/pathcanon.c b/third_party/bash/pathcanon.c new file mode 100644 index 000000000..41abbd26c --- /dev/null +++ b/third_party/bash/pathcanon.c @@ -0,0 +1,234 @@ +/* pathcanon.c -- canonicalize and manipulate pathnames. */ + +/* Copyright (C) 2000 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#if defined (HAVE_SYS_PARAM_H) +# include +#endif +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "filecntl.h" +#include "bashansi.h" +#include +#include "chartypes.h" +#include + +#include "shell.h" + +#if !defined (errno) +extern int errno; +#endif + +#if defined (__CYGWIN__) +#include + +static int +_is_cygdrive (path) + char *path; +{ + static char user[MAXPATHLEN]; + static char system[MAXPATHLEN]; + static int first_time = 1; + + /* If the path is the first part of a network path, treat it as + existing. */ + if (path[0] == '/' && path[1] == '/' && !strchr (path + 2, '/')) + return 1; + /* Otherwise check for /cygdrive prefix. */ + if (first_time) + { + char user_flags[MAXPATHLEN]; + char system_flags[MAXPATHLEN]; + /* Get the cygdrive info */ + cygwin_internal (CW_GET_CYGDRIVE_INFO, user, system, user_flags, system_flags); + first_time = 0; + } + return !strcasecmp (path, user) || !strcasecmp (path, system); +} +#endif /* __CYGWIN__ */ + +/* Return 1 if PATH corresponds to a directory. A function for debugging. */ +static int +_path_isdir (path) + char *path; +{ + int l; + struct stat sb; + + /* This should leave errno set to the correct value. */ + errno = 0; + l = stat (path, &sb) == 0 && S_ISDIR (sb.st_mode); +#if defined (__CYGWIN__) + if (l == 0) + l = _is_cygdrive (path); +#endif + return l; +} + +/* Canonicalize PATH, and return a new path. The new path differs from PATH + in that: + Multiple `/'s are collapsed to a single `/'. + Leading `./'s and trailing `/.'s are removed. + Trailing `/'s are removed. + Non-leading `../'s and trailing `..'s are handled by removing + portions of the path. */ + +/* Look for ROOTEDPATH, PATHSEP, DIRSEP, and ISDIRSEP in ../../general.h */ + +#define DOUBLE_SLASH(p) ((p[0] == '/') && (p[1] == '/') && p[2] != '/') + +char * +sh_canonpath (path, flags) + char *path; + int flags; +{ + char stub_char; + char *result, *p, *q, *base, *dotdot; + int rooted, double_slash_path; + + /* The result cannot be larger than the input PATH. */ + result = (flags & PATH_NOALLOC) ? path : savestring (path); + + /* POSIX.2 says to leave a leading `//' alone. On cygwin, we skip over any + leading `x:' (dos drive name). */ + if (rooted = ROOTEDPATH(path)) + { + stub_char = DIRSEP; +#if defined (__CYGWIN__) + base = (ISALPHA((unsigned char)result[0]) && result[1] == ':') ? result + 3 : result + 1; +#else + base = result + 1; +#endif + double_slash_path = DOUBLE_SLASH (path); + base += double_slash_path; + } + else + { + stub_char = '.'; +#if defined (__CYGWIN__) + base = (ISALPHA((unsigned char)result[0]) && result[1] == ':') ? result + 2 : result; +#else + base = result; +#endif + double_slash_path = 0; + } + + /* + * invariants: + * base points to the portion of the path we want to modify + * p points at beginning of path element we're considering. + * q points just past the last path element we wrote (no slash). + * dotdot points just past the point where .. cannot backtrack + * any further (no slash). + */ + p = q = dotdot = base; + + while (*p) + { + if (ISDIRSEP(p[0])) /* null element */ + p++; + else if(p[0] == '.' && PATHSEP(p[1])) /* . and ./ */ + p += 1; /* don't count the separator in case it is nul */ + else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */ + { + p += 2; /* skip `..' */ + if (q > dotdot) /* can backtrack */ + { + if (flags & PATH_CHECKDOTDOT) + { + char c; + + /* Make sure what we have so far corresponds to a valid + path before we chop some of it off. */ + c = *q; + *q = '\0'; + if (_path_isdir (result) == 0) + { + if ((flags & PATH_NOALLOC) == 0) + free (result); + return ((char *)NULL); + } + *q = c; + } + + while (--q > dotdot && ISDIRSEP(*q) == 0) + ; + } + else if (rooted == 0) + { + /* /.. is / but ./../ is .. */ + if (q != base) + *q++ = DIRSEP; + *q++ = '.'; + *q++ = '.'; + dotdot = q; + } + } + else /* real path element */ + { + /* add separator if not at start of work portion of result */ + if (q != base) + *q++ = DIRSEP; + while (*p && (ISDIRSEP(*p) == 0)) + *q++ = *p++; + /* Check here for a valid directory with _path_isdir. */ + if (flags & PATH_CHECKEXISTS) + { + char c; + + /* Make sure what we have so far corresponds to a valid + path before we chop some of it off. */ + c = *q; + *q = '\0'; + if (_path_isdir (result) == 0) + { + if ((flags & PATH_NOALLOC) == 0) + free (result); + return ((char *)NULL); + } + *q = c; + } + } + } + + /* Empty string is really ``.'' or `/', depending on what we started with. */ + if (q == result) + *q++ = stub_char; + *q = '\0'; + + /* If the result starts with `//', but the original path does not, we + can turn the // into /. Because of how we set `base', this should never + be true, but it's a sanity check. */ + if (DOUBLE_SLASH(result) && double_slash_path == 0) + { + if (result[2] == '\0') /* short-circuit for bare `//' */ + result[1] = '\0'; + else + memmove (result, result + 1, strlen (result + 1) + 1); + } + + return (result); +} diff --git a/third_party/bash/pathexp.c b/third_party/bash/pathexp.c new file mode 100644 index 000000000..9ba07d974 --- /dev/null +++ b/third_party/bash/pathexp.c @@ -0,0 +1,637 @@ +/* pathexp.c -- The shell interface to the globbing library. */ + +/* Copyright (C) 1995-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" + +#include "shell.h" +#include "pathexp.h" +#include "flags.h" + +#include "shmbutil.h" +#include "bashintl.h" + +#include "strmatch.h" + +static int glob_name_is_acceptable PARAMS((const char *)); +static void ignore_globbed_names PARAMS((char **, sh_ignore_func_t *)); +static char *split_ignorespec PARAMS((char *, int *)); + +#include "glob.h" + +/* Control whether * matches .files in globbing. */ +int glob_dot_filenames; + +/* Control whether the extended globbing features are enabled. */ +int extended_glob = EXTGLOB_DEFAULT; + +/* Control enabling special handling of `**' */ +int glob_star = 0; + +/* Return nonzero if STRING has any unquoted special globbing chars in it. + This is supposed to be called when pathname expansion is performed, so + it implements the rules in Posix 2.13.3, specifically that an unquoted + slash cannot appear in a bracket expression. */ +int +unquoted_glob_pattern_p (string) + register char *string; +{ + register int c; + char *send; + int open, bsquote; + + DECLARE_MBSTATE; + + open = bsquote = 0; + send = string + strlen (string); + + while (c = *string++) + { + switch (c) + { + case '?': + case '*': + return (1); + + case '[': + open++; + continue; + + case ']': + if (open) /* XXX - if --open == 0? */ + return (1); + continue; + + case '/': + if (open) + open = 0; + + case '+': + case '@': + case '!': + if (*string == '(') /*)*/ + return (1); + continue; + + /* A pattern can't end with a backslash, but a backslash in the pattern + can be special to the matching engine, so we note it in case we + need it later. */ + case '\\': + if (*string != '\0' && *string != '/') + { + bsquote = 1; + string++; + continue; + } + else if (open && *string == '/') + { + string++; /* quoted slashes in bracket expressions are ok */ + continue; + } + else if (*string == 0) + return (0); + + case CTLESC: + if (*string++ == '\0') + return (0); + } + + /* Advance one fewer byte than an entire multibyte character to + account for the auto-increment in the loop above. */ +#ifdef HANDLE_MULTIBYTE + string--; + ADVANCE_CHAR_P (string, send - string); + string++; +#else + ADVANCE_CHAR_P (string, send - string); +#endif + } + +#if 0 + return (bsquote ? 2 : 0); +#else + return (0); +#endif +} + +/* Return 1 if C is a character that is `special' in a POSIX ERE and needs to + be quoted to match itself. */ +static inline int +ere_char (c) + int c; +{ + switch (c) + { + case '.': + case '[': + case '\\': + case '(': + case ')': + case '*': + case '+': + case '?': + case '{': + case '|': + case '^': + case '$': + return 1; + default: + return 0; + } + return (0); +} + +/* This is only used to determine whether to backslash-quote a character. */ +int +glob_char_p (s) + const char *s; +{ + switch (*s) + { + case '*': + case '[': + case ']': + case '?': + case '\\': + return 1; + case '+': + case '@': + case '!': + if (s[1] == '(') /*(*/ + return 1; + break; + } + return 0; +} + +/* PATHNAME can contain characters prefixed by CTLESC; this indicates + that the character is to be quoted. We quote it here in the style + that the glob library recognizes. If flags includes QGLOB_CVTNULL, + we change quoted null strings (pathname[0] == CTLNUL) into empty + strings (pathname[0] == 0). If this is called after quote removal + is performed, (flags & QGLOB_CVTNULL) should be 0; if called when quote + removal has not been done (for example, before attempting to match a + pattern while executing a case statement), flags should include + QGLOB_CVTNULL. If flags includes QGLOB_CTLESC, we need to remove CTLESC + quoting CTLESC or CTLNUL (as if dequote_string were called). If flags + includes QGLOB_FILENAME, appropriate quoting to match a filename should be + performed. QGLOB_REGEXP means we're quoting for a Posix ERE (for + [[ string =~ pat ]]) and that requires some special handling. */ +char * +quote_string_for_globbing (pathname, qflags) + const char *pathname; + int qflags; +{ + char *temp; + register int i, j; + int cclass, collsym, equiv, c, last_was_backslash; + int savei, savej; + + temp = (char *)xmalloc (2 * strlen (pathname) + 1); + + if ((qflags & QGLOB_CVTNULL) && QUOTED_NULL (pathname)) + { + temp[0] = '\0'; + return temp; + } + + cclass = collsym = equiv = last_was_backslash = 0; + for (i = j = 0; pathname[i]; i++) + { + /* Fix for CTLESC at the end of the string? */ + if (pathname[i] == CTLESC && pathname[i+1] == '\0') + { + temp[j++] = pathname[i++]; + break; + } + /* If we are parsing regexp, turn CTLESC CTLESC into CTLESC. It's not an + ERE special character, so we should just be able to pass it through. */ + else if ((qflags & (QGLOB_REGEXP|QGLOB_CTLESC)) && pathname[i] == CTLESC && (pathname[i+1] == CTLESC || pathname[i+1] == CTLNUL)) + { + i++; + temp[j++] = pathname[i]; + continue; + } + else if (pathname[i] == CTLESC) + { +convert_to_backslash: + if ((qflags & QGLOB_FILENAME) && pathname[i+1] == '/') + continue; + /* What to do if preceding char is backslash? */ + if (pathname[i+1] != CTLESC && (qflags & QGLOB_REGEXP) && ere_char (pathname[i+1]) == 0) + continue; + temp[j++] = '\\'; + i++; + if (pathname[i] == '\0') + break; + } + else if ((qflags & QGLOB_REGEXP) && (i == 0 || pathname[i-1] != CTLESC) && pathname[i] == '[') /*]*/ + { + temp[j++] = pathname[i++]; /* open bracket */ + savej = j; + savei = i; + c = pathname[i++]; /* c == char after open bracket */ + if (c == '^') /* ignore pattern negation */ + { + temp[j++] = c; + c = pathname[i++]; + } + if (c == ']') /* ignore right bracket if first char */ + { + temp[j++] = c; + c = pathname[i++]; + } + do + { + if (c == 0) + goto endpat; + else if (c == CTLESC) + { + /* skip c, check for EOS, let assignment at end of loop */ + /* pathname[i] == backslash-escaped character */ + if (pathname[i] == 0) + goto endpat; + temp[j++] = pathname[i++]; + } + else if (c == '[' && pathname[i] == ':') + { + temp[j++] = c; + temp[j++] = pathname[i++]; + cclass = 1; + } + else if (cclass && c == ':' && pathname[i] == ']') + { + temp[j++] = c; + temp[j++] = pathname[i++]; + cclass = 0; + } + else if (c == '[' && pathname[i] == '=') + { + temp[j++] = c; + temp[j++] = pathname[i++]; + if (pathname[i] == ']') + temp[j++] = pathname[i++]; /* right brack can be in equiv */ + equiv = 1; + } + else if (equiv && c == '=' && pathname[i] == ']') + { + temp[j++] = c; + temp[j++] = pathname[i++]; + equiv = 0; + } + else if (c == '[' && pathname[i] == '.') + { + temp[j++] = c; + temp[j++] = pathname[i++]; + if (pathname[i] == ']') + temp[j++] = pathname[i++]; /* right brack can be in collsym */ + collsym = 1; + } + else if (collsym && c == '.' && pathname[i] == ']') + { + temp[j++] = c; + temp[j++] = pathname[i++]; + collsym = 0; + } + else + temp[j++] = c; + } + while (((c = pathname[i++]) != ']') && c != 0); + + /* If we don't find the closing bracket before we hit the end of + the string, rescan string without treating it as a bracket + expression (has implications for backslash and special ERE + chars) */ + if (c == 0) + { + i = savei - 1; /* -1 for autoincrement above */ + j = savej; + continue; + } + + temp[j++] = c; /* closing right bracket */ + i--; /* increment will happen above in loop */ + continue; /* skip double assignment below */ + } + else if (pathname[i] == '\\' && (qflags & QGLOB_REGEXP) == 0) + { + /* XXX - if not quoting regexp, use backslash as quote char. Should + We just pass it through without treating it as special? That is + what ksh93 seems to do. */ + + /* If we want to pass through backslash unaltered, comment out these + lines. */ + temp[j++] = '\\'; + + i++; + if (pathname[i] == '\0') + break; + /* If we are turning CTLESC CTLESC into CTLESC, we need to do that + even when the first CTLESC is preceded by a backslash. */ + if ((qflags & QGLOB_CTLESC) && pathname[i] == CTLESC && (pathname[i+1] == CTLESC || pathname[i+1] == CTLNUL)) + i++; /* skip over the CTLESC */ + else if ((qflags & QGLOB_CTLESC) && pathname[i] == CTLESC) + /* A little more general: if there is an unquoted backslash in the + pattern and we are handling quoted characters in the pattern, + convert the CTLESC to backslash and add the next character on + the theory that the backslash will quote the next character + but it would be inconsistent not to replace the CTLESC with + another backslash here. We can't tell at this point whether the + CTLESC comes from a backslash or other form of quoting in the + original pattern. */ + goto convert_to_backslash; + } + else if (pathname[i] == '\\' && (qflags & QGLOB_REGEXP)) + last_was_backslash = 1; + temp[j++] = pathname[i]; + } +endpat: + temp[j] = '\0'; + + return (temp); +} + +char * +quote_globbing_chars (string) + const char *string; +{ + size_t slen; + char *temp, *t; + const char *s, *send; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + temp = (char *)xmalloc (slen * 2 + 1); + for (t = temp, s = string; *s; ) + { + if (glob_char_p (s)) + *t++ = '\\'; + + /* Copy a single (possibly multibyte) character from s to t, + incrementing both. */ + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + return temp; +} + +/* Call the glob library to do globbing on PATHNAME. */ +char ** +shell_glob_filename (pathname, qflags) + const char *pathname; + int qflags; +{ + char *temp, **results; + int gflags, quoted_pattern; + + noglob_dot_filenames = glob_dot_filenames == 0; + + temp = quote_string_for_globbing (pathname, QGLOB_FILENAME|qflags); + gflags = glob_star ? GX_GLOBSTAR : 0; + results = glob_filename (temp, gflags); + free (temp); + + if (results && ((GLOB_FAILED (results)) == 0)) + { + if (should_ignore_glob_matches ()) + ignore_glob_matches (results); + if (results && results[0]) + strvec_sort (results, 1); /* posix sort */ + else + { + FREE (results); + results = (char **)&glob_error_return; + } + } + + return (results); +} + +/* Stuff for GLOBIGNORE. */ + +static struct ignorevar globignore = +{ + "GLOBIGNORE", + (struct ign *)0, + 0, + (char *)0, + (sh_iv_item_func_t *)0, +}; + +/* Set up to ignore some glob matches because the value of GLOBIGNORE + has changed. If GLOBIGNORE is being unset, we also need to disable + the globbing of filenames beginning with a `.'. */ +void +setup_glob_ignore (name) + char *name; +{ + char *v; + + v = get_string_value (name); + setup_ignore_patterns (&globignore); + + if (globignore.num_ignores) + glob_dot_filenames = 1; + else if (v == 0) + glob_dot_filenames = 0; +} + +int +should_ignore_glob_matches () +{ + return globignore.num_ignores; +} + +/* Return 0 if NAME matches a pattern in the globignore.ignores list. */ +static int +glob_name_is_acceptable (name) + const char *name; +{ + struct ign *p; + char *n; + int flags; + + /* . and .. are never matched. We extend this to the terminal component of a + pathname. */ + n = strrchr (name, '/'); + if (n == 0 || n[1] == 0) + n = (char *)name; + else + n++; + + if (n[0] == '.' && (n[1] == '\0' || (n[1] == '.' && n[2] == '\0'))) + return (0); + + flags = FNM_PATHNAME | FNMATCH_EXTFLAG | FNMATCH_NOCASEGLOB; + for (p = globignore.ignores; p->val; p++) + { + if (strmatch (p->val, (char *)name, flags) != FNM_NOMATCH) + return (0); + } + return (1); +} + +/* Internal function to test whether filenames in NAMES should be + ignored. NAME_FUNC is a pointer to a function to call with each + name. It returns non-zero if the name is acceptable to the particular + ignore function which called _ignore_names; zero if the name should + be removed from NAMES. */ + +static void +ignore_globbed_names (names, name_func) + char **names; + sh_ignore_func_t *name_func; +{ + char **newnames; + int n, i; + + for (i = 0; names[i]; i++) + ; + newnames = strvec_create (i + 1); + + for (n = i = 0; names[i]; i++) + { + if ((*name_func) (names[i])) + newnames[n++] = names[i]; + else + free (names[i]); + } + + newnames[n] = (char *)NULL; + + if (n == 0) + { + names[0] = (char *)NULL; + free (newnames); + return; + } + + /* Copy the acceptable names from NEWNAMES back to NAMES and set the + new array end. */ + for (n = 0; newnames[n]; n++) + names[n] = newnames[n]; + names[n] = (char *)NULL; + free (newnames); +} + +void +ignore_glob_matches (names) + char **names; +{ + if (globignore.num_ignores == 0) + return; + + ignore_globbed_names (names, glob_name_is_acceptable); +} + +static char * +split_ignorespec (s, ip) + char *s; + int *ip; +{ + char *t; + int n, i; + + if (s == 0) + return 0; + + i = *ip; + if (s[i] == 0) + return 0; + + n = skip_to_delim (s, i, ":", SD_NOJMP|SD_EXTGLOB|SD_GLOB); + t = substring (s, i, n); + + if (s[n] == ':') + n++; + *ip = n; + return t; +} + +void +setup_ignore_patterns (ivp) + struct ignorevar *ivp; +{ + int numitems, maxitems, ptr; + char *colon_bit, *this_ignoreval; + struct ign *p; + + this_ignoreval = get_string_value (ivp->varname); + + /* If nothing has changed then just exit now. */ + if ((this_ignoreval && ivp->last_ignoreval && STREQ (this_ignoreval, ivp->last_ignoreval)) || + (!this_ignoreval && !ivp->last_ignoreval)) + return; + + /* Oops. The ignore variable has changed. Re-parse it. */ + ivp->num_ignores = 0; + + if (ivp->ignores) + { + for (p = ivp->ignores; p->val; p++) + free(p->val); + free (ivp->ignores); + ivp->ignores = (struct ign *)NULL; + } + + if (ivp->last_ignoreval) + { + free (ivp->last_ignoreval); + ivp->last_ignoreval = (char *)NULL; + } + + if (this_ignoreval == 0 || *this_ignoreval == '\0') + return; + + ivp->last_ignoreval = savestring (this_ignoreval); + + numitems = maxitems = ptr = 0; + +#if 0 + while (colon_bit = extract_colon_unit (this_ignoreval, &ptr)) +#else + while (colon_bit = split_ignorespec (this_ignoreval, &ptr)) +#endif + { + if (numitems + 1 >= maxitems) + { + maxitems += 10; + ivp->ignores = (struct ign *)xrealloc (ivp->ignores, maxitems * sizeof (struct ign)); + } + ivp->ignores[numitems].val = colon_bit; + ivp->ignores[numitems].len = strlen (colon_bit); + ivp->ignores[numitems].flags = 0; + if (ivp->item_func) + (*ivp->item_func) (&ivp->ignores[numitems]); + numitems++; + } + ivp->ignores[numitems].val = (char *)NULL; + ivp->num_ignores = numitems; +} diff --git a/third_party/bash/pathexp.h b/third_party/bash/pathexp.h new file mode 100644 index 000000000..b96f92af4 --- /dev/null +++ b/third_party/bash/pathexp.h @@ -0,0 +1,104 @@ +/* pathexp.h -- The shell interface to the globbing library. */ + +/* Copyright (C) 1987-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_PATHEXP_H_) +#define _PATHEXP_H_ + +#define GLOB_FAILED(glist) (glist) == (char **)&glob_error_return + +extern int noglob_dot_filenames; +extern char *glob_error_return; + +/* Flag values for quote_string_for_globbing */ +#define QGLOB_CVTNULL 0x01 /* convert QUOTED_NULL strings to '\0' */ +#define QGLOB_FILENAME 0x02 /* do correct quoting for matching filenames */ +#define QGLOB_REGEXP 0x04 /* quote an ERE for regcomp/regexec */ +#define QGLOB_CTLESC 0x08 /* turn CTLESC CTLESC into CTLESC for BREs */ +#define QGLOB_DEQUOTE 0x10 /* like dequote_string but quote glob chars */ + +#if defined (EXTENDED_GLOB) +/* Flags to OR with other flag args to strmatch() to enabled the extended + pattern matching. */ +# define FNMATCH_EXTFLAG (extended_glob ? FNM_EXTMATCH : 0) +#else +# define FNMATCH_EXTFLAG 0 +#endif /* !EXTENDED_GLOB */ + +#define FNMATCH_IGNCASE (match_ignore_case ? FNM_CASEFOLD : 0) +#define FNMATCH_NOCASEGLOB (glob_ignore_case ? FNM_CASEFOLD : 0) + +extern int glob_dot_filenames; +extern int extended_glob; +extern int glob_star; +extern int match_ignore_case; /* doesn't really belong here */ + +extern int unquoted_glob_pattern_p PARAMS((char *)); + +/* PATHNAME can contain characters prefixed by CTLESC; this indicates + that the character is to be quoted. We quote it here in the style + that the glob library recognizes. If flags includes QGLOB_CVTNULL, + we change quoted null strings (pathname[0] == CTLNUL) into empty + strings (pathname[0] == 0). If this is called after quote removal + is performed, (flags & QGLOB_CVTNULL) should be 0; if called when quote + removal has not been done (for example, before attempting to match a + pattern while executing a case statement), flags should include + QGLOB_CVTNULL. If flags includes QGLOB_FILENAME, appropriate quoting + to match a filename should be performed. */ +extern char *quote_string_for_globbing PARAMS((const char *, int)); + +extern int glob_char_p PARAMS((const char *)); +extern char *quote_globbing_chars PARAMS((const char *)); + +/* Call the glob library to do globbing on PATHNAME. FLAGS is additional + flags to pass to QUOTE_STRING_FOR_GLOBBING, mostly having to do with + whether or not we've already performed quote removal. */ +extern char **shell_glob_filename PARAMS((const char *, int)); + +/* Filename completion ignore. Used to implement the "fignore" facility of + tcsh, GLOBIGNORE (like ksh-93 FIGNORE), and EXECIGNORE. + + It is passed a NULL-terminated array of (char *)'s that must be + free()'d if they are deleted. The first element (names[0]) is the + least-common-denominator string of the matching patterns (i.e. + u produces names[0] = "und", names[1] = "under.c", names[2] = + "undun.c", name[3] = NULL). */ + +struct ign { + char *val; + int len, flags; +}; + +typedef int sh_iv_item_func_t PARAMS((struct ign *)); + +struct ignorevar { + char *varname; /* FIGNORE, GLOBIGNORE, or EXECIGNORE */ + struct ign *ignores; /* Store the ignore strings here */ + int num_ignores; /* How many are there? */ + char *last_ignoreval; /* Last value of variable - cached for speed */ + sh_iv_item_func_t *item_func; /* Called when each item is parsed from $`varname' */ +}; + +extern void setup_ignore_patterns PARAMS((struct ignorevar *)); + +extern void setup_glob_ignore PARAMS((char *)); +extern int should_ignore_glob_matches PARAMS((void)); +extern void ignore_glob_matches PARAMS((char **)); + +#endif diff --git a/third_party/bash/pathnames.h b/third_party/bash/pathnames.h new file mode 100644 index 000000000..f506f3cb4 --- /dev/null +++ b/third_party/bash/pathnames.h @@ -0,0 +1,33 @@ +/* pathnames.h -- absolute filenames that bash wants for various defaults. */ + +/* Copyright (C) 1987-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_PATHNAMES_H_) +#define _PATHNAMES_H_ + +/* The default file for hostname completion. */ +#define DEFAULT_HOSTS_FILE "/etc/hosts" + +/* The default login shell startup file. */ +#define SYS_PROFILE "/etc/profile" + +/* The default location of the bash debugger initialization/startup file. */ +#define DEBUGGER_START_FILE "/zip/usr/share/bashdb/bashdb-main.inc" + +#endif /* _PATHNAMES_H */ diff --git a/third_party/bash/pathphys.c b/third_party/bash/pathphys.c new file mode 100644 index 000000000..2b6211d6a --- /dev/null +++ b/third_party/bash/pathphys.c @@ -0,0 +1,296 @@ +/* pathphys.c -- return pathname with all symlinks expanded. */ + +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#if defined (HAVE_SYS_PARAM_H) +# include +#endif +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "filecntl.h" +#include "bashansi.h" +#include +#include "chartypes.h" +#include + +#include "shell.h" + +#if !defined (MAXSYMLINKS) +# define MAXSYMLINKS 32 +#endif + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern char *get_working_directory PARAMS((char *)); + +static int +_path_readlink (path, buf, bufsiz) + char *path; + char *buf; + int bufsiz; +{ +#ifdef HAVE_READLINK + return readlink (path, buf, bufsiz); +#else + errno = EINVAL; + return -1; +#endif +} + +/* Look for ROOTEDPATH, PATHSEP, DIRSEP, and ISDIRSEP in ../../general.h */ + +#define DOUBLE_SLASH(p) ((p[0] == '/') && (p[1] == '/') && p[2] != '/') + +/* + * Return PATH with all symlinks expanded in newly-allocated memory. + * This always gets an absolute pathname. + */ + +char * +sh_physpath (path, flags) + char *path; + int flags; +{ + char tbuf[PATH_MAX+1], linkbuf[PATH_MAX+1]; + char *result, *p, *q, *qsave, *qbase, *workpath; + int double_slash_path, linklen, nlink; + + linklen = strlen (path); + +#if 0 + /* First sanity check -- punt immediately if the name is too long. */ + if (linklen >= PATH_MAX) + return (savestring (path)); +#endif + + nlink = 0; + q = result = (char *)xmalloc (PATH_MAX + 1); + + /* Even if we get something longer than PATH_MAX, we might be able to + shorten it, so we try. */ + if (linklen >= PATH_MAX) + workpath = savestring (path); + else + { + workpath = (char *)xmalloc (PATH_MAX + 1); + strcpy (workpath, path); + } + + /* This always gets an absolute pathname. */ + + /* POSIX.2 says to leave a leading `//' alone. On cygwin, we skip over any + leading `x:' (dos drive name). */ +#if defined (__CYGWIN__) + qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1; +#else + qbase = workpath + 1; +#endif + double_slash_path = DOUBLE_SLASH (workpath); + qbase += double_slash_path; + + for (p = workpath; p < qbase; ) + *q++ = *p++; + qbase = q; + + /* + * invariants: + * qbase points to the portion of the result path we want to modify + * p points at beginning of path element we're considering. + * q points just past the last path element we wrote (no slash). + * + * XXX -- need to fix error checking for too-long pathnames + */ + + while (*p) + { + if (ISDIRSEP(p[0])) /* null element */ + p++; + else if(p[0] == '.' && PATHSEP(p[1])) /* . and ./ */ + p += 1; /* don't count the separator in case it is nul */ + else if (p[0] == '.' && p[1] == '.' && PATHSEP(p[2])) /* .. and ../ */ + { + p += 2; /* skip `..' */ + if (q > qbase) + { + while (--q > qbase && ISDIRSEP(*q) == 0) + ; + } + } + else /* real path element */ + { + /* add separator if not at start of work portion of result */ + qsave = q; + if (q != qbase) + *q++ = DIRSEP; + while (*p && (ISDIRSEP(*p) == 0)) + { + if (q - result >= PATH_MAX) + { +#ifdef ENAMETOOLONG + errno = ENAMETOOLONG; +#else + errno = EINVAL; +#endif + goto error; + } + + *q++ = *p++; + } + + *q = '\0'; + + linklen = _path_readlink (result, linkbuf, PATH_MAX); + if (linklen < 0) /* if errno == EINVAL, it's not a symlink */ + { + if (errno != EINVAL) + goto error; + continue; + } + + /* It's a symlink, and the value is in LINKBUF. */ + nlink++; + if (nlink > MAXSYMLINKS) + { +#ifdef ELOOP + errno = ELOOP; +#else + errno = EINVAL; +#endif +error: + free (result); + free (workpath); + return ((char *)NULL); + } + + linkbuf[linklen] = '\0'; + + /* If the new path length would overrun PATH_MAX, punt now. */ + if ((strlen (p) + linklen + 2) >= PATH_MAX) + { +#ifdef ENAMETOOLONG + errno = ENAMETOOLONG; +#else + errno = EINVAL; +#endif + goto error; + } + + /* Form the new pathname by copying the link value to a temporary + buffer and appending the rest of `workpath'. Reset p to point + to the start of the rest of the path. If the link value is an + absolute pathname, reset p, q, and qbase. If not, reset p + and q. */ + strcpy (tbuf, linkbuf); + tbuf[linklen] = '/'; + strcpy (tbuf + linklen, p); + strcpy (workpath, tbuf); + + if (ABSPATH(linkbuf)) + { + q = result; + /* Duplicating some code here... */ +#if defined (__CYGWIN__) + qbase = (ISALPHA((unsigned char)workpath[0]) && workpath[1] == ':') ? workpath + 3 : workpath + 1; +#else + qbase = workpath + 1; +#endif + double_slash_path = DOUBLE_SLASH (workpath); + qbase += double_slash_path; + + for (p = workpath; p < qbase; ) + *q++ = *p++; + qbase = q; + } + else + { + p = workpath; + q = qsave; + } + } + } + + *q = '\0'; + free (workpath); + + /* If the result starts with `//', but the original path does not, we + can turn the // into /. Because of how we set `qbase', this should never + be true, but it's a sanity check. */ + if (DOUBLE_SLASH(result) && double_slash_path == 0) + { + if (result[2] == '\0') /* short-circuit for bare `//' */ + result[1] = '\0'; + else + memmove (result, result + 1, strlen (result + 1) + 1); + } + + return (result); +} + +char * +sh_realpath (pathname, resolved) + const char *pathname; + char *resolved; +{ + char *tdir, *wd; + + if (pathname == 0 || *pathname == '\0') + { + errno = (pathname == 0) ? EINVAL : ENOENT; + return ((char *)NULL); + } + + if (ABSPATH (pathname) == 0) + { + wd = get_working_directory ("sh_realpath"); + if (wd == 0) + return ((char *)NULL); + tdir = sh_makepath (wd, (char *)pathname, 0); + free (wd); + } + else + tdir = savestring (pathname); + + wd = sh_physpath (tdir, 0); + free (tdir); + + if (resolved == 0) + return (wd); + + if (wd) + { + strncpy (resolved, wd, PATH_MAX - 1); + resolved[PATH_MAX - 1] = '\0'; + free (wd); + return resolved; + } + else + { + resolved[0] = '\0'; + return wd; + } +} diff --git a/third_party/bash/pcomplete.c b/third_party/bash/pcomplete.c new file mode 100644 index 000000000..e78b929be --- /dev/null +++ b/third_party/bash/pcomplete.c @@ -0,0 +1,1757 @@ +/* pcomplete.c - functions to generate lists of matches for programmable completion. */ + +/* Copyright (C) 1999-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (PROGRAMMABLE_COMPLETION) + +#include "bashtypes.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#if defined (PREFER_STDARG) +# include +#else +# include +#endif + +#include "posixtime.h" + +#include +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "pcomplete.h" +#include "alias.h" +#include "bashline.h" +#include "execute_cmd.h" +#include "pathexp.h" + +#if defined (JOB_CONTROL) +# include "jobs.h" +#endif + +#if !defined (NSIG) +# include "trap.h" +#endif + +#include "shmbutil.h" + +#include "builtins.h" +#include "common.h" +#include "builtext.h" + +#include "glob.h" +#include "strmatch.h" + +#include "third_party/readline/rlconf.h" +#include "third_party/readline/readline.h" +#include "third_party/readline/history.h" + +#ifdef STRDUP +# undef STRDUP +#endif +#define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) + +typedef SHELL_VAR **SVFUNC (); + +#ifndef HAVE_STRPBRK +extern char *strpbrk PARAMS((char *, char *)); +#endif + +extern STRING_INT_ALIST word_token_alist[]; +extern char *signal_names[]; + +#if defined (DEBUG) +#if defined (PREFER_STDARG) +static void debug_printf (const char *, ...) __attribute__((__format__ (printf, 1, 2))); +#endif +#endif /* DEBUG */ + +static int it_init_joblist PARAMS((ITEMLIST *, int)); + +static int it_init_aliases PARAMS((ITEMLIST *)); +static int it_init_arrayvars PARAMS((ITEMLIST *)); +static int it_init_bindings PARAMS((ITEMLIST *)); +static int it_init_builtins PARAMS((ITEMLIST *)); +static int it_init_disabled PARAMS((ITEMLIST *)); +static int it_init_enabled PARAMS((ITEMLIST *)); +static int it_init_exported PARAMS((ITEMLIST *)); +static int it_init_functions PARAMS((ITEMLIST *)); +static int it_init_helptopics PARAMS((ITEMLIST *)); +static int it_init_hostnames PARAMS((ITEMLIST *)); +static int it_init_jobs PARAMS((ITEMLIST *)); +static int it_init_running PARAMS((ITEMLIST *)); +static int it_init_stopped PARAMS((ITEMLIST *)); +static int it_init_keywords PARAMS((ITEMLIST *)); +static int it_init_signals PARAMS((ITEMLIST *)); +static int it_init_variables PARAMS((ITEMLIST *)); +static int it_init_setopts PARAMS((ITEMLIST *)); +static int it_init_shopts PARAMS((ITEMLIST *)); + +static int shouldexp_filterpat PARAMS((char *)); +static char *preproc_filterpat PARAMS((char *, const char *)); + +static void init_itemlist_from_varlist PARAMS((ITEMLIST *, SVFUNC *)); + +static STRINGLIST *gen_matches_from_itemlist PARAMS((ITEMLIST *, const char *)); +static STRINGLIST *gen_action_completions PARAMS((COMPSPEC *, const char *)); +static STRINGLIST *gen_globpat_matches PARAMS((COMPSPEC *, const char *)); +static STRINGLIST *gen_wordlist_matches PARAMS((COMPSPEC *, const char *)); +static STRINGLIST *gen_shell_function_matches PARAMS((COMPSPEC *, const char *, + const char *, + char *, int, WORD_LIST *, + int, int, int *)); +static STRINGLIST *gen_command_matches PARAMS((COMPSPEC *, const char *, + const char *, + char *, int, WORD_LIST *, + int, int)); + +static STRINGLIST *gen_progcomp_completions PARAMS((const char *, const char *, + const char *, + int, int, int *, int *, + COMPSPEC **)); + +static char *pcomp_filename_completion_function PARAMS((const char *, int)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *bind_comp_words PARAMS((WORD_LIST *)); +#endif +static void bind_compfunc_variables PARAMS((char *, int, WORD_LIST *, int, int)); +static void unbind_compfunc_variables PARAMS((int)); +static WORD_LIST *build_arg_list PARAMS((char *, const char *, const char *, WORD_LIST *, int)); +static WORD_LIST *command_line_to_word_list PARAMS((char *, int, int, int *, int *)); + +#ifdef DEBUG +static int progcomp_debug = 0; +#endif + +int prog_completion_enabled = 1; + +#ifdef ALIAS +int progcomp_alias = 0; /* unavailable to user code for now */ +#endif + +/* These are used to manage the arrays of strings for possible completions. */ +ITEMLIST it_aliases = { 0, it_init_aliases, (STRINGLIST *)0 }; +ITEMLIST it_arrayvars = { LIST_DYNAMIC, it_init_arrayvars, (STRINGLIST *)0 }; +ITEMLIST it_bindings = { 0, it_init_bindings, (STRINGLIST *)0 }; +ITEMLIST it_builtins = { 0, it_init_builtins, (STRINGLIST *)0 }; +ITEMLIST it_commands = { LIST_DYNAMIC }; /* unused */ +ITEMLIST it_directories = { LIST_DYNAMIC }; /* unused */ +ITEMLIST it_disabled = { 0, it_init_disabled, (STRINGLIST *)0 }; +ITEMLIST it_enabled = { 0, it_init_enabled, (STRINGLIST *)0 }; +ITEMLIST it_exports = { LIST_DYNAMIC, it_init_exported, (STRINGLIST *)0 }; +ITEMLIST it_files = { LIST_DYNAMIC }; /* unused */ +ITEMLIST it_functions = { 0, it_init_functions, (STRINGLIST *)0 }; +ITEMLIST it_helptopics = { 0, it_init_helptopics, (STRINGLIST *)0 }; +ITEMLIST it_hostnames = { LIST_DYNAMIC, it_init_hostnames, (STRINGLIST *)0 }; +ITEMLIST it_groups = { LIST_DYNAMIC }; /* unused */ +ITEMLIST it_jobs = { LIST_DYNAMIC, it_init_jobs, (STRINGLIST *)0 }; +ITEMLIST it_keywords = { 0, it_init_keywords, (STRINGLIST *)0 }; +ITEMLIST it_running = { LIST_DYNAMIC, it_init_running, (STRINGLIST *)0 }; +ITEMLIST it_services = { LIST_DYNAMIC }; /* unused */ +ITEMLIST it_setopts = { 0, it_init_setopts, (STRINGLIST *)0 }; +ITEMLIST it_shopts = { 0, it_init_shopts, (STRINGLIST *)0 }; +ITEMLIST it_signals = { 0, it_init_signals, (STRINGLIST *)0 }; +ITEMLIST it_stopped = { LIST_DYNAMIC, it_init_stopped, (STRINGLIST *)0 }; +ITEMLIST it_users = { LIST_DYNAMIC }; /* unused */ +ITEMLIST it_variables = { LIST_DYNAMIC, it_init_variables, (STRINGLIST *)0 }; + +COMPSPEC *pcomp_curcs; +const char *pcomp_curcmd; +const char *pcomp_curtxt; + +char *pcomp_line; +int pcomp_ind; + +#ifdef DEBUG +/* Debugging code */ +static void +#if defined (PREFER_STDARG) +debug_printf (const char *format, ...) +#else +debug_printf (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + if (progcomp_debug == 0) + return; + + SH_VA_START (args, format); + + fprintf (stdout, "DEBUG: "); + vfprintf (stdout, format, args); + fprintf (stdout, "\n"); + + rl_on_new_line (); + + va_end (args); +} +#endif + +/* Functions to manage the item lists */ + +void +set_itemlist_dirty (it) + ITEMLIST *it; +{ + it->flags |= LIST_DIRTY; +} + +void +initialize_itemlist (itp) + ITEMLIST *itp; +{ + (*itp->list_getter) (itp); + itp->flags |= LIST_INITIALIZED; + itp->flags &= ~LIST_DIRTY; +} + +void +clean_itemlist (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + + sl = itp->slist; + if (sl) + { + if ((itp->flags & (LIST_DONTFREEMEMBERS|LIST_DONTFREE)) == 0) + strvec_flush (sl->list); + if ((itp->flags & LIST_DONTFREE) == 0) + free (sl->list); + free (sl); + } + itp->slist = (STRINGLIST *)NULL; + itp->flags &= ~(LIST_DONTFREE|LIST_DONTFREEMEMBERS|LIST_INITIALIZED|LIST_DIRTY); +} + + +static int +shouldexp_filterpat (s) + char *s; +{ + register char *p; + + for (p = s; p && *p; p++) + { + if (*p == '\\') + p++; + else if (*p == '&') + return 1; + } + return 0; +} + +/* Replace any instance of `&' in PAT with TEXT. Backslash may be used to + quote a `&' and inhibit substitution. Returns a new string. This just + calls stringlib.c:strcreplace(). */ +static char * +preproc_filterpat (pat, text) + char *pat; + const char *text; +{ + char *ret; + + ret = strcreplace (pat, '&', text, 1); + return ret; +} + +/* Remove any match of FILTERPAT from SL. A `&' in FILTERPAT is replaced by + TEXT. A leading `!' in FILTERPAT negates the pattern; in this case + any member of SL->list that does *not* match will be removed. This returns + a new STRINGLIST with the matching members of SL *copied*. Any + non-matching members of SL->list are *freed*. */ +STRINGLIST * +filter_stringlist (sl, filterpat, text) + STRINGLIST *sl; + char *filterpat; + const char *text; +{ + int i, m, not; + STRINGLIST *ret; + char *npat, *t; + + if (sl == 0 || sl->list == 0 || sl->list_len == 0) + return sl; + + npat = shouldexp_filterpat (filterpat) ? preproc_filterpat (filterpat, text) : filterpat; + +#if defined (EXTENDED_GLOB) + not = (npat[0] == '!' && (extended_glob == 0 || npat[1] != '(')); /*)*/ +#else + not = (npat[0] == '!'); +#endif + t = not ? npat + 1 : npat; + + ret = strlist_create (sl->list_size); + for (i = 0; i < sl->list_len; i++) + { + m = strmatch (t, sl->list[i], FNMATCH_EXTFLAG | FNMATCH_IGNCASE); + if ((not && m == FNM_NOMATCH) || (not == 0 && m != FNM_NOMATCH)) + free (sl->list[i]); + else + ret->list[ret->list_len++] = sl->list[i]; + } + + ret->list[ret->list_len] = (char *)NULL; + if (npat != filterpat) + free (npat); + + return ret; +} + +/* Turn an array of strings returned by rl_completion_matches into a STRINGLIST. + This understands how rl_completion_matches sets matches[0] (the lcd of the + strings in the list, unless it's the only match). */ +STRINGLIST * +completions_to_stringlist (matches) + char **matches; +{ + STRINGLIST *sl; + int mlen, i, n; + + mlen = (matches == 0) ? 0 : strvec_len (matches); + sl = strlist_create (mlen + 1); + + if (matches == 0 || matches[0] == 0) + return sl; + + if (matches[1] == 0) + { + sl->list[0] = STRDUP (matches[0]); + sl->list[sl->list_len = 1] = (char *)NULL; + return sl; + } + + for (i = 1, n = 0; i < mlen; i++, n++) + sl->list[n] = STRDUP (matches[i]); + sl->list_len = n; + sl->list[n] = (char *)NULL; + + return sl; +} + +/* Functions to manage the various ITEMLISTs that we populate internally. + The caller is responsible for setting ITP->flags correctly. */ + +static int +it_init_aliases (itp) + ITEMLIST *itp; +{ +#ifdef ALIAS + alias_t **alias_list; + register int i, n; + STRINGLIST *sl; + + alias_list = all_aliases (); + if (alias_list == 0) + { + itp->slist = (STRINGLIST *)NULL; + return 0; + } + for (n = 0; alias_list[n]; n++) + ; + sl = strlist_create (n+1); + for (i = 0; i < n; i++) + sl->list[i] = STRDUP (alias_list[i]->name); + sl->list[n] = (char *)NULL; + sl->list_size = sl->list_len = n; + itp->slist = sl; +#else + itp->slist = (STRINGLIST *)NULL; +#endif + free (alias_list); + return 1; +} + +static void +init_itemlist_from_varlist (itp, svfunc) + ITEMLIST *itp; + SVFUNC *svfunc; +{ + SHELL_VAR **vlist; + STRINGLIST *sl; + register int i, n; + + vlist = (*svfunc) (); + if (vlist == 0) + { + itp->slist = (STRINGLIST *)NULL; + return; + } + for (n = 0; vlist[n]; n++) + ; + sl = strlist_create (n+1); + for (i = 0; i < n; i++) + sl->list[i] = savestring (vlist[i]->name); + sl->list[sl->list_len = n] = (char *)NULL; + itp->slist = sl; + free (vlist); +} + +static int +it_init_arrayvars (itp) + ITEMLIST *itp; +{ +#if defined (ARRAY_VARS) + init_itemlist_from_varlist (itp, all_array_variables); + return 1; +#else + return 0; +#endif +} + +static int +it_init_bindings (itp) + ITEMLIST *itp; +{ + char **blist; + STRINGLIST *sl; + + /* rl_funmap_names allocates blist, but not its members */ + blist = (char **)rl_funmap_names (); /* XXX fix const later */ + sl = strlist_create (0); + sl->list = blist; + sl->list_size = 0; + sl->list_len = strvec_len (sl->list); + itp->flags |= LIST_DONTFREEMEMBERS; + itp->slist = sl; + + return 0; +} + +static int +it_init_builtins (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + register int i, n; + + sl = strlist_create (num_shell_builtins); + for (i = n = 0; i < num_shell_builtins; i++) + if (shell_builtins[i].function) + sl->list[n++] = shell_builtins[i].name; + sl->list[sl->list_len = n] = (char *)NULL; + itp->flags |= LIST_DONTFREEMEMBERS; + itp->slist = sl; + return 0; +} + +static int +it_init_enabled (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + register int i, n; + + sl = strlist_create (num_shell_builtins); + for (i = n = 0; i < num_shell_builtins; i++) + { + if (shell_builtins[i].function && (shell_builtins[i].flags & BUILTIN_ENABLED)) + sl->list[n++] = shell_builtins[i].name; + } + sl->list[sl->list_len = n] = (char *)NULL; + itp->flags |= LIST_DONTFREEMEMBERS; + itp->slist = sl; + return 0; +} + +static int +it_init_disabled (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + register int i, n; + + sl = strlist_create (num_shell_builtins); + for (i = n = 0; i < num_shell_builtins; i++) + { + if (shell_builtins[i].function && ((shell_builtins[i].flags & BUILTIN_ENABLED) == 0)) + sl->list[n++] = shell_builtins[i].name; + } + sl->list[sl->list_len = n] = (char *)NULL; + itp->flags |= LIST_DONTFREEMEMBERS; + itp->slist = sl; + return 0; +} + +static int +it_init_exported (itp) + ITEMLIST *itp; +{ + init_itemlist_from_varlist (itp, all_exported_variables); + return 0; +} + +static int +it_init_functions (itp) + ITEMLIST *itp; +{ + init_itemlist_from_varlist (itp, all_visible_functions); + return 0; +} + +/* Like it_init_builtins, but includes everything the help builtin looks at, + not just builtins with an active implementing function. */ +static int +it_init_helptopics (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + register int i, n; + + sl = strlist_create (num_shell_builtins); + for (i = n = 0; i < num_shell_builtins; i++) + sl->list[n++] = shell_builtins[i].name; + sl->list[sl->list_len = n] = (char *)NULL; + itp->flags |= LIST_DONTFREEMEMBERS; + itp->slist = sl; + return 0; +} + +static int +it_init_hostnames (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + + sl = strlist_create (0); + sl->list = get_hostname_list (); + sl->list_len = sl->list ? strvec_len (sl->list) : 0; + sl->list_size = sl->list_len; + itp->slist = sl; + itp->flags |= LIST_DONTFREEMEMBERS|LIST_DONTFREE; + return 0; +} + +static int +it_init_joblist (itp, jstate) + ITEMLIST *itp; + int jstate; +{ +#if defined (JOB_CONTROL) + STRINGLIST *sl; + register int i; + register PROCESS *p; + char *s, *t; + JOB *j; + JOB_STATE ws; /* wanted state */ + + ws = JNONE; + if (jstate == 0) + ws = JRUNNING; + else if (jstate == 1) + ws = JSTOPPED; + + sl = strlist_create (js.j_jobslots); + for (i = js.j_jobslots - 1; i >= 0; i--) + { + j = get_job_by_jid (i); + if (j == 0) + continue; + p = j->pipe; + if (jstate == -1 || JOBSTATE(i) == ws) + { + s = savestring (p->command); + t = strpbrk (s, " \t\n"); + if (t) + *t = '\0'; + sl->list[sl->list_len++] = s; + } + } + itp->slist = sl; +#else + itp->slist = (STRINGLIST *)NULL; +#endif + return 0; +} + +static int +it_init_jobs (itp) + ITEMLIST *itp; +{ + return (it_init_joblist (itp, -1)); +} + +static int +it_init_running (itp) + ITEMLIST *itp; +{ + return (it_init_joblist (itp, 0)); +} + +static int +it_init_stopped (itp) + ITEMLIST *itp; +{ + return (it_init_joblist (itp, 1)); +} + +static int +it_init_keywords (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + register int i, n; + + for (n = 0; word_token_alist[n].word; n++) + ; + sl = strlist_create (n); + for (i = 0; i < n; i++) + sl->list[i] = word_token_alist[i].word; + sl->list[sl->list_len = i] = (char *)NULL; + itp->flags |= LIST_DONTFREEMEMBERS; + itp->slist = sl; + return 0; +} + +static int +it_init_signals (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + + sl = strlist_create (0); + sl->list = signal_names; + sl->list_len = strvec_len (sl->list); + itp->flags |= LIST_DONTFREE; + itp->slist = sl; + return 0; +} + +static int +it_init_variables (itp) + ITEMLIST *itp; +{ + init_itemlist_from_varlist (itp, all_visible_variables); + return 0; +} + +static int +it_init_setopts (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + + sl = strlist_create (0); + sl->list = get_minus_o_opts (); + sl->list_len = strvec_len (sl->list); + itp->slist = sl; + itp->flags |= LIST_DONTFREEMEMBERS; + return 0; +} + +static int +it_init_shopts (itp) + ITEMLIST *itp; +{ + STRINGLIST *sl; + + sl = strlist_create (0); + sl->list = get_shopt_options (); + sl->list_len = strvec_len (sl->list); + itp->slist = sl; + itp->flags |= LIST_DONTFREEMEMBERS; + return 0; +} + +/* Generate a list of all matches for TEXT using the STRINGLIST in itp->slist + as the list of possibilities. If the itemlist has been marked dirty or + it should be regenerated every time, destroy the old STRINGLIST and make a + new one before trying the match. TEXT is dequoted before attempting a + match. */ +static STRINGLIST * +gen_matches_from_itemlist (itp, text) + ITEMLIST *itp; + const char *text; +{ + STRINGLIST *ret, *sl; + int tlen, i, n; + char *ntxt; + + if ((itp->flags & (LIST_DIRTY|LIST_DYNAMIC)) || + (itp->flags & LIST_INITIALIZED) == 0) + { + if (itp->flags & (LIST_DIRTY|LIST_DYNAMIC)) + clean_itemlist (itp); + if ((itp->flags & LIST_INITIALIZED) == 0) + initialize_itemlist (itp); + } + if (itp->slist == 0) + return ((STRINGLIST *)NULL); + ret = strlist_create (itp->slist->list_len+1); + sl = itp->slist; + + ntxt = bash_dequote_text (text); + tlen = STRLEN (ntxt); + + for (i = n = 0; i < sl->list_len; i++) + { + if (tlen == 0 || STREQN (sl->list[i], ntxt, tlen)) + ret->list[n++] = STRDUP (sl->list[i]); + } + ret->list[ret->list_len = n] = (char *)NULL; + + FREE (ntxt); + return ret; +} + +/* A wrapper for rl_filename_completion_function that dequotes the filename + before attempting completions. */ +static char * +pcomp_filename_completion_function (text, state) + const char *text; + int state; +{ + static char *dfn; /* dequoted filename */ + int iscompgen, iscompleting; + + if (state == 0) + { + FREE (dfn); + /* remove backslashes quoting special characters in filenames. */ + /* There are roughly three paths we can follow to get here: + 1. complete -f + 2. compgen -f "$word" from a completion function + 3. compgen -f "$word" from the command line + They all need to be handled. + + In the first two cases, readline will run the filename dequoting + function in rl_filename_completion_function if it found a filename + quoting character in the word to be completed + (rl_completion_found_quote). We run the dequoting function here + if we're running compgen, we're not completing, and the + rl_filename_completion_function won't dequote the filename + (rl_completion_found_quote == 0). */ + iscompgen = this_shell_builtin == compgen_builtin; + iscompleting = RL_ISSTATE (RL_STATE_COMPLETING); + if (iscompgen && iscompleting == 0 && rl_completion_found_quote == 0 + && rl_filename_dequoting_function) + { + /* Use rl_completion_quote_character because any single or + double quotes have been removed by the time TEXT makes it + here, and we don't want to remove backslashes inside + quoted strings. */ + dfn = (*rl_filename_dequoting_function) ((char *)text, rl_completion_quote_character); + } + /* Intended to solve a mismatched assumption by bash-completion. If + the text to be completed is empty, but bash-completion turns it into + a quoted string ('') assuming that this code will dequote it before + calling readline, do the dequoting. */ + else if (iscompgen && iscompleting && + pcomp_curtxt && *pcomp_curtxt == 0 && + text && (*text == '\'' || *text == '"') && text[1] == text[0] && text[2] == 0 && + rl_filename_dequoting_function) + dfn = (*rl_filename_dequoting_function) ((char *)text, rl_completion_quote_character); + /* Another mismatched assumption by bash-completion. If compgen is being + run as part of bash-completion, and the argument to compgen is not + the same as the word originally passed to the programmable completion + code, dequote the argument if it has quote characters. It's an + attempt to detect when bash-completion is quoting its filename + argument before calling compgen. */ + /* We could check whether gen_shell_function_matches is in the call + stack by checking whether the gen-shell-function-matches tag is in + the unwind-protect stack, but there's no function to do that yet. + We could simply check whether we're executing in a function by + checking variable_context, and may end up doing that. */ + else if (iscompgen && iscompleting && rl_filename_dequoting_function && + pcomp_curtxt && text && + STREQ (pcomp_curtxt, text) == 0 && + variable_context && + sh_contains_quotes (text)) /* guess */ + dfn = (*rl_filename_dequoting_function) ((char *)text, rl_completion_quote_character); + else + dfn = savestring (text); + } + + return (rl_filename_completion_function (dfn, state)); +} + +#define GEN_COMPS(bmap, flag, it, text, glist, tlist) \ + do { \ + if (bmap & flag) \ + { \ + tlist = gen_matches_from_itemlist (it, text); \ + if (tlist) \ + { \ + glist = strlist_append (glist, tlist); \ + strlist_dispose (tlist); \ + } \ + } \ + } while (0) + +#define GEN_XCOMPS(bmap, flag, text, func, cmatches, glist, tlist) \ + do { \ + if (bmap & flag) \ + { \ + cmatches = rl_completion_matches (text, func); \ + tlist = completions_to_stringlist (cmatches); \ + glist = strlist_append (glist, tlist); \ + strvec_dispose (cmatches); \ + strlist_dispose (tlist); \ + } \ + } while (0) + +/* Functions to generate lists of matches from the actions member of CS. */ + +static STRINGLIST * +gen_action_completions (cs, text) + COMPSPEC *cs; + const char *text; +{ + STRINGLIST *ret, *tmatches; + char **cmatches; /* from rl_completion_matches ... */ + unsigned long flags; + int t; + + ret = tmatches = (STRINGLIST *)NULL; + flags = cs->actions; + + GEN_COMPS (flags, CA_ALIAS, &it_aliases, text, ret, tmatches); + GEN_COMPS (flags, CA_ARRAYVAR, &it_arrayvars, text, ret, tmatches); + GEN_COMPS (flags, CA_BINDING, &it_bindings, text, ret, tmatches); + GEN_COMPS (flags, CA_BUILTIN, &it_builtins, text, ret, tmatches); + GEN_COMPS (flags, CA_DISABLED, &it_disabled, text, ret, tmatches); + GEN_COMPS (flags, CA_ENABLED, &it_enabled, text, ret, tmatches); + GEN_COMPS (flags, CA_EXPORT, &it_exports, text, ret, tmatches); + GEN_COMPS (flags, CA_FUNCTION, &it_functions, text, ret, tmatches); + GEN_COMPS (flags, CA_HELPTOPIC, &it_helptopics, text, ret, tmatches); + GEN_COMPS (flags, CA_HOSTNAME, &it_hostnames, text, ret, tmatches); + GEN_COMPS (flags, CA_JOB, &it_jobs, text, ret, tmatches); + GEN_COMPS (flags, CA_KEYWORD, &it_keywords, text, ret, tmatches); + GEN_COMPS (flags, CA_RUNNING, &it_running, text, ret, tmatches); + GEN_COMPS (flags, CA_SETOPT, &it_setopts, text, ret, tmatches); + GEN_COMPS (flags, CA_SHOPT, &it_shopts, text, ret, tmatches); + GEN_COMPS (flags, CA_SIGNAL, &it_signals, text, ret, tmatches); + GEN_COMPS (flags, CA_STOPPED, &it_stopped, text, ret, tmatches); + GEN_COMPS (flags, CA_VARIABLE, &it_variables, text, ret, tmatches); + + GEN_XCOMPS(flags, CA_COMMAND, text, command_word_completion_function, cmatches, ret, tmatches); + GEN_XCOMPS(flags, CA_FILE, text, pcomp_filename_completion_function, cmatches, ret, tmatches); + GEN_XCOMPS(flags, CA_USER, text, rl_username_completion_function, cmatches, ret, tmatches); + GEN_XCOMPS(flags, CA_GROUP, text, bash_groupname_completion_function, cmatches, ret, tmatches); + GEN_XCOMPS(flags, CA_SERVICE, text, bash_servicename_completion_function, cmatches, ret, tmatches); + + /* And lastly, the special case for directories */ + if (flags & CA_DIRECTORY) + { + t = rl_filename_completion_desired; + rl_completion_mark_symlink_dirs = 1; /* override user preference */ + cmatches = bash_directory_completion_matches (text); + /* If we did not want filename completion before this, and there are + no matches, turn off rl_filename_completion_desired so whatever + matches we get are not treated as filenames (it gets turned on by + rl_filename_completion_function unconditionally). */ + if (t == 0 && cmatches == 0 && rl_filename_completion_desired == 1) + rl_filename_completion_desired = 0; + tmatches = completions_to_stringlist (cmatches); + ret = strlist_append (ret, tmatches); + strvec_dispose (cmatches); + strlist_dispose (tmatches); + } + + return ret; +} + +/* Generate a list of matches for CS->globpat. Unresolved: should this use + TEXT as a match prefix, or just go without? Currently, the code does not + use TEXT, just globs CS->globpat and returns the results. If we do decide + to use TEXT, we should call quote_string_for_globbing before the call to + glob_filename (in which case we could use shell_glob_filename). */ +static STRINGLIST * +gen_globpat_matches (cs, text) + COMPSPEC *cs; + const char *text; +{ + STRINGLIST *sl; + int gflags; + + sl = strlist_create (0); + gflags = glob_star ? GX_GLOBSTAR : 0; + sl->list = glob_filename (cs->globpat, gflags); + if (GLOB_FAILED (sl->list)) + sl->list = (char **)NULL; + if (sl->list) + sl->list_len = sl->list_size = strvec_len (sl->list); + return sl; +} + +/* Perform the shell word expansions on CS->words and return the results. + Again, this ignores TEXT. */ +static STRINGLIST * +gen_wordlist_matches (cs, text) + COMPSPEC *cs; + const char *text; +{ + WORD_LIST *l, *l2; + STRINGLIST *sl; + int nw, tlen; + char *ntxt; /* dequoted TEXT to use in comparisons */ + + if (cs->words == 0 || cs->words[0] == '\0') + return ((STRINGLIST *)NULL); + + /* This used to be a simple expand_string(cs->words, 0), but that won't + do -- there's no way to split a simple list into individual words + that way, since the shell semantics say that word splitting is done + only on the results of expansion. split_at_delims also handles embedded + quoted strings and preserves the quotes for the expand_words_shellexp + function call that follows. */ + /* XXX - this is where this function spends most of its time */ + l = split_at_delims (cs->words, strlen (cs->words), (char *)NULL, -1, 0, (int *)NULL, (int *)NULL); + if (l == 0) + return ((STRINGLIST *)NULL); + /* This will jump back to the top level if the expansion fails... */ + l2 = expand_words_shellexp (l); + dispose_words (l); + + nw = list_length (l2); + sl = strlist_create (nw + 1); + + ntxt = bash_dequote_text (text); + tlen = STRLEN (ntxt); + + for (nw = 0, l = l2; l; l = l->next) + { + if (tlen == 0 || STREQN (l->word->word, ntxt, tlen)) + sl->list[nw++] = STRDUP (l->word->word); + } + sl->list[sl->list_len = nw] = (char *)NULL; + + dispose_words (l2); + FREE (ntxt); + return sl; +} + +#ifdef ARRAY_VARS + +static SHELL_VAR * +bind_comp_words (lwords) + WORD_LIST *lwords; +{ + SHELL_VAR *v; + + v = find_variable_noref ("COMP_WORDS"); + if (v == 0) + v = make_new_array_variable ("COMP_WORDS"); + if (nameref_p (v)) + VUNSETATTR (v, att_nameref); +#if 0 + if (readonly_p (v)) + VUNSETATTR (v, att_readonly); +#endif + if (array_p (v) == 0) + v = convert_var_to_array (v); + v = assign_array_var_from_word_list (v, lwords, 0); + + VUNSETATTR (v, att_invisible); + return v; +} +#endif /* ARRAY_VARS */ + +static void +bind_compfunc_variables (line, ind, lwords, cw, exported) + char *line; + int ind; + WORD_LIST *lwords; + int cw, exported; +{ + char ibuf[INT_STRLEN_BOUND(int) + 1]; + char *value; + SHELL_VAR *v; + size_t llen; + int c; + + /* Set the variables that the function expects while it executes. Maybe + these should be in the function environment (temporary_env). */ + v = bind_variable ("COMP_LINE", line, 0); + if (v && exported) + VSETATTR(v, att_exported); + + /* Post bash-4.2: COMP_POINT is characters instead of bytes. */ + c = line[ind]; + line[ind] = '\0'; + llen = MB_STRLEN (line); + line[ind] = c; + value = inttostr (llen, ibuf, sizeof(ibuf)); + v = bind_int_variable ("COMP_POINT", value, 0); + if (v && exported) + VSETATTR(v, att_exported); + + value = inttostr (rl_completion_type, ibuf, sizeof (ibuf)); + v = bind_int_variable ("COMP_TYPE", value, 0); + if (v && exported) + VSETATTR(v, att_exported); + + value = inttostr (rl_completion_invoking_key, ibuf, sizeof (ibuf)); + v = bind_int_variable ("COMP_KEY", value, 0); + if (v && exported) + VSETATTR(v, att_exported); + + /* Since array variables can't be exported, we don't bother making the + array of words. */ + if (exported == 0) + { +#ifdef ARRAY_VARS + v = bind_comp_words (lwords); + value = inttostr (cw, ibuf, sizeof(ibuf)); + bind_int_variable ("COMP_CWORD", value, 0); +#endif + } + else + array_needs_making = 1; +} + +static void +unbind_compfunc_variables (exported) + int exported; +{ + unbind_variable_noref ("COMP_LINE"); + unbind_variable_noref ("COMP_POINT"); + unbind_variable_noref ("COMP_TYPE"); + unbind_variable_noref ("COMP_KEY"); +#ifdef ARRAY_VARS + unbind_variable_noref ("COMP_WORDS"); + unbind_variable_noref ("COMP_CWORD"); +#endif + if (exported) + array_needs_making = 1; +} + +/* Build the list of words to pass to a function or external command + as arguments. When the function or command is invoked, + + $0 == function or command being invoked + $1 == command name + $2 == word to be completed (possibly null) + $3 == previous word + + Functions can access all of the words in the current command line + with the COMP_WORDS array. External commands cannot; they have to + make do with the COMP_LINE and COMP_POINT variables. */ + +static WORD_LIST * +build_arg_list (cmd, cname, text, lwords, ind) + char *cmd; + const char *cname; + const char *text; + WORD_LIST *lwords; + int ind; +{ + WORD_LIST *ret, *cl, *l; + WORD_DESC *w; + int i; + + ret = (WORD_LIST *)NULL; + w = make_word (cmd); + ret = make_word_list (w, (WORD_LIST *)NULL); /* $0 */ + + w = make_word (cname); /* $1 */ + cl = ret->next = make_word_list (w, (WORD_LIST *)NULL); + + w = make_word (text); + cl->next = make_word_list (w, (WORD_LIST *)NULL); /* $2 */ + cl = cl->next; + + /* Search lwords for current word */ + for (l = lwords, i = 1; l && i < ind-1; l = l->next, i++) + ; + w = (l && l->word) ? copy_word (l->word) : make_word (""); + cl->next = make_word_list (w, (WORD_LIST *)NULL); + + return ret; +} + +/* Build a command string with + $0 == cs->funcname (function to execute for completion list) + $1 == command name (command being completed) + $2 = word to be completed (possibly null) + $3 = previous word + and run in the current shell. The function should put its completion + list into the array variable COMPREPLY. We build a STRINGLIST + from the results and return it. + + Since the shell function should return its list of matches in an array + variable, this does nothing if arrays are not compiled into the shell. */ + +static STRINGLIST * +gen_shell_function_matches (cs, cmd, text, line, ind, lwords, nw, cw, foundp) + COMPSPEC *cs; + const char *cmd; + const char *text; + char *line; + int ind; + WORD_LIST *lwords; + int nw, cw; + int *foundp; +{ + char *funcname; + STRINGLIST *sl; + SHELL_VAR *f, *v; + WORD_LIST *cmdlist; + int fval, found; + sh_parser_state_t ps; + sh_parser_state_t * restrict pps; +#if defined (ARRAY_VARS) + ARRAY *a; +#endif + + found = 0; + if (foundp) + *foundp = found; + + funcname = cs->funcname; + f = find_function (funcname); + if (f == 0) + { + internal_error (_("completion: function `%s' not found"), funcname); + rl_ding (); + rl_on_new_line (); + return ((STRINGLIST *)NULL); + } + +#if !defined (ARRAY_VARS) + return ((STRINGLIST *)NULL); +#else + + /* We pass cw - 1 because command_line_to_word_list returns indices that are + 1-based, while bash arrays are 0-based. */ + bind_compfunc_variables (line, ind, lwords, cw - 1, 0); + + cmdlist = build_arg_list (funcname, cmd, text, lwords, cw); + + pps = &ps; + save_parser_state (pps); + begin_unwind_frame ("gen-shell-function-matches"); + add_unwind_protect (restore_parser_state, (char *)pps); + add_unwind_protect (dispose_words, (char *)cmdlist); + add_unwind_protect (unbind_compfunc_variables, (char *)0); + + fval = execute_shell_function (f, cmdlist); + + discard_unwind_frame ("gen-shell-function-matches"); + restore_parser_state (pps); + + found = fval != EX_NOTFOUND; + if (fval == EX_RETRYFAIL) + found |= PCOMP_RETRYFAIL; + if (foundp) + *foundp = found; + + /* Now clean up and destroy everything. */ + dispose_words (cmdlist); + unbind_compfunc_variables (0); + + /* The list of completions is returned in the array variable COMPREPLY. */ + v = find_variable ("COMPREPLY"); + if (v == 0) + return ((STRINGLIST *)NULL); + if (array_p (v) == 0 && assoc_p (v) == 0) + v = convert_var_to_array (v); + + VUNSETATTR (v, att_invisible); + + a = array_cell (v); + if (found == 0 || (found & PCOMP_RETRYFAIL) || a == 0 || array_p (v) == 0 || array_empty (a)) + sl = (STRINGLIST *)NULL; + else + { + /* XXX - should we filter the list of completions so only those matching + TEXT are returned? Right now, we do not. */ + sl = strlist_create (0); + sl->list = array_to_argv (a, 0); + sl->list_len = sl->list_size = array_num_elements (a); + } + + /* XXX - should we unbind COMPREPLY here? */ + unbind_variable_noref ("COMPREPLY"); + + return (sl); +#endif +} + +/* Build a command string with + $0 == cs->command (command to execute for completion list) + $1 == command name (command being completed) + $2 == word to be completed (possibly null) + $3 == previous word + and run it with command substitution. Parse the results, one word + per line, with backslashes allowed to escape newlines. Build a + STRINGLIST from the results and return it. */ + +static STRINGLIST * +gen_command_matches (cs, cmd, text, line, ind, lwords, nw, cw) + COMPSPEC *cs; + const char *cmd; + const char *text; + char *line; + int ind; + WORD_LIST *lwords; + int nw, cw; +{ + char *csbuf, *cscmd, *t; + int cmdlen, cmdsize, n, ws, we; + WORD_LIST *cmdlist, *cl; + WORD_DESC *tw; + STRINGLIST *sl; + + bind_compfunc_variables (line, ind, lwords, cw, 1); + cmdlist = build_arg_list (cs->command, cmd, text, lwords, cw); + + /* Estimate the size needed for the buffer. */ + n = strlen (cs->command); + cmdsize = n + 1; + for (cl = cmdlist->next; cl; cl = cl->next) + cmdsize += STRLEN (cl->word->word) + 3; + cmdsize += 2; + + /* allocate the string for the command and fill it in. */ + cscmd = (char *)xmalloc (cmdsize + 1); + + strcpy (cscmd, cs->command); /* $0 */ + cmdlen = n; + cscmd[cmdlen++] = ' '; + for (cl = cmdlist->next; cl; cl = cl->next) /* $1, $2, $3, ... */ + { + t = sh_single_quote (cl->word->word ? cl->word->word : ""); + n = strlen (t); + RESIZE_MALLOCED_BUFFER (cscmd, cmdlen, n + 2, cmdsize, 64); + strcpy (cscmd + cmdlen, t); + cmdlen += n; + if (cl->next) + cscmd[cmdlen++] = ' '; + free (t); + } + cscmd[cmdlen] = '\0'; + + tw = command_substitute (cscmd, 0, 0); + csbuf = tw ? tw->word : (char *)NULL; + if (tw) + dispose_word_desc (tw); + + /* Now clean up and destroy everything. */ + dispose_words (cmdlist); + free (cscmd); + unbind_compfunc_variables (1); + + if (csbuf == 0 || *csbuf == '\0') + { + FREE (csbuf); + return ((STRINGLIST *)NULL); + } + + /* Now break CSBUF up at newlines, with backslash allowed to escape a + newline, and put the individual words into a STRINGLIST. */ + sl = strlist_create (16); + for (ws = 0; csbuf[ws]; ) + { + we = ws; + while (csbuf[we] && csbuf[we] != '\n') + { + if (csbuf[we] == '\\' && csbuf[we+1] == '\n') + we++; + we++; + } + t = substring (csbuf, ws, we); + if (sl->list_len >= sl->list_size - 1) + strlist_resize (sl, sl->list_size + 16); + sl->list[sl->list_len++] = t; + while (csbuf[we] == '\n') we++; + ws = we; + } + sl->list[sl->list_len] = (char *)NULL; + + free (csbuf); + return (sl); +} + +static WORD_LIST * +command_line_to_word_list (line, llen, sentinel, nwp, cwp) + char *line; + int llen, sentinel, *nwp, *cwp; +{ + WORD_LIST *ret; + const char *delims; + +#if 0 + delims = "()<>;&| \t\n"; /* shell metacharacters break words */ +#else + delims = rl_completer_word_break_characters; +#endif + ret = split_at_delims (line, llen, delims, sentinel, SD_NOQUOTEDELIM|SD_COMPLETE, nwp, cwp); + return (ret); +} + +/* Evaluate COMPSPEC *cs and return all matches for WORD. */ + +STRINGLIST * +gen_compspec_completions (cs, cmd, word, start, end, foundp) + COMPSPEC *cs; + const char *cmd; + const char *word; + int start, end; + int *foundp; +{ + STRINGLIST *ret, *tmatches; + char *line; + int llen, nw, cw, found, foundf; + WORD_LIST *lwords; + WORD_DESC *lw; + COMPSPEC *tcs; + + found = 1; + +#ifdef DEBUG + debug_printf ("gen_compspec_completions (%s, %s, %d, %d)", cmd, word, start, end); + debug_printf ("gen_compspec_completions: %s -> %p", cmd, cs); +#endif + ret = gen_action_completions (cs, word); +#ifdef DEBUG + if (ret && progcomp_debug) + { + debug_printf ("gen_action_completions (%p, %s) -->", cs, word); + strlist_print (ret, "\t"); + rl_on_new_line (); + } +#endif + + /* Now we start generating completions based on the other members of CS. */ + if (cs->globpat) + { + tmatches = gen_globpat_matches (cs, word); + if (tmatches) + { +#ifdef DEBUG + if (progcomp_debug) + { + debug_printf ("gen_globpat_matches (%p, %s) -->", cs, word); + strlist_print (tmatches, "\t"); + rl_on_new_line (); + } +#endif + ret = strlist_append (ret, tmatches); + strlist_dispose (tmatches); + rl_filename_completion_desired = 1; + } + } + + if (cs->words) + { + tmatches = gen_wordlist_matches (cs, word); + if (tmatches) + { +#ifdef DEBUG + if (progcomp_debug) + { + debug_printf ("gen_wordlist_matches (%p, %s) -->", cs, word); + strlist_print (tmatches, "\t"); + rl_on_new_line (); + } +#endif + ret = strlist_append (ret, tmatches); + strlist_dispose (tmatches); + } + } + + lwords = (WORD_LIST *)NULL; + line = (char *)NULL; + if (cs->command || cs->funcname) + { + /* If we have a command or function to execute, we need to first break + the command line into individual words, find the number of words, + and find the word in the list containing the word to be completed. */ + line = substring (pcomp_line, start, end); + llen = end - start; + +#ifdef DEBUG + debug_printf ("command_line_to_word_list (%s, %d, %d, %p, %p)", + line, llen, pcomp_ind - start, &nw, &cw); +#endif + lwords = command_line_to_word_list (line, llen, pcomp_ind - start, &nw, &cw); + /* If we skipped a NULL word at the beginning of the line, add it back */ + if (lwords && lwords->word && cmd[0] == 0 && lwords->word->word[0] != 0) + { + lw = make_bare_word (cmd); + lwords = make_word_list (lw, lwords); + nw++; + cw++; + } +#ifdef DEBUG + if (lwords == 0 && llen > 0) + debug_printf ("ERROR: command_line_to_word_list returns NULL"); + else if (progcomp_debug) + { + debug_printf ("command_line_to_word_list -->"); + printf ("\t"); + print_word_list (lwords, "!"); + printf ("\n"); + fflush(stdout); + rl_on_new_line (); + } +#endif + } + + if (cs->funcname) + { + foundf = 0; + tmatches = gen_shell_function_matches (cs, cmd, word, line, pcomp_ind - start, lwords, nw, cw, &foundf); + if (foundf != 0) + found = foundf; + if (tmatches) + { +#ifdef DEBUG + if (progcomp_debug) + { + debug_printf ("gen_shell_function_matches (%p, %s, %s, %p, %d, %d) -->", cs, cmd, word, lwords, nw, cw); + strlist_print (tmatches, "\t"); + rl_on_new_line (); + } +#endif + ret = strlist_append (ret, tmatches); + strlist_dispose (tmatches); + } + } + + if (cs->command) + { + tmatches = gen_command_matches (cs, cmd, word, line, pcomp_ind - start, lwords, nw, cw); + if (tmatches) + { +#ifdef DEBUG + if (progcomp_debug) + { + debug_printf ("gen_command_matches (%p, %s, %s, %p, %d, %d) -->", cs, cmd, word, lwords, nw, cw); + strlist_print (tmatches, "\t"); + rl_on_new_line (); + } +#endif + ret = strlist_append (ret, tmatches); + strlist_dispose (tmatches); + } + } + + if (cs->command || cs->funcname) + { + if (lwords) + dispose_words (lwords); + FREE (line); + } + + if (foundp) + *foundp = found; + + if (found == 0 || (found & PCOMP_RETRYFAIL)) + { + strlist_dispose (ret); + return NULL; + } + + if (cs->filterpat) + { + tmatches = filter_stringlist (ret, cs->filterpat, word); +#ifdef DEBUG + if (progcomp_debug) + { + debug_printf ("filter_stringlist (%p, %s, %s) -->", ret, cs->filterpat, word); + strlist_print (tmatches, "\t"); + rl_on_new_line (); + } +#endif + if (ret && ret != tmatches) + { + FREE (ret->list); + free (ret); + } + ret = tmatches; + } + + if (cs->prefix || cs->suffix) + ret = strlist_prefix_suffix (ret, cs->prefix, cs->suffix); + + /* If no matches have been generated and the user has specified that + directory completion should be done as a default, call + gen_action_completions again to generate a list of matching directory + names. */ + if ((ret == 0 || ret->list_len == 0) && (cs->options & COPT_DIRNAMES)) + { + tcs = compspec_create (); + tcs->actions = CA_DIRECTORY; + FREE (ret); + ret = gen_action_completions (tcs, word); + compspec_dispose (tcs); + } + else if (cs->options & COPT_PLUSDIRS) + { + tcs = compspec_create (); + tcs->actions = CA_DIRECTORY; + tmatches = gen_action_completions (tcs, word); + ret = strlist_append (ret, tmatches); + strlist_dispose (tmatches); + compspec_dispose (tcs); + } + + return (ret); +} + +void +pcomp_set_readline_variables (flags, nval) + int flags, nval; +{ + /* If the user specified that the compspec returns filenames, make + sure that readline knows it. */ + if (flags & COPT_FILENAMES) + rl_filename_completion_desired = nval; + /* If the user doesn't want a space appended, tell readline. */ + if (flags & COPT_NOSPACE) + rl_completion_suppress_append = nval; + /* The value here is inverted, since the default is on and the `noquote' + option is supposed to turn it off */ + if (flags & COPT_NOQUOTE) + rl_filename_quoting_desired = 1 - nval; + if (flags & COPT_NOSORT) + rl_sort_completion_matches = 1 - nval; +} + +/* Set or unset FLAGS in the options word of the current compspec. + SET_OR_UNSET is 1 for setting, 0 for unsetting. */ +void +pcomp_set_compspec_options (cs, flags, set_or_unset) + COMPSPEC *cs; + int flags, set_or_unset; +{ + if (cs == 0 && ((cs = pcomp_curcs) == 0)) + return; + if (set_or_unset) + cs->options |= flags; + else + cs->options &= ~flags; +} + +static STRINGLIST * +gen_progcomp_completions (ocmd, cmd, word, start, end, foundp, retryp, lastcs) + const char *ocmd; + const char *cmd; + const char *word; + int start, end; + int *foundp, *retryp; + COMPSPEC **lastcs; +{ + COMPSPEC *cs, *oldcs; + const char *oldcmd, *oldtxt; + STRINGLIST *ret; + + cs = progcomp_search (ocmd); + + if (cs == 0 || cs == *lastcs) + { +#if 0 + if (foundp) + *foundp = 0; +#endif + return (NULL); + } + + if (*lastcs) + compspec_dispose (*lastcs); + cs->refcount++; /* XXX */ + *lastcs = cs; + + cs = compspec_copy (cs); + + oldcs = pcomp_curcs; + oldcmd = pcomp_curcmd; + oldtxt = pcomp_curtxt; + + pcomp_curcs = cs; + pcomp_curcmd = cmd; + pcomp_curtxt = word; + + ret = gen_compspec_completions (cs, cmd, word, start, end, foundp); + + pcomp_curcs = oldcs; + pcomp_curcmd = oldcmd; + pcomp_curtxt = oldtxt; + + /* We need to conditionally handle setting *retryp here */ + if (retryp) + *retryp = foundp && (*foundp & PCOMP_RETRYFAIL); + + if (foundp) + { + *foundp &= ~PCOMP_RETRYFAIL; + *foundp |= cs->options; + } + + compspec_dispose (cs); + return ret; +} + +/* The driver function for the programmable completion code. Returns a list + of matches for WORD, which is an argument to command CMD. START and END + bound the command currently being completed in pcomp_line (usually + rl_line_buffer). */ +char ** +programmable_completions (cmd, word, start, end, foundp) + const char *cmd; + const char *word; + int start, end, *foundp; +{ + COMPSPEC *lastcs; + STRINGLIST *ret; + char **rmatches, *t; + int found, retry, count; + char *ocmd; + int oend; +#if defined (ALIAS) + alias_t *al; +#endif + + lastcs = 0; + found = count = 0; + + pcomp_line = rl_line_buffer; + pcomp_ind = rl_point; + + ocmd = (char *)cmd; + oend = end; + + do + { + retry = 0; + + /* We look at the basename of CMD if the full command does not have + an associated COMPSPEC. */ + ret = gen_progcomp_completions (ocmd, ocmd, word, start, oend, &found, &retry, &lastcs); + if (found == 0) + { + t = strrchr (ocmd, '/'); + if (t && *(++t)) + ret = gen_progcomp_completions (t, ocmd, word, start, oend, &found, &retry, &lastcs); + } + + if (found == 0) + ret = gen_progcomp_completions (DEFAULTCMD, ocmd, word, start, oend, &found, &retry, &lastcs); + +#if defined (ALIAS) + /* Look up any alias for CMD, try to gen completions for it */ + /* Look up the alias, find the value, build a new line replacing CMD + with that value, offsetting PCOMP_IND and END appropriately, reset + PCOMP_LINE to the new line and OCMD with the new command name, then + call gen_progcomp_completions again. We could use alias_expand for + this, but it does more (and less) than we need right now. */ + if (found == 0 && retry == 0 && progcomp_alias && (al = find_alias (ocmd))) + { + char *ncmd, *nline, *ntxt; + int ind, lendiff; + size_t nlen, olen, llen; + + /* We found an alias for OCMD. Take the value and build a new line */ + ntxt = al->value; + nlen = strlen (ntxt); + if (nlen == 0) + break; + olen = strlen (ocmd); + lendiff = nlen - olen; /* can be negative */ + llen = strlen (pcomp_line); + + nline = (char *)xmalloc (llen + lendiff + 1); + if (start > 0) + strncpy (nline, pcomp_line, start); + strncpy (nline + start, ntxt, nlen); + strcpy (nline + start + nlen, pcomp_line + start + olen); + + /* Find the first word of the alias value and use that as OCMD. We + don't check the alias value to see whether it begins with a valid + command name, so this can be fooled. */ + ind = skip_to_delim (ntxt, 0, "()<>;&| \t\n", SD_NOJMP|SD_COMPLETE); + if (ind > 0) + ncmd = substring (ntxt, 0, ind); + else + { + free (nline); + break; /* will free pcomp_line and ocmd later */ + } + + /* Adjust PCOMP_IND and OEND appropriately */ + pcomp_ind += lendiff; + oend += lendiff; + + /* Set up values with new line. WORD stays the same. */ + if (ocmd != cmd) + free (ocmd); + if (pcomp_line != rl_line_buffer) + free (pcomp_line); + + ocmd = ncmd; + pcomp_line = nline; + + /* And go back and start over. */ + retry = 1; + } +#endif /* ALIAS */ + + count++; + + if (count > 32) + { + internal_warning (_("programmable_completion: %s: possible retry loop"), cmd); + break; + } + } + while (retry); + + if (pcomp_line != rl_line_buffer) + free (pcomp_line); + if (ocmd != cmd) + free (ocmd); + + if (ret) + { + rmatches = ret->list; + free (ret); + } + else + rmatches = (char **)NULL; + + if (foundp) + *foundp = found; + + if (lastcs) /* XXX - should be while? */ + compspec_dispose (lastcs); + + /* XXX restore pcomp_line and pcomp_ind? */ + pcomp_line = rl_line_buffer; + pcomp_ind = rl_point; + + return (rmatches); +} + +#endif /* PROGRAMMABLE_COMPLETION */ diff --git a/third_party/bash/pcomplete.h b/third_party/bash/pcomplete.h new file mode 100644 index 000000000..2a68e6a3f --- /dev/null +++ b/third_party/bash/pcomplete.h @@ -0,0 +1,177 @@ +/* pcomplete.h - structure definitions and other stuff for programmable + completion. */ + +/* Copyright (C) 1999-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_PCOMPLETE_H_) +# define _PCOMPLETE_H_ + +#include "stdc.h" +#include "hashlib.h" + +typedef struct compspec { + int refcount; + unsigned long actions; + unsigned long options; + char *globpat; + char *words; + char *prefix; + char *suffix; + char *funcname; + char *command; + char *lcommand; + char *filterpat; +} COMPSPEC; + +/* Values for COMPSPEC actions. These are things the shell knows how to + build internally. */ +#define CA_ALIAS (1<<0) +#define CA_ARRAYVAR (1<<1) +#define CA_BINDING (1<<2) +#define CA_BUILTIN (1<<3) +#define CA_COMMAND (1<<4) +#define CA_DIRECTORY (1<<5) +#define CA_DISABLED (1<<6) +#define CA_ENABLED (1<<7) +#define CA_EXPORT (1<<8) +#define CA_FILE (1<<9) +#define CA_FUNCTION (1<<10) +#define CA_GROUP (1<<11) +#define CA_HELPTOPIC (1<<12) +#define CA_HOSTNAME (1<<13) +#define CA_JOB (1<<14) +#define CA_KEYWORD (1<<15) +#define CA_RUNNING (1<<16) +#define CA_SERVICE (1<<17) +#define CA_SETOPT (1<<18) +#define CA_SHOPT (1<<19) +#define CA_SIGNAL (1<<20) +#define CA_STOPPED (1<<21) +#define CA_USER (1<<22) +#define CA_VARIABLE (1<<23) + +/* Values for COMPSPEC options field. */ +#define COPT_RESERVED (1<<0) /* reserved for other use */ +#define COPT_DEFAULT (1<<1) +#define COPT_FILENAMES (1<<2) +#define COPT_DIRNAMES (1<<3) +#define COPT_NOQUOTE (1<<4) +#define COPT_NOSPACE (1<<5) +#define COPT_BASHDEFAULT (1<<6) +#define COPT_PLUSDIRS (1<<7) +#define COPT_NOSORT (1<<8) + +#define COPT_LASTUSER COPT_NOSORT + +#define PCOMP_RETRYFAIL (COPT_LASTUSER << 1) +#define PCOMP_NOTFOUND (COPT_LASTUSER << 2) + + +/* List of items is used by the code that implements the programmable + completions. */ +typedef struct _list_of_items { + int flags; + int (*list_getter) PARAMS((struct _list_of_items *)); /* function to call to get the list */ + + STRINGLIST *slist; + + /* These may or may not be used. */ + STRINGLIST *genlist; /* for handing to the completion code one item at a time */ + int genindex; /* index of item last handed to completion code */ + +} ITEMLIST; + +/* Values for ITEMLIST -> flags */ +#define LIST_DYNAMIC 0x001 +#define LIST_DIRTY 0x002 +#define LIST_INITIALIZED 0x004 +#define LIST_MUSTSORT 0x008 +#define LIST_DONTFREE 0x010 +#define LIST_DONTFREEMEMBERS 0x020 + +#define EMPTYCMD "_EmptycmD_" +#define DEFAULTCMD "_DefaultCmD_" +#define INITIALWORD "_InitialWorD_" + +extern HASH_TABLE *prog_completes; + +extern char *pcomp_line; +extern int pcomp_ind; + +extern int prog_completion_enabled; +extern int progcomp_alias; + +/* Not all of these are used yet. */ +extern ITEMLIST it_aliases; +extern ITEMLIST it_arrayvars; +extern ITEMLIST it_bindings; +extern ITEMLIST it_builtins; +extern ITEMLIST it_commands; +extern ITEMLIST it_directories; +extern ITEMLIST it_disabled; +extern ITEMLIST it_enabled; +extern ITEMLIST it_exports; +extern ITEMLIST it_files; +extern ITEMLIST it_functions; +extern ITEMLIST it_groups; +extern ITEMLIST it_helptopics; +extern ITEMLIST it_hostnames; +extern ITEMLIST it_jobs; +extern ITEMLIST it_keywords; +extern ITEMLIST it_running; +extern ITEMLIST it_services; +extern ITEMLIST it_setopts; +extern ITEMLIST it_shopts; +extern ITEMLIST it_signals; +extern ITEMLIST it_stopped; +extern ITEMLIST it_users; +extern ITEMLIST it_variables; + +extern COMPSPEC *pcomp_curcs; +extern const char *pcomp_curcmd; + +/* Functions from pcomplib.c */ +extern COMPSPEC *compspec_create PARAMS((void)); +extern void compspec_dispose PARAMS((COMPSPEC *)); +extern COMPSPEC *compspec_copy PARAMS((COMPSPEC *)); + +extern void progcomp_create PARAMS((void)); +extern void progcomp_flush PARAMS((void)); +extern void progcomp_dispose PARAMS((void)); + +extern int progcomp_size PARAMS((void)); + +extern int progcomp_insert PARAMS((char *, COMPSPEC *)); +extern int progcomp_remove PARAMS((char *)); + +extern COMPSPEC *progcomp_search PARAMS((const char *)); + +extern void progcomp_walk PARAMS((hash_wfunc *)); + +/* Functions from pcomplete.c */ +extern void set_itemlist_dirty PARAMS((ITEMLIST *)); + +extern STRINGLIST *completions_to_stringlist PARAMS((char **)); + +extern STRINGLIST *gen_compspec_completions PARAMS((COMPSPEC *, const char *, const char *, int, int, int *)); +extern char **programmable_completions PARAMS((const char *, const char *, int, int, int *)); + +extern void pcomp_set_readline_variables PARAMS((int, int)); +extern void pcomp_set_compspec_options PARAMS((COMPSPEC *, int, int)); +#endif /* _PCOMPLETE_H_ */ diff --git a/third_party/bash/pcomplib.c b/third_party/bash/pcomplib.c new file mode 100644 index 000000000..f3ecea662 --- /dev/null +++ b/third_party/bash/pcomplib.c @@ -0,0 +1,228 @@ +/* pcomplib.c - library functions for programmable completion. */ + +/* Copyright (C) 1999-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (PROGRAMMABLE_COMPLETION) + +#include "bashansi.h" +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "pcomplete.h" + +#define COMPLETE_HASH_BUCKETS 512 /* must be power of two */ + +#define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) + +HASH_TABLE *prog_completes = (HASH_TABLE *)NULL; + +static void free_progcomp PARAMS((PTR_T)); + +COMPSPEC * +compspec_create () +{ + COMPSPEC *ret; + + ret = (COMPSPEC *)xmalloc (sizeof (COMPSPEC)); + ret->refcount = 0; + + ret->actions = (unsigned long)0; + ret->options = (unsigned long)0; + + ret->globpat = (char *)NULL; + ret->words = (char *)NULL; + ret->prefix = (char *)NULL; + ret->suffix = (char *)NULL; + ret->funcname = (char *)NULL; + ret->command = (char *)NULL; + ret->lcommand = (char *)NULL; + ret->filterpat = (char *)NULL; + + return ret; +} + +void +compspec_dispose (cs) + COMPSPEC *cs; +{ + cs->refcount--; + if (cs->refcount == 0) + { + FREE (cs->globpat); + FREE (cs->words); + FREE (cs->prefix); + FREE (cs->suffix); + FREE (cs->funcname); + FREE (cs->command); + FREE (cs->lcommand); + FREE (cs->filterpat); + + free (cs); + } +} + +COMPSPEC * +compspec_copy (cs) + COMPSPEC *cs; +{ + COMPSPEC *new; + + new = (COMPSPEC *)xmalloc (sizeof (COMPSPEC)); + + new->refcount = 1; /* was cs->refcount, but this is a fresh copy */ + new->actions = cs->actions; + new->options = cs->options; + + new->globpat = STRDUP (cs->globpat); + new->words = STRDUP (cs->words); + new->prefix = STRDUP (cs->prefix); + new->suffix = STRDUP (cs->suffix); + new->funcname = STRDUP (cs->funcname); + new->command = STRDUP (cs->command); + new->lcommand = STRDUP (cs->lcommand); + new->filterpat = STRDUP (cs->filterpat); + + return new; +} + +void +progcomp_create () +{ + if (prog_completes == 0) + prog_completes = hash_create (COMPLETE_HASH_BUCKETS); +} + +int +progcomp_size () +{ + return (HASH_ENTRIES (prog_completes)); +} + +static void +free_progcomp (data) + PTR_T data; +{ + COMPSPEC *cs; + + cs = (COMPSPEC *)data; + compspec_dispose (cs); +} + +void +progcomp_flush () +{ + if (prog_completes) + hash_flush (prog_completes, free_progcomp); +} + +void +progcomp_dispose () +{ + if (prog_completes) + hash_dispose (prog_completes); + prog_completes = (HASH_TABLE *)NULL; +} + +int +progcomp_remove (cmd) + char *cmd; +{ + register BUCKET_CONTENTS *item; + + if (prog_completes == 0) + return 1; + + item = hash_remove (cmd, prog_completes, 0); + if (item) + { + if (item->data) + free_progcomp (item->data); + free (item->key); + free (item); + return (1); + } + return (0); +} + +int +progcomp_insert (cmd, cs) + char *cmd; + COMPSPEC *cs; +{ + register BUCKET_CONTENTS *item; + + if (cs == NULL) + programming_error (_("progcomp_insert: %s: NULL COMPSPEC"), cmd); + + if (prog_completes == 0) + progcomp_create (); + + cs->refcount++; + item = hash_insert (cmd, prog_completes, 0); + if (item->data) + free_progcomp (item->data); + else + item->key = savestring (cmd); + item->data = cs; + + return 1; +} + +COMPSPEC * +progcomp_search (cmd) + const char *cmd; +{ + register BUCKET_CONTENTS *item; + COMPSPEC *cs; + + if (prog_completes == 0) + return ((COMPSPEC *)NULL); + + item = hash_search (cmd, prog_completes, 0); + + if (item == NULL) + return ((COMPSPEC *)NULL); + + cs = (COMPSPEC *)item->data; + + return (cs); +} + +void +progcomp_walk (pfunc) + hash_wfunc *pfunc; +{ + if (prog_completes == 0 || pfunc == 0 || HASH_ENTRIES (prog_completes) == 0) + return; + + hash_walk (prog_completes, pfunc); +} + +#endif /* PROGRAMMABLE_COMPLETION */ diff --git a/third_party/bash/pipesize.h b/third_party/bash/pipesize.h new file mode 100644 index 000000000..816a0b2d1 --- /dev/null +++ b/third_party/bash/pipesize.h @@ -0,0 +1,8 @@ +/* + * pipesize.h + * + * This file is automatically generated by psize.sh + * Do not edit! + */ + +#define PIPESIZE 65536 diff --git a/third_party/bash/posixdir.h b/third_party/bash/posixdir.h new file mode 100644 index 000000000..b737bd7d1 --- /dev/null +++ b/third_party/bash/posixdir.h @@ -0,0 +1,71 @@ +/* posixdir.h -- Posix directory reading includes and defines. */ + +/* Copyright (C) 1987,1991,2012,2019,2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* This file should be included instead of or . */ + +#if !defined (_POSIXDIR_H_) +#define _POSIXDIR_H_ + +#if defined (HAVE_DIRENT_H) +# include +# if defined (HAVE_STRUCT_DIRENT_D_NAMLEN) +# define D_NAMLEN(d) ((d)->d_namlen) +# else +# define D_NAMLEN(d) (strlen ((d)->d_name)) +# endif /* !HAVE_STRUCT_DIRENT_D_NAMLEN */ +#else +# if defined (HAVE_SYS_NDIR_H) +# include +# endif +# if defined (HAVE_SYS_DIR_H) +# include +# endif +# if defined (HAVE_NDIR_H) +# include +# endif +# if !defined (dirent) +# define dirent direct +# endif /* !dirent */ +# define D_NAMLEN(d) ((d)->d_namlen) +#endif /* !HAVE_DIRENT_H */ + +/* The bash code fairly consistently uses d_fileno; make sure it's available */ +#if defined (HAVE_STRUCT_DIRENT_D_INO) && !defined (HAVE_STRUCT_DIRENT_D_FILENO) +# define d_fileno d_ino +#endif + +/* Posix does not require that the d_ino field be present, and some + systems do not provide it. */ +#if !defined (HAVE_STRUCT_DIRENT_D_INO) || defined (BROKEN_DIRENT_D_INO) +# define REAL_DIR_ENTRY(dp) 1 +#else +# define REAL_DIR_ENTRY(dp) (dp->d_ino != 0) +#endif /* _POSIX_SOURCE */ + +#if defined (HAVE_STRUCT_DIRENT_D_INO) && !defined (BROKEN_DIRENT_D_INO) +# define D_INO_AVAILABLE +#endif + +/* Signal the rest of the code that it can safely use dirent.d_fileno */ +#if defined (D_INO_AVAILABLE) || defined (HAVE_STRUCT_DIRENT_D_FILENO) +# define D_FILENO_AVAILABLE 1 +#endif + +#endif /* !_POSIXDIR_H_ */ diff --git a/third_party/bash/posixjmp.h b/third_party/bash/posixjmp.h new file mode 100644 index 000000000..9c7e99ed4 --- /dev/null +++ b/third_party/bash/posixjmp.h @@ -0,0 +1,46 @@ +/* posixjmp.h -- wrapper for setjmp.h with changes for POSIX systems. */ + +/* Copyright (C) 1987,1991-2015 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _POSIXJMP_H_ +#define _POSIXJMP_H_ + +#include + +/* This *must* be included *after* config.h */ + +#if defined (HAVE_POSIX_SIGSETJMP) +# define procenv_t sigjmp_buf + +# define setjmp_nosigs(x) sigsetjmp((x), 0) +# define setjmp_sigs(x) sigsetjmp((x), 1) + +# define _rl_longjmp(x, n) siglongjmp((x), (n)) +# define sh_longjmp(x, n) siglongjmp((x), (n)) +#else +# define procenv_t jmp_buf + +# define setjmp_nosigs setjmp +# define setjmp_sigs setjmp + +# define _rl_longjmp(x, n) longjmp((x), (n)) +# define sh_longjmp(x, n) longjmp((x), (n)) +#endif + +#endif /* _POSIXJMP_H_ */ diff --git a/third_party/bash/posixselect.h b/third_party/bash/posixselect.h new file mode 100644 index 000000000..da6a1ace0 --- /dev/null +++ b/third_party/bash/posixselect.h @@ -0,0 +1,47 @@ +/* posixselect.h -- wrapper for select(2) includes and definitions */ + +/* Copyright (C) 2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _POSIXSELECT_H_ +#define _POSIXSELECT_H_ + +#if defined (FD_SET) && !defined (HAVE_SELECT) +# define HAVE_SELECT 1 +#endif + +#if defined (HAVE_SELECT) +# if !defined (HAVE_SYS_SELECT_H) || !defined (M_UNIX) +# include +# endif +#endif /* HAVE_SELECT */ +#if defined (HAVE_SYS_SELECT_H) +# include +#endif + +#ifndef USEC_PER_SEC +# define USEC_PER_SEC 1000000 +#endif + +#define USEC_TO_TIMEVAL(us, tv) \ +do { \ + (tv).tv_sec = (us) / USEC_PER_SEC; \ + (tv).tv_usec = (us) % USEC_PER_SEC; \ +} while (0) + +#endif /* _POSIXSELECT_H_ */ diff --git a/third_party/bash/posixstat.h b/third_party/bash/posixstat.h new file mode 100644 index 000000000..b60778606 --- /dev/null +++ b/third_party/bash/posixstat.h @@ -0,0 +1,162 @@ +/* posixstat.h -- Posix stat(2) definitions for systems that + don't have them. */ + +/* Copyright (C) 1987-2019 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* This file should be included instead of . + It relies on the local sys/stat.h to work though. */ +#if !defined (_POSIXSTAT_H_) +#define _POSIXSTAT_H_ + +#include + +#if defined (STAT_MACROS_BROKEN) +# undef S_ISBLK +# undef S_ISCHR +# undef S_ISDIR +# undef S_ISFIFO +# undef S_ISREG +# undef S_ISLNK +#endif /* STAT_MACROS_BROKEN */ + +/* These are guaranteed to work only on isc386 */ +#if !defined (S_IFDIR) && !defined (S_ISDIR) +# define S_IFDIR 0040000 +#endif /* !S_IFDIR && !S_ISDIR */ +#if !defined (S_IFMT) +# define S_IFMT 0170000 +#endif /* !S_IFMT */ + +/* Posix 1003.1 5.6.1.1 file types */ + +/* Some Posix-wannabe systems define _S_IF* macros instead of S_IF*, but + do not provide the S_IS* macros that Posix requires. */ + +#if defined (_S_IFMT) && !defined (S_IFMT) +#define S_IFMT _S_IFMT +#endif +#if defined (_S_IFIFO) && !defined (S_IFIFO) +#define S_IFIFO _S_IFIFO +#endif +#if defined (_S_IFCHR) && !defined (S_IFCHR) +#define S_IFCHR _S_IFCHR +#endif +#if defined (_S_IFDIR) && !defined (S_IFDIR) +#define S_IFDIR _S_IFDIR +#endif +#if defined (_S_IFBLK) && !defined (S_IFBLK) +#define S_IFBLK _S_IFBLK +#endif +#if defined (_S_IFREG) && !defined (S_IFREG) +#define S_IFREG _S_IFREG +#endif +#if defined (_S_IFLNK) && !defined (S_IFLNK) +#define S_IFLNK _S_IFLNK +#endif +#if defined (_S_IFSOCK) && !defined (S_IFSOCK) +#define S_IFSOCK _S_IFSOCK +#endif + +/* Test for each symbol individually and define the ones necessary (some + systems claiming Posix compatibility define some but not all). */ + +#if defined (S_IFBLK) && !defined (S_ISBLK) +#define S_ISBLK(m) (((m)&S_IFMT) == S_IFBLK) /* block device */ +#endif + +#if defined (S_IFCHR) && !defined (S_ISCHR) +#define S_ISCHR(m) (((m)&S_IFMT) == S_IFCHR) /* character device */ +#endif + +#if defined (S_IFDIR) && !defined (S_ISDIR) +#define S_ISDIR(m) (((m)&S_IFMT) == S_IFDIR) /* directory */ +#endif + +#if defined (S_IFREG) && !defined (S_ISREG) +#define S_ISREG(m) (((m)&S_IFMT) == S_IFREG) /* file */ +#endif + +#if defined (S_IFIFO) && !defined (S_ISFIFO) +#define S_ISFIFO(m) (((m)&S_IFMT) == S_IFIFO) /* fifo - named pipe */ +#endif + +#if defined (S_IFLNK) && !defined (S_ISLNK) +#define S_ISLNK(m) (((m)&S_IFMT) == S_IFLNK) /* symbolic link */ +#endif + +#if defined (S_IFSOCK) && !defined (S_ISSOCK) +#define S_ISSOCK(m) (((m)&S_IFMT) == S_IFSOCK) /* socket */ +#endif + +/* + * POSIX 1003.1 5.6.1.2 File Modes + */ + +#if !defined (S_IRWXU) +# if !defined (S_IREAD) +# define S_IREAD 00400 +# define S_IWRITE 00200 +# define S_IEXEC 00100 +# endif /* S_IREAD */ + +# if !defined (S_IRUSR) +# define S_IRUSR S_IREAD /* read, owner */ +# define S_IWUSR S_IWRITE /* write, owner */ +# define S_IXUSR S_IEXEC /* execute, owner */ + +# define S_IRGRP (S_IREAD >> 3) /* read, group */ +# define S_IWGRP (S_IWRITE >> 3) /* write, group */ +# define S_IXGRP (S_IEXEC >> 3) /* execute, group */ + +# define S_IROTH (S_IREAD >> 6) /* read, other */ +# define S_IWOTH (S_IWRITE >> 6) /* write, other */ +# define S_IXOTH (S_IEXEC >> 6) /* execute, other */ +# endif /* !S_IRUSR */ + +# define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) +# define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) +# define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) +#else /* !S_IRWXU */ + /* S_IRWXU is defined, but "group" and "other" bits might not be + (happens in certain versions of MinGW). */ +# if !defined (S_IRGRP) +# define S_IRGRP (S_IREAD >> 3) /* read, group */ +# define S_IWGRP (S_IWRITE >> 3) /* write, group */ +# define S_IXGRP (S_IEXEC >> 3) /* execute, group */ +# endif /* !S_IRGRP */ + +# if !defined (S_IROTH) +# define S_IROTH (S_IREAD >> 6) /* read, other */ +# define S_IWOTH (S_IWRITE >> 6) /* write, other */ +# define S_IXOTH (S_IEXEC >> 6) /* execute, other */ +# endif /* !S_IROTH */ +# if !defined (S_IRWXG) +# define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) +# endif +# if !defined (S_IRWXO) +# define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) +# endif +#endif /* !S_IRWXU */ + +/* These are non-standard, but are used in builtins.c$symbolic_umask() */ +#define S_IRUGO (S_IRUSR | S_IRGRP | S_IROTH) +#define S_IWUGO (S_IWUSR | S_IWGRP | S_IWOTH) +#define S_IXUGO (S_IXUSR | S_IXGRP | S_IXOTH) + +#endif /* _POSIXSTAT_H_ */ diff --git a/third_party/bash/posixtime.h b/third_party/bash/posixtime.h new file mode 100644 index 000000000..e70ebec67 --- /dev/null +++ b/third_party/bash/posixtime.h @@ -0,0 +1,84 @@ +/* posixtime.h -- wrapper for time.h, sys/times.h mess. */ + +/* Copyright (C) 1999-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _POSIXTIME_H_ +#define _POSIXTIME_H_ + +/* include this after config.h */ +/* Some systems require this, mostly for the definition of `struct timezone'. + For example, Dynix/ptx has that definition in rather than + sys/time.h */ +#if defined (HAVE_SYS_TIME_H) +# include +#endif +#include + +#if !defined (HAVE_SYSCONF) || !defined (_SC_CLK_TCK) +# if !defined (CLK_TCK) +# if defined (HZ) +# define CLK_TCK HZ +# else +# define CLK_TCK 60 /* 60HZ */ +# endif +# endif /* !CLK_TCK */ +#endif /* !HAVE_SYSCONF && !_SC_CLK_TCK */ + +#if !HAVE_TIMEVAL +struct timeval +{ + time_t tv_sec; + long int tv_usec; +}; +#endif + +#if !HAVE_GETTIMEOFDAY +extern int gettimeofday PARAMS((struct timeval *, void *)); +#endif + +/* These exist on BSD systems, at least. */ +#if !defined (timerclear) +# define timerclear(tvp) do { (tvp)->tv_sec = 0; (tvp)->tv_usec = 0; } while (0) +#endif +#if !defined (timerisset) +# define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) +#endif +#if !defined (timercmp) +# define timercmp(a, b, CMP) \ + (((a)->tv_sec == (b)->tv_sec) ? ((a)->tv_usec CMP (b)->tv_usec) \ + : ((a)->tv_sec CMP (b)->tv_sec)) +#endif + +/* These are non-standard. */ +#if !defined (timerisunset) +# define timerisunset(tvp) ((tvp)->tv_sec == 0 && (tvp)->tv_usec == 0) +#endif +#if !defined (timerset) +# define timerset(tvp, s, u) do { tvp->tv_sec = s; tvp->tv_usec = u; } while (0) +#endif + +#ifndef TIMEVAL_TO_TIMESPEC +# define TIMEVAL_TO_TIMESPEC(tv, ts) \ + do { \ + (ts)->tv_sec = (tv)->tv_sec; \ + (ts)->tv_nsec = (tv)->tv_usec * 1000; \ + } while (0) +#endif + +#endif /* _POSIXTIME_H_ */ diff --git a/third_party/bash/posixwait.h b/third_party/bash/posixwait.h new file mode 100644 index 000000000..63b59c24a --- /dev/null +++ b/third_party/bash/posixwait.h @@ -0,0 +1,107 @@ +/* posixwait.h -- job control definitions from POSIX 1003.1 */ + +/* Copyright (C) 1997 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_POSIXWAIT_H_) +# define _POSIXWAIT_H_ + +/* If _POSIX_VERSION is not defined, we assume that defines + a `union wait' and various macros used to manipulate it. Look in + unionwait.h for the things we expect to find. */ +#if defined (HAVE_SYS_WAIT_H) +# include +#else /* !HAVE_SYS_WAIT_H */ +# if !defined (_POSIX_VERSION) +# include "unionwait.h" +# endif +#endif /* !HAVE_SYS_WAIT_H */ + +/* How to get the status of a job. For Posix, this is just an + int, but for other systems we have to crack the union wait. */ +#if !defined (_POSIX_VERSION) +typedef union wait WAIT; +# define WSTATUS(t) (t.w_status) +#else /* _POSIX_VERSION */ +typedef int WAIT; +# define WSTATUS(t) (t) +#endif /* _POSIX_VERSION */ + +/* Make sure that parameters to wait3 are defined. */ +#if !defined (WNOHANG) +# define WNOHANG 1 +# define WUNTRACED 2 +#endif /* WNOHANG */ + +/* More Posix P1003.1 definitions. In the POSIX versions, the parameter is + passed as an `int', in the non-POSIX version, as `union wait'. */ +#if defined (_POSIX_VERSION) + +# if !defined (WSTOPSIG) +# define WSTOPSIG(s) ((s) >> 8) +# endif /* !WSTOPSIG */ + +# if !defined (WTERMSIG) +# define WTERMSIG(s) ((s) & 0177) +# endif /* !WTERMSIG */ + +# if !defined (WEXITSTATUS) +# define WEXITSTATUS(s) ((s) >> 8) +# endif /* !WEXITSTATUS */ + +# if !defined (WIFSTOPPED) +# define WIFSTOPPED(s) (((s) & 0177) == 0177) +# endif /* !WIFSTOPPED */ + +# if !defined (WIFEXITED) +# define WIFEXITED(s) (((s) & 0377) == 0) +# endif /* !WIFEXITED */ + +# if !defined (WIFSIGNALED) +# define WIFSIGNALED(s) (!WIFSTOPPED(s) && !WIFEXITED(s)) +# endif /* !WIFSIGNALED */ + +# if !defined (WIFCORED) +# if defined (WCOREDUMP) +# define WIFCORED(s) (WCOREDUMP(s)) +# else +# define WIFCORED(s) ((s) & 0200) +# endif +# endif /* !WIFCORED */ + +#else /* !_POSIX_VERSION */ + +# if !defined (WSTOPSIG) +# define WSTOPSIG(s) ((s).w_stopsig) +# endif /* !WSTOPSIG */ + +# if !defined (WTERMSIG) +# define WTERMSIG(s) ((s).w_termsig) +# endif /* !WTERMSIG */ + +# if !defined (WEXITSTATUS) +# define WEXITSTATUS(s) ((s).w_retcode) +# endif /* !WEXITSTATUS */ + +# if !defined (WIFCORED) +# define WIFCORED(s) ((s).w_coredump) +# endif /* !WIFCORED */ + +#endif /* !_POSIX_VERSION */ + +#endif /* !_POSIXWAIT_H_ */ diff --git a/third_party/bash/print_cmd.c b/third_party/bash/print_cmd.c new file mode 100644 index 000000000..e7bffc8cb --- /dev/null +++ b/third_party/bash/print_cmd.c @@ -0,0 +1,1654 @@ +/* print_command -- A way to make readable commands from a command tree. */ + +/* Copyright (C) 1989-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#if defined (PREFER_STDARG) +# include +#else +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#define NEED_XTRACE_SET_DECL + +#include "shell.h" +#include "flags.h" +#include "y.tab.h" /* use <...> so we pick it up from the build directory */ +#include "input.h" + +#include "shmbutil.h" + +#include "common.h" + +#if !HAVE_DECL_PRINTF +extern int printf PARAMS((const char *, ...)); /* Yuck. Double yuck. */ +#endif + +static int indentation; +static int indentation_amount = 4; + +#if defined (PREFER_STDARG) +typedef void PFUNC PARAMS((const char *, ...)); + +static void cprintf PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); +static void xprintf PARAMS((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); +#else +#define PFUNC VFunction +static void cprintf (); +static void xprintf (); +#endif + +static void reset_locals PARAMS((void)); +static void newline PARAMS((char *)); +static void indent PARAMS((int)); +static void semicolon PARAMS((void)); +static void the_printed_command_resize PARAMS((int)); + +static void make_command_string_internal PARAMS((COMMAND *)); +static void _print_word_list PARAMS((WORD_LIST *, char *, PFUNC *)); +static void command_print_word_list PARAMS((WORD_LIST *, char *)); +static void print_case_clauses PARAMS((PATTERN_LIST *)); +static void print_redirection_list PARAMS((REDIRECT *)); +static void print_redirection PARAMS((REDIRECT *)); +static void print_heredoc_header PARAMS((REDIRECT *)); +static void print_heredoc_body PARAMS((REDIRECT *)); +static void print_heredocs PARAMS((REDIRECT *)); +static void print_heredoc_bodies PARAMS((REDIRECT *)); +static void print_deferred_heredocs PARAMS((const char *)); + +static void print_for_command PARAMS((FOR_COM *)); +#if defined (ARITH_FOR_COMMAND) +static void print_arith_for_command PARAMS((ARITH_FOR_COM *)); +#endif +#if defined (SELECT_COMMAND) +static void print_select_command PARAMS((SELECT_COM *)); +#endif +static void print_group_command PARAMS((GROUP_COM *)); +static void print_case_command PARAMS((CASE_COM *)); +static void print_while_command PARAMS((WHILE_COM *)); +static void print_until_command PARAMS((WHILE_COM *)); +static void print_until_or_while PARAMS((WHILE_COM *, char *)); +static void print_if_command PARAMS((IF_COM *)); +#if defined (COND_COMMAND) +static void print_cond_node PARAMS((COND_COM *)); +#endif +static void print_function_def PARAMS((FUNCTION_DEF *)); + +#define PRINTED_COMMAND_INITIAL_SIZE 64 +#define PRINTED_COMMAND_GROW_SIZE 128 + +char *the_printed_command = (char *)NULL; +int the_printed_command_size = 0; +int command_string_index = 0; + +int xtrace_fd = -1; +FILE *xtrace_fp = 0; + +#define CHECK_XTRACE_FP xtrace_fp = (xtrace_fp ? xtrace_fp : stderr) + +/* shell expansion characters: used in print_redirection_list */ +#define EXPCHAR(c) ((c) == '{' || (c) == '~' || (c) == '$' || (c) == '`') + +#define PRINT_DEFERRED_HEREDOCS(x) \ + do { \ + if (deferred_heredocs) \ + print_deferred_heredocs (x); \ + } while (0) + +/* Non-zero means the stuff being printed is inside of a function def. */ +static int inside_function_def; +static int skip_this_indent; +static int was_heredoc; +static int printing_connection; +static int printing_comsub; +static REDIRECT *deferred_heredocs; + +/* The depth of the group commands that we are currently printing. This + includes the group command that is a function body. */ +static int group_command_nesting; + +/* A buffer to indicate the indirection level (PS4) when set -x is enabled. */ +static char *indirection_string = 0; +static int indirection_stringsiz = 0; + +/* Print COMMAND (a command tree) on standard output. */ +void +print_command (command) + COMMAND *command; +{ + command_string_index = 0; + printf ("%s", make_command_string (command)); +} + +/* Make a string which is the printed representation of the command + tree in COMMAND. We return this string. However, the string is + not consed, so you have to do that yourself if you want it to + remain around. */ +char * +make_command_string (command) + COMMAND *command; +{ + command_string_index = was_heredoc = 0; + deferred_heredocs = 0; + make_command_string_internal (command); + return (the_printed_command); +} + +/* Print a command substitution after parsing it in parse_comsub to turn it + back into an external representation without turning newlines into `;'. + Placeholder for other changes, if any are necessary. */ +char * +print_comsub (command) + COMMAND *command; +{ + char *ret; + + printing_comsub++; + ret = make_command_string (command); + printing_comsub--; + return ret; +} + +/* The internal function. This is the real workhorse. */ +static void +make_command_string_internal (command) + COMMAND *command; +{ + char s[3]; + + if (command == 0) + cprintf (""); + else + { + if (skip_this_indent) + skip_this_indent--; + else + indent (indentation); + + if (command->flags & CMD_TIME_PIPELINE) + { + cprintf ("time "); + if (command->flags & CMD_TIME_POSIX) + cprintf ("-p "); + } + + if (command->flags & CMD_INVERT_RETURN) + cprintf ("! "); + + switch (command->type) + { + case cm_for: + print_for_command (command->value.For); + break; + +#if defined (ARITH_FOR_COMMAND) + case cm_arith_for: + print_arith_for_command (command->value.ArithFor); + break; +#endif + +#if defined (SELECT_COMMAND) + case cm_select: + print_select_command (command->value.Select); + break; +#endif + + case cm_case: + print_case_command (command->value.Case); + break; + + case cm_while: + print_while_command (command->value.While); + break; + + case cm_until: + print_until_command (command->value.While); + break; + + case cm_if: + print_if_command (command->value.If); + break; + +#if defined (DPAREN_ARITHMETIC) + case cm_arith: + print_arith_command (command->value.Arith->exp); + break; +#endif + +#if defined (COND_COMMAND) + case cm_cond: + print_cond_command (command->value.Cond); + break; +#endif + + case cm_simple: + print_simple_command (command->value.Simple); + break; + + case cm_connection: + + skip_this_indent++; + printing_connection++; + make_command_string_internal (command->value.Connection->first); + + switch (command->value.Connection->connector) + { + case '&': + case '|': + { + char c = command->value.Connection->connector; + + s[0] = ' '; + s[1] = c; + s[2] = '\0'; + + print_deferred_heredocs (s); + + if (c != '&' || command->value.Connection->second) + { + cprintf (" "); + skip_this_indent++; + } + } + break; + + case AND_AND: + print_deferred_heredocs (" && "); + if (command->value.Connection->second) + skip_this_indent++; + break; + + case OR_OR: + print_deferred_heredocs (" || "); + if (command->value.Connection->second) + skip_this_indent++; + break; + + case ';': + case '\n': /* special case this */ + { + char c = command->value.Connection->connector; + + s[0] = printing_comsub ? c : ';'; + s[1] = '\0'; + + if (deferred_heredocs == 0) + { + if (was_heredoc == 0) + cprintf ("%s", s); /* inside_function_def? */ + else + was_heredoc = 0; + } + else + /* print_deferred_heredocs special-cases `;' */ + print_deferred_heredocs (inside_function_def ? "" : ";"); + + if (inside_function_def) + cprintf ("\n"); + else + { + if (c == ';') + cprintf (" "); + if (command->value.Connection->second) + skip_this_indent++; + } + break; + } + + default: + cprintf (_("print_command: bad connector `%d'"), + command->value.Connection->connector); + break; + } + + make_command_string_internal (command->value.Connection->second); + PRINT_DEFERRED_HEREDOCS (""); + printing_connection--; + break; + + case cm_function_def: + print_function_def (command->value.Function_def); + break; + + case cm_group: + print_group_command (command->value.Group); + break; + + case cm_subshell: + cprintf ("( "); + skip_this_indent++; + make_command_string_internal (command->value.Subshell->command); + PRINT_DEFERRED_HEREDOCS (""); + cprintf (" )"); + break; + + case cm_coproc: + cprintf ("coproc %s ", command->value.Coproc->name); + skip_this_indent++; + make_command_string_internal (command->value.Coproc->command); + break; + + default: + command_error ("print_command", CMDERR_BADTYPE, command->type, 0); + break; + } + + + if (command->redirects) + { + cprintf (" "); + print_redirection_list (command->redirects); + } + } +} + +static void +_print_word_list (list, separator, pfunc) + WORD_LIST *list; + char *separator; + PFUNC *pfunc; +{ + WORD_LIST *w; + + for (w = list; w; w = w->next) + (*pfunc) ("%s%s", w->word->word, w->next ? separator : ""); +} + +void +print_word_list (list, separator) + WORD_LIST *list; + char *separator; +{ + _print_word_list (list, separator, xprintf); +} + +void +xtrace_set (fd, fp) + int fd; + FILE *fp; +{ + if (fd >= 0 && sh_validfd (fd) == 0) + { + internal_error (_("xtrace_set: %d: invalid file descriptor"), fd); + return; + } + if (fp == 0) + { + internal_error (_("xtrace_set: NULL file pointer")); + return; + } + if (fd >= 0 && fileno (fp) != fd) + internal_warning (_("xtrace fd (%d) != fileno xtrace fp (%d)"), fd, fileno (fp)); + + xtrace_fd = fd; + xtrace_fp = fp; +} + +void +xtrace_init () +{ + xtrace_set (-1, stderr); +} + +void +xtrace_reset () +{ + if (xtrace_fd >= 0 && xtrace_fp) + { + fflush (xtrace_fp); + fclose (xtrace_fp); + } + else if (xtrace_fd >= 0) + close (xtrace_fd); + + xtrace_fd = -1; + xtrace_fp = stderr; +} + +void +xtrace_fdchk (fd) + int fd; +{ + if (fd == xtrace_fd) + xtrace_reset (); +} + +/* Return a string denoting what our indirection level is. */ + +char * +indirection_level_string () +{ + register int i, j; + char *ps4; + char ps4_firstc[MB_LEN_MAX+1]; + int ps4_firstc_len, ps4_len, ineed, old; + + ps4 = get_string_value ("PS4"); + if (indirection_string == 0) + indirection_string = xmalloc (indirection_stringsiz = 100); + indirection_string[0] = '\0'; + + if (ps4 == 0 || *ps4 == '\0') + return (indirection_string); + + old = change_flag ('x', FLAG_OFF); + ps4 = decode_prompt_string (ps4); + if (old) + change_flag ('x', FLAG_ON); + + if (ps4 == 0 || *ps4 == '\0') + { + FREE (ps4); + return (indirection_string); + } + +#if defined (HANDLE_MULTIBYTE) + ps4_len = strnlen (ps4, MB_CUR_MAX); + ps4_firstc_len = MBLEN (ps4, ps4_len); + if (ps4_firstc_len == 1 || ps4_firstc_len == 0 || ps4_firstc_len < 0) + { + ps4_firstc[0] = ps4[0]; + ps4_firstc[ps4_firstc_len = 1] = '\0'; + } + else + memcpy (ps4_firstc, ps4, ps4_firstc_len); +#else + ps4_firstc[0] = ps4[0]; + ps4_firstc[ps4_firstc_len = 1] = '\0'; +#endif + + /* Dynamically resize indirection_string so we have room for everything + and we don't have to truncate ps4 */ + ineed = (ps4_firstc_len * indirection_level) + strlen (ps4); + if (ineed > indirection_stringsiz - 1) + { + indirection_stringsiz = ineed + 1; + indirection_string = xrealloc (indirection_string, indirection_stringsiz); + } + + for (i = j = 0; ps4_firstc[0] && j < indirection_level && i < indirection_stringsiz - 1; i += ps4_firstc_len, j++) + { + if (ps4_firstc_len == 1) + indirection_string[i] = ps4_firstc[0]; + else + memcpy (indirection_string+i, ps4_firstc, ps4_firstc_len); + } + + for (j = ps4_firstc_len; *ps4 && ps4[j] && i < indirection_stringsiz - 1; i++, j++) + indirection_string[i] = ps4[j]; + + indirection_string[i] = '\0'; + free (ps4); + return (indirection_string); +} + +void +xtrace_print_assignment (name, value, assign_list, xflags) + char *name, *value; + int assign_list, xflags; +{ + char *nval; + + CHECK_XTRACE_FP; + + if (xflags) + fprintf (xtrace_fp, "%s", indirection_level_string ()); + + /* VALUE should not be NULL when this is called. */ + if (*value == '\0' || assign_list) + nval = value; + else if (sh_contains_shell_metas (value)) + nval = sh_single_quote (value); + else if (ansic_shouldquote (value)) + nval = ansic_quote (value, 0, (int *)0); + else + nval = value; + + if (assign_list) + fprintf (xtrace_fp, "%s=(%s)\n", name, nval); + else + fprintf (xtrace_fp, "%s=%s\n", name, nval); + + if (nval != value) + FREE (nval); + + fflush (xtrace_fp); +} + +/* A function to print the words of a simple command when set -x is on. Also used to + print the word list in a for or select command header; in that case, we suppress + quoting the words because they haven't been expanded yet. XTFLAGS&1 means to + print $PS4; XTFLAGS&2 means to suppress quoting the words in LIST. */ +void +xtrace_print_word_list (list, xtflags) + WORD_LIST *list; + int xtflags; +{ + WORD_LIST *w; + char *t, *x; + + CHECK_XTRACE_FP; + + if (xtflags&1) + fprintf (xtrace_fp, "%s", indirection_level_string ()); + + for (w = list; w; w = w->next) + { + t = w->word->word; + if (t == 0 || *t == '\0') + fprintf (xtrace_fp, "''%s", w->next ? " " : ""); + else if (xtflags & 2) + fprintf (xtrace_fp, "%s%s", t, w->next ? " " : ""); + else if (sh_contains_shell_metas (t)) + { + x = sh_single_quote (t); + fprintf (xtrace_fp, "%s%s", x, w->next ? " " : ""); + free (x); + } + else if (ansic_shouldquote (t)) + { + x = ansic_quote (t, 0, (int *)0); + fprintf (xtrace_fp, "%s%s", x, w->next ? " " : ""); + free (x); + } + else + fprintf (xtrace_fp, "%s%s", t, w->next ? " " : ""); + } + fprintf (xtrace_fp, "\n"); + fflush (xtrace_fp); +} + +static void +command_print_word_list (list, separator) + WORD_LIST *list; + char *separator; +{ + _print_word_list (list, separator, cprintf); +} + +void +print_for_command_head (for_command) + FOR_COM *for_command; +{ + cprintf ("for %s in ", for_command->name->word); + command_print_word_list (for_command->map_list, " "); +} + +void +xtrace_print_for_command_head (for_command) + FOR_COM *for_command; +{ + CHECK_XTRACE_FP; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "for %s in ", for_command->name->word); + xtrace_print_word_list (for_command->map_list, 2); +} + +static void +print_for_command (for_command) + FOR_COM *for_command; +{ + print_for_command_head (for_command); + cprintf (";"); + newline ("do\n"); + + indentation += indentation_amount; + make_command_string_internal (for_command->action); + PRINT_DEFERRED_HEREDOCS (""); + semicolon (); + indentation -= indentation_amount; + + newline ("done"); +} + +#if defined (ARITH_FOR_COMMAND) +static void +print_arith_for_command (arith_for_command) + ARITH_FOR_COM *arith_for_command; +{ + cprintf ("for (("); + command_print_word_list (arith_for_command->init, " "); + cprintf ("; "); + command_print_word_list (arith_for_command->test, " "); + cprintf ("; "); + command_print_word_list (arith_for_command->step, " "); + cprintf ("))"); + newline ("do\n"); + indentation += indentation_amount; + make_command_string_internal (arith_for_command->action); + PRINT_DEFERRED_HEREDOCS (""); + semicolon (); + indentation -= indentation_amount; + newline ("done"); +} +#endif /* ARITH_FOR_COMMAND */ + +#if defined (SELECT_COMMAND) +void +print_select_command_head (select_command) + SELECT_COM *select_command; +{ + cprintf ("select %s in ", select_command->name->word); + command_print_word_list (select_command->map_list, " "); +} + +void +xtrace_print_select_command_head (select_command) + SELECT_COM *select_command; +{ + CHECK_XTRACE_FP; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "select %s in ", select_command->name->word); + xtrace_print_word_list (select_command->map_list, 2); +} + +static void +print_select_command (select_command) + SELECT_COM *select_command; +{ + print_select_command_head (select_command); + + cprintf (";"); + newline ("do\n"); + indentation += indentation_amount; + make_command_string_internal (select_command->action); + PRINT_DEFERRED_HEREDOCS (""); + semicolon (); + indentation -= indentation_amount; + newline ("done"); +} +#endif /* SELECT_COMMAND */ + +static void +print_group_command (group_command) + GROUP_COM *group_command; +{ + group_command_nesting++; + cprintf ("{ "); + + if (inside_function_def == 0) + skip_this_indent++; + else + { + /* This is a group command { ... } inside of a function + definition, and should be printed as a multiline group + command, using the current indentation. */ + cprintf ("\n"); + indentation += indentation_amount; + } + + make_command_string_internal (group_command->command); + PRINT_DEFERRED_HEREDOCS (""); + + if (inside_function_def) + { + cprintf ("\n"); + indentation -= indentation_amount; + indent (indentation); + } + else + { + semicolon (); + cprintf (" "); + } + + cprintf ("}"); + + group_command_nesting--; +} + +void +print_case_command_head (case_command) + CASE_COM *case_command; +{ + cprintf ("case %s in ", case_command->word->word); +} + +void +xtrace_print_case_command_head (case_command) + CASE_COM *case_command; +{ + CHECK_XTRACE_FP; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "case %s in\n", case_command->word->word); +} + +static void +print_case_command (case_command) + CASE_COM *case_command; +{ + print_case_command_head (case_command); + + if (case_command->clauses) + print_case_clauses (case_command->clauses); + newline ("esac"); +} + +static void +print_case_clauses (clauses) + PATTERN_LIST *clauses; +{ + indentation += indentation_amount; + while (clauses) + { + newline (""); + command_print_word_list (clauses->patterns, " | "); + cprintf (")\n"); + indentation += indentation_amount; + make_command_string_internal (clauses->action); + indentation -= indentation_amount; + PRINT_DEFERRED_HEREDOCS (""); + if (clauses->flags & CASEPAT_FALLTHROUGH) + newline (";&"); + else if (clauses->flags & CASEPAT_TESTNEXT) + newline (";;&"); + else + newline (";;"); + clauses = clauses->next; + } + indentation -= indentation_amount; +} + +static void +print_while_command (while_command) + WHILE_COM *while_command; +{ + print_until_or_while (while_command, "while"); +} + +static void +print_until_command (while_command) + WHILE_COM *while_command; +{ + print_until_or_while (while_command, "until"); +} + +static void +print_until_or_while (while_command, which) + WHILE_COM *while_command; + char *which; +{ + cprintf ("%s ", which); + skip_this_indent++; + make_command_string_internal (while_command->test); + PRINT_DEFERRED_HEREDOCS (""); + semicolon (); + cprintf (" do\n"); /* was newline ("do\n"); */ + indentation += indentation_amount; + make_command_string_internal (while_command->action); + PRINT_DEFERRED_HEREDOCS (""); + indentation -= indentation_amount; + semicolon (); + newline ("done"); +} + +static void +print_if_command (if_command) + IF_COM *if_command; +{ + cprintf ("if "); + skip_this_indent++; + make_command_string_internal (if_command->test); + semicolon (); + cprintf (" then\n"); + indentation += indentation_amount; + make_command_string_internal (if_command->true_case); + PRINT_DEFERRED_HEREDOCS (""); + indentation -= indentation_amount; + + if (if_command->false_case) + { + semicolon (); + newline ("else\n"); + indentation += indentation_amount; + make_command_string_internal (if_command->false_case); + PRINT_DEFERRED_HEREDOCS (""); + indentation -= indentation_amount; + } + semicolon (); + newline ("fi"); +} + +#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) +void +print_arith_command (arith_cmd_list) + WORD_LIST *arith_cmd_list; +{ + cprintf ("(("); + command_print_word_list (arith_cmd_list, " "); + cprintf ("))"); +} +#endif + +#if defined (COND_COMMAND) +static void +print_cond_node (cond) + COND_COM *cond; +{ + if (cond->flags & CMD_INVERT_RETURN) + cprintf ("! "); + + if (cond->type == COND_EXPR) + { + cprintf ("( "); + print_cond_node (cond->left); + cprintf (" )"); + } + else if (cond->type == COND_AND) + { + print_cond_node (cond->left); + cprintf (" && "); + print_cond_node (cond->right); + } + else if (cond->type == COND_OR) + { + print_cond_node (cond->left); + cprintf (" || "); + print_cond_node (cond->right); + } + else if (cond->type == COND_UNARY) + { + cprintf ("%s", cond->op->word); + cprintf (" "); + print_cond_node (cond->left); + } + else if (cond->type == COND_BINARY) + { + print_cond_node (cond->left); + cprintf (" "); + cprintf ("%s", cond->op->word); + cprintf (" "); + print_cond_node (cond->right); + } + else if (cond->type == COND_TERM) + { + cprintf ("%s", cond->op->word); /* need to add quoting here */ + } +} + +void +print_cond_command (cond) + COND_COM *cond; +{ + cprintf ("[[ "); + print_cond_node (cond); + cprintf (" ]]"); +} + +#ifdef DEBUG +void +debug_print_word_list (s, list, sep) + char *s; + WORD_LIST *list; + char *sep; +{ + WORD_LIST *w; + + if (s) + fprintf (stderr, "%s: ", s); + for (w = list; w; w = w->next) + fprintf (stderr, "%s%s", w->word->word, w->next ? sep : ""); + fprintf (stderr, "\n"); +} + +void +debug_print_cond_command (cond) + COND_COM *cond; +{ + fprintf (stderr, "DEBUG: "); + command_string_index = 0; + print_cond_command (cond); + fprintf (stderr, "%s\n", the_printed_command); +} +#endif + +void +xtrace_print_cond_term (type, invert, op, arg1, arg2) + int type, invert; + WORD_DESC *op; + char *arg1, *arg2; +{ + CHECK_XTRACE_FP; + command_string_index = 0; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "[[ "); + if (invert) + fprintf (xtrace_fp, "! "); + + if (type == COND_UNARY) + { + fprintf (xtrace_fp, "%s ", op->word); + fprintf (xtrace_fp, "%s", (arg1 && *arg1) ? arg1 : "''"); + } + else if (type == COND_BINARY) + { + fprintf (xtrace_fp, "%s", (arg1 && *arg1) ? arg1 : "''"); + fprintf (xtrace_fp, " %s ", op->word); + fprintf (xtrace_fp, "%s", (arg2 && *arg2) ? arg2 : "''"); + } + + fprintf (xtrace_fp, " ]]\n"); + + fflush (xtrace_fp); +} +#endif /* COND_COMMAND */ + +#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) +/* A function to print the words of an arithmetic command when set -x is on. */ +void +xtrace_print_arith_cmd (list) + WORD_LIST *list; +{ + WORD_LIST *w; + + CHECK_XTRACE_FP; + fprintf (xtrace_fp, "%s", indirection_level_string ()); + fprintf (xtrace_fp, "(( "); + for (w = list; w; w = w->next) + fprintf (xtrace_fp, "%s%s", w->word->word, w->next ? " " : ""); + fprintf (xtrace_fp, " ))\n"); + + fflush (xtrace_fp); +} +#endif + +void +print_simple_command (simple_command) + SIMPLE_COM *simple_command; +{ + if (simple_command->words) + command_print_word_list (simple_command->words, " "); + + if (simple_command->redirects) + { + if (simple_command->words) + cprintf (" "); + print_redirection_list (simple_command->redirects); + } +} + +static void +print_heredocs (heredocs) + REDIRECT *heredocs; +{ + REDIRECT *hdtail; + + cprintf (" "); + for (hdtail = heredocs; hdtail; hdtail = hdtail->next) + { + print_redirection (hdtail); + cprintf ("\n"); + } + was_heredoc = 1; +} + +static void +print_heredoc_bodies (heredocs) + REDIRECT *heredocs; +{ + REDIRECT *hdtail; + + cprintf ("\n"); + for (hdtail = heredocs; hdtail; hdtail = hdtail->next) + { + print_heredoc_body (hdtail); + cprintf ("\n"); + } + was_heredoc = 1; +} + +/* Print heredocs that are attached to the command before the connector + represented by CSTRING. The parsing semantics require us to print the + here-doc delimiters, then the connector (CSTRING), then the here-doc + bodies. We print the here-doc delimiters in print_redirection_list + and print the connector and the bodies here. We don't print the connector + if it's a `;', but we use it to note not to print an extra space after the + last heredoc body and newline. */ +static void +print_deferred_heredocs (cstring) + const char *cstring; +{ + /* We now print the heredoc headers in print_redirection_list */ + if (cstring && cstring[0] && (cstring[0] != ';' || cstring[1])) + cprintf ("%s", cstring); + if (deferred_heredocs) + { + print_heredoc_bodies (deferred_heredocs); + if (cstring && cstring[0] && (cstring[0] != ';' || cstring[1])) + cprintf (" "); /* make sure there's at least one space */ + dispose_redirects (deferred_heredocs); + was_heredoc = 1; + } + deferred_heredocs = (REDIRECT *)NULL; +} + +static void +print_redirection_list (redirects) + REDIRECT *redirects; +{ + REDIRECT *heredocs, *hdtail, *newredir; + char *rw; + + heredocs = (REDIRECT *)NULL; + hdtail = heredocs; + + was_heredoc = 0; + while (redirects) + { + /* Defer printing the here document bodiess until we've printed the rest of the + redirections, but print the headers in the order they're given. */ + if (redirects->instruction == r_reading_until || redirects->instruction == r_deblank_reading_until) + { + newredir = copy_redirect (redirects); + newredir->next = (REDIRECT *)NULL; + + print_heredoc_header (newredir); + + if (heredocs) + { + hdtail->next = newredir; + hdtail = newredir; + } + else + hdtail = heredocs = newredir; + } +#if 0 + /* Remove this heuristic now that the command printing code doesn't + unconditionally put in the redirector file descriptor. */ + else if (redirects->instruction == r_duplicating_output_word && (redirects->flags & REDIR_VARASSIGN) == 0 && redirects->redirector.dest == 1) + { + /* Temporarily translate it as the execution code does. */ + rw = redirects->redirectee.filename->word; + if (rw && *rw != '-' && DIGIT (*rw) == 0 && EXPCHAR (*rw) == 0) + redirects->instruction = r_err_and_out; + print_redirection (redirects); + redirects->instruction = r_duplicating_output_word; + } +#endif + else + print_redirection (redirects); + + redirects = redirects->next; + if (redirects) + cprintf (" "); + } + + /* Now that we've printed all the other redirections (on one line), + print the here documents. If we're printing a connection, we wait until + we print the connector symbol, then we print the here document bodies */ + if (heredocs && printing_connection) + deferred_heredocs = heredocs; + else if (heredocs) + { + print_heredoc_bodies (heredocs); + dispose_redirects (heredocs); + } +} + +static void +print_heredoc_header (redirect) + REDIRECT *redirect; +{ + int kill_leading; + char *x; + + kill_leading = redirect->instruction == r_deblank_reading_until; + + /* Here doc header */ + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redirect->redirector.filename->word); + else if (redirect->redirector.dest != 0) + cprintf ("%d", redirect->redirector.dest); + + /* If the here document delimiter is quoted, single-quote it. */ + if (redirect->redirectee.filename->flags & W_QUOTED) + { + x = sh_single_quote (redirect->here_doc_eof); + cprintf ("<<%s%s", kill_leading ? "-" : "", x); + free (x); + } + else + cprintf ("<<%s%s", kill_leading ? "-" : "", redirect->here_doc_eof); +} + +static void +print_heredoc_body (redirect) + REDIRECT *redirect; +{ + /* Here doc body */ + cprintf ("%s%s", redirect->redirectee.filename->word, redirect->here_doc_eof); +} + +static void +print_redirection (redirect) + REDIRECT *redirect; +{ + int redirector, redir_fd; + WORD_DESC *redirectee, *redir_word; + + redirectee = redirect->redirectee.filename; + redir_fd = redirect->redirectee.dest; + + redir_word = redirect->redirector.filename; + redirector = redirect->redirector.dest; + + switch (redirect->instruction) + { + case r_input_direction: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 0) + cprintf ("%d", redirector); + cprintf ("< %s", redirectee->word); + break; + + case r_output_direction: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 1) + cprintf ("%d", redirector); + cprintf ("> %s", redirectee->word); + break; + + case r_inputa_direction: /* Redirection created by the shell. */ + cprintf ("&"); + break; + + case r_output_force: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 1) + cprintf ("%d", redirector); + cprintf (">| %s", redirectee->word); + break; + + case r_appending_to: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 1) + cprintf ("%d", redirector); + cprintf (">> %s", redirectee->word); + break; + + case r_input_output: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 1) + cprintf ("%d", redirector); + cprintf ("<> %s", redirectee->word); + break; + + case r_deblank_reading_until: + case r_reading_until: + print_heredoc_header (redirect); + cprintf ("\n"); + print_heredoc_body (redirect); + break; + + case r_reading_string: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}", redir_word->word); + else if (redirector != 0) + cprintf ("%d", redirector); +#if 0 + /* Don't need to check whether or not to requote, since original quotes + are still intact. The only thing that has happened is that $'...' + has been replaced with 'expanded ...'. */ + if (ansic_shouldquote (redirect->redirectee.filename->word)) + { + char *x; + x = ansic_quote (redirect->redirectee.filename->word, 0, (int *)0); + cprintf ("<<< %s", x); + free (x); + } + else +#endif + cprintf ("<<< %s", redirect->redirectee.filename->word); + break; + + case r_duplicating_input: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}<&%d", redir_word->word, redir_fd); + else + cprintf ("%d<&%d", redirector, redir_fd); + break; + + case r_duplicating_output: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&%d", redir_word->word, redir_fd); + else + cprintf ("%d>&%d", redirector, redir_fd); + break; + + case r_duplicating_input_word: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}<&%s", redir_word->word, redirectee->word); + else if (redirector == 0) + cprintf ("<&%s", redirectee->word); + else + cprintf ("%d<&%s", redirector, redirectee->word); + break; + + case r_duplicating_output_word: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&%s", redir_word->word, redirectee->word); + else if (redirector == 1) + cprintf (">&%s", redirectee->word); + else + cprintf ("%d>&%s", redirector, redirectee->word); + break; + + case r_move_input: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}<&%d-", redir_word->word, redir_fd); + else + cprintf ("%d<&%d-", redirector, redir_fd); + break; + + case r_move_output: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&%d-", redir_word->word, redir_fd); + else + cprintf ("%d>&%d-", redirector, redir_fd); + break; + + case r_move_input_word: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}<&%s-", redir_word->word, redirectee->word); + else + cprintf ("%d<&%s-", redirector, redirectee->word); + break; + + case r_move_output_word: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&%s-", redir_word->word, redirectee->word); + else + cprintf ("%d>&%s-", redirector, redirectee->word); + break; + + case r_close_this: + if (redirect->rflags & REDIR_VARASSIGN) + cprintf ("{%s}>&-", redir_word->word); + else + cprintf ("%d>&-", redirector); + break; + + case r_err_and_out: + cprintf ("&> %s", redirectee->word); + break; + + case r_append_err_and_out: + cprintf ("&>> %s", redirectee->word); + break; + } +} + +static void +reset_locals () +{ + inside_function_def = 0; + indentation = 0; + printing_connection = 0; + deferred_heredocs = 0; + printing_comsub = 0; +} + +static void +print_function_def (func) + FUNCTION_DEF *func; +{ + COMMAND *cmdcopy; + REDIRECT *func_redirects; + + func_redirects = NULL; + /* When in posix mode, print functions as posix specifies them. */ + if (posixly_correct == 0) + cprintf ("function %s () \n", func->name->word); + else + cprintf ("%s () \n", func->name->word); + add_unwind_protect (reset_locals, 0); + + indent (indentation); + cprintf ("{ \n"); + + inside_function_def++; + indentation += indentation_amount; + + cmdcopy = copy_command (func->command); + if (cmdcopy->type == cm_group) + { + func_redirects = cmdcopy->redirects; + cmdcopy->redirects = (REDIRECT *)NULL; + } + make_command_string_internal (cmdcopy->type == cm_group + ? cmdcopy->value.Group->command + : cmdcopy); + PRINT_DEFERRED_HEREDOCS (""); + + remove_unwind_protect (); + indentation -= indentation_amount; + inside_function_def--; + + if (func_redirects) + { /* { */ + newline ("} "); + print_redirection_list (func_redirects); + cmdcopy->redirects = func_redirects; + } + else + newline ("}"); + + dispose_command (cmdcopy); +} + +/* Return the string representation of the named function. + NAME is the name of the function. + COMMAND is the function body. It should be a GROUP_COM. + flags&FUNC_MULTILINE is non-zero to pretty-print, or zero for all on one line. + flags&FUNC_EXTERNAL means convert from internal to external form + */ +char * +named_function_string (name, command, flags) + char *name; + COMMAND *command; + int flags; +{ + char *result; + int old_indent, old_amount; + COMMAND *cmdcopy; + REDIRECT *func_redirects; + + old_indent = indentation; + old_amount = indentation_amount; + command_string_index = was_heredoc = 0; + deferred_heredocs = 0; + printing_comsub = 0; + + if (name && *name) + { + if (find_reserved_word (name) >= 0) + cprintf ("function "); + cprintf ("%s ", name); + } + + cprintf ("() "); + + if ((flags & FUNC_MULTILINE) == 0) + { + indentation = 1; + indentation_amount = 0; + } + else + { + cprintf ("\n"); + indentation += indentation_amount; + } + + inside_function_def++; + + cprintf ((flags & FUNC_MULTILINE) ? "{ \n" : "{ "); + + cmdcopy = copy_command (command); + /* Take any redirections specified in the function definition (which should + apply to the function as a whole) and save them for printing later. */ + func_redirects = (REDIRECT *)NULL; + if (cmdcopy->type == cm_group) + { + func_redirects = cmdcopy->redirects; + cmdcopy->redirects = (REDIRECT *)NULL; + } + make_command_string_internal (cmdcopy->type == cm_group + ? cmdcopy->value.Group->command + : cmdcopy); + PRINT_DEFERRED_HEREDOCS (""); + + indentation = old_indent; + indentation_amount = old_amount; + inside_function_def--; + + if (func_redirects) + { /* { */ + newline ("} "); + print_redirection_list (func_redirects); + cmdcopy->redirects = func_redirects; + } + else + newline ("}"); + + result = the_printed_command; + + if ((flags & FUNC_MULTILINE) == 0) + { +#if 0 + register int i; + for (i = 0; result[i]; i++) + if (result[i] == '\n') + { + strcpy (result + i, result + i + 1); + --i; + } +#else + if (result[2] == '\n') /* XXX -- experimental */ + memmove (result + 2, result + 3, strlen (result) - 2); +#endif + } + + dispose_command (cmdcopy); + + if (flags & FUNC_EXTERNAL) + result = remove_quoted_escapes (result); + + return (result); +} + +static void +newline (string) + char *string; +{ + cprintf ("\n"); + indent (indentation); + if (string && *string) + cprintf ("%s", string); +} + +static char *indentation_string; +static int indentation_size; + +static void +indent (amount) + int amount; +{ + register int i; + + RESIZE_MALLOCED_BUFFER (indentation_string, 0, amount, indentation_size, 16); + + for (i = 0; amount > 0; amount--) + indentation_string[i++] = ' '; + indentation_string[i] = '\0'; + cprintf ("%s", indentation_string); +} + +static void +semicolon () +{ + if (command_string_index > 0 && + (the_printed_command[command_string_index - 1] == '&' || + the_printed_command[command_string_index - 1] == '\n')) + return; + cprintf (";"); +} + +/* How to make the string. */ +static void +#if defined (PREFER_STDARG) +cprintf (const char *control, ...) +#else +cprintf (control, va_alist) + const char *control; + va_dcl +#endif +{ + register const char *s; + char char_arg[2], *argp, intbuf[INT_STRLEN_BOUND (unsigned int) + 1]; + int digit_arg, arg_len, c; + va_list args; + + SH_VA_START (args, control); + + arg_len = strlen (control); + the_printed_command_resize (arg_len + 1); + + char_arg[1] = '\0'; + s = control; + while (s && *s) + { + c = *s++; + argp = (char *)NULL; + if (c != '%' || !*s) + { + char_arg[0] = c; + argp = char_arg; + arg_len = 1; + } + else + { + c = *s++; + switch (c) + { + case '%': + char_arg[0] = c; + argp = char_arg; + arg_len = 1; + break; + + case 's': + argp = va_arg (args, char *); + arg_len = strlen (argp); + break; + + case 'd': + /* Represent an out-of-range file descriptor with an out-of-range + integer value. We can do this because the only use of `%d' in + the calls to cprintf is to output a file descriptor number for + a redirection. */ + digit_arg = va_arg (args, int); + if (digit_arg < 0) + { + sprintf (intbuf, "%u", (unsigned int)-1); + argp = intbuf; + } + else + argp = inttostr (digit_arg, intbuf, sizeof (intbuf)); + arg_len = strlen (argp); + break; + + case 'c': + char_arg[0] = va_arg (args, int); + argp = char_arg; + arg_len = 1; + break; + + default: + programming_error (_("cprintf: `%c': invalid format character"), c); + /*NOTREACHED*/ + } + } + + if (argp && arg_len) + { + the_printed_command_resize (arg_len + 1); + FASTCOPY (argp, the_printed_command + command_string_index, arg_len); + command_string_index += arg_len; + } + } + + va_end (args); + + the_printed_command[command_string_index] = '\0'; +} + +/* Ensure that there is enough space to stuff LENGTH characters into + THE_PRINTED_COMMAND. */ +static void +the_printed_command_resize (length) + int length; +{ + if (the_printed_command == 0) + { + the_printed_command_size = (length + PRINTED_COMMAND_INITIAL_SIZE - 1) & ~(PRINTED_COMMAND_INITIAL_SIZE - 1); + the_printed_command = (char *)xmalloc (the_printed_command_size); + command_string_index = 0; + } + else if ((command_string_index + length) >= the_printed_command_size) + { + int new; + new = command_string_index + length + 1; + + /* Round up to the next multiple of PRINTED_COMMAND_GROW_SIZE. */ + new = (new + PRINTED_COMMAND_GROW_SIZE - 1) & ~(PRINTED_COMMAND_GROW_SIZE - 1); + the_printed_command_size = new; + + the_printed_command = (char *)xrealloc (the_printed_command, the_printed_command_size); + } +} + +#if defined (HAVE_VPRINTF) +/* ``If vprintf is available, you may assume that vfprintf and vsprintf are + also available.'' */ + +static void +#if defined (PREFER_STDARG) +xprintf (const char *format, ...) +#else +xprintf (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + SH_VA_START (args, format); + + vfprintf (stdout, format, args); + va_end (args); +} + +#else + +static void +xprintf (format, arg1, arg2, arg3, arg4, arg5) + const char *format; +{ + printf (format, arg1, arg2, arg3, arg4, arg5); +} + +#endif /* !HAVE_VPRINTF */ diff --git a/third_party/bash/psize.c b/third_party/bash/psize.c new file mode 100644 index 000000000..66f5f918a --- /dev/null +++ b/third_party/bash/psize.c @@ -0,0 +1,79 @@ +/* psize.c - Find pipe size. */ + +/* Copyright (C) 1987, 1991 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* Write output in 128-byte chunks until we get a sigpipe or write gets an + EPIPE. Then report how many bytes we wrote. We assume that this is the + pipe size. */ +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#ifndef _MINIX +#include "bashtypes.h" +#endif +#include +#include + +#include "command.h" +#include "general.h" +#include "sig.h" + +#ifndef errno +extern int errno; +#endif + +int nw; + +sighandler +sigpipe (sig) + int sig; +{ + fprintf (stderr, "%d\n", nw); + exit (0); +} + +int +main (argc, argv) + int argc; + char **argv; +{ + char buf[128]; + register int i; + + for (i = 0; i < 128; i++) + buf[i] = ' '; + + signal (SIGPIPE, sigpipe); + + nw = 0; + for (;;) + { + int n; + n = write (1, buf, 128); + nw += n; + } + return (0); +} diff --git a/third_party/bash/quit.h b/third_party/bash/quit.h new file mode 100644 index 000000000..0af1d121f --- /dev/null +++ b/third_party/bash/quit.h @@ -0,0 +1,75 @@ +/* quit.h -- How to handle SIGINT gracefully. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_QUIT_H_) +#define _QUIT_H_ + +#include "sig.h" /* for sig_atomic_t */ + +/* Non-zero means SIGINT has already occurred. */ +extern volatile sig_atomic_t interrupt_state; +extern volatile sig_atomic_t terminating_signal; + +/* Macro to call a great deal. SIGINT just sets the interrupt_state variable. + When it is safe, put QUIT in the code, and the "interrupt" will take + place. The same scheme is used for terminating signals (e.g., SIGHUP) + and the terminating_signal variable. That calls a function which will + end up exiting the shell. */ +#define QUIT \ + do { \ + if (terminating_signal) termsig_handler (terminating_signal); \ + if (interrupt_state) throw_to_top_level (); \ + } while (0) + +#define SETINTERRUPT interrupt_state = 1 +#define CLRINTERRUPT interrupt_state = 0 + +#define ADDINTERRUPT interrupt_state++ +#define DELINTERRUPT interrupt_state-- + +#define ISINTERRUPT interrupt_state != 0 + +/* The same sort of thing, this time just for signals that would ordinarily + cause the shell to terminate. */ + +#define CHECK_TERMSIG \ + do { \ + if (terminating_signal) termsig_handler (terminating_signal); \ + } while (0) + +#define LASTSIG() \ + (terminating_signal ? terminating_signal : (interrupt_state ? SIGINT : 0)) + +#define CHECK_WAIT_INTR \ + do { \ + if (wait_intr_flag && wait_signal_received && this_shell_builtin && (this_shell_builtin == wait_builtin)) \ + sh_longjmp (wait_intr_buf, 1); \ + } while (0) + +#define RESET_SIGTERM \ + do { \ + sigterm_received = 0; \ + } while (0) + +#define CHECK_SIGTERM \ + do { \ + if (sigterm_received) termsig_handler (SIGTERM); \ + } while (0) +#endif /* _QUIT_H_ */ diff --git a/third_party/bash/random.c b/third_party/bash/random.c new file mode 100644 index 000000000..1eaa71aac --- /dev/null +++ b/third_party/bash/random.c @@ -0,0 +1,240 @@ +/* random.c -- Functions for managing 16-bit and 32-bit random numbers. */ + +/* Copyright (C) 2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_SYS_RANDOM_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif +#include "filecntl.h" + +#include +#include "bashansi.h" + +#include "shell.h" + +extern time_t shell_start_time; + +extern int last_random_value; + +static u_bits32_t intrand32 PARAMS((u_bits32_t)); +static u_bits32_t genseed PARAMS((void)); + +static u_bits32_t brand32 PARAMS((void)); +static void sbrand32 PARAMS((u_bits32_t)); +static void perturb_rand32 PARAMS((void)); + +/* The random number seed. You can change this by setting RANDOM. */ +static u_bits32_t rseed = 1; + +/* Returns a 32-bit pseudo-random number. */ +static u_bits32_t +intrand32 (last) + u_bits32_t last; +{ + /* Minimal Standard generator from + "Random number generators: good ones are hard to find", + Park and Miller, Communications of the ACM, vol. 31, no. 10, + October 1988, p. 1195. Filtered through FreeBSD. + + x(n+1) = 16807 * x(n) mod (m). + + We split up the calculations to avoid overflow. + + h = last / q; l = x - h * q; t = a * l - h * r + m = 2147483647, a = 16807, q = 127773, r = 2836 + + There are lots of other combinations of constants to use; look at + https://www.gnu.org/software/gsl/manual/html_node/Other-random-number-generators.html#Other-random-number-generators */ + + bits32_t h, l, t; + u_bits32_t ret; + + /* Can't seed with 0. */ + ret = (last == 0) ? 123459876 : last; + h = ret / 127773; + l = ret - (127773 * h); + t = 16807 * l - 2836 * h; + ret = (t < 0) ? t + 0x7fffffff : t; + + return (ret); +} + +static u_bits32_t +genseed () +{ + struct timeval tv; + u_bits32_t iv; + + gettimeofday (&tv, NULL); + iv = (u_bits32_t)seedrand; /* let the compiler truncate */ + iv = tv.tv_sec ^ tv.tv_usec ^ getpid () ^ getppid () ^ current_user.uid ^ iv; + return (iv); +} + +#define BASH_RAND_MAX 32767 /* 0x7fff - 16 bits */ + +/* Returns a pseudo-random number between 0 and 32767. */ +int +brand () +{ + unsigned int ret; + + rseed = intrand32 (rseed); + if (shell_compatibility_level > 50) + ret = (rseed >> 16) ^ (rseed & 65535); + else + ret = rseed; + return (ret & BASH_RAND_MAX); +} + +/* Set the random number generator seed to SEED. */ +void +sbrand (seed) + unsigned long seed; +{ + rseed = seed; + last_random_value = 0; +} + +void +seedrand () +{ + u_bits32_t iv; + + iv = genseed (); + sbrand (iv); +} + +static u_bits32_t rseed32 = 1073741823; +static int last_rand32; + +static int urandfd = -1; + +#define BASH_RAND32_MAX 0x7fffffff /* 32 bits */ + +/* Returns a 32-bit pseudo-random number between 0 and 4294967295. */ +static u_bits32_t +brand32 () +{ + u_bits32_t ret; + + rseed32 = intrand32 (rseed32); + return (rseed32 & BASH_RAND32_MAX); +} + +static void +sbrand32 (seed) + u_bits32_t seed; +{ + last_rand32 = rseed32 = seed; +} + +void +seedrand32 () +{ + u_bits32_t iv; + + iv = genseed (); + sbrand32 (iv); +} + +static void +perturb_rand32 () +{ + rseed32 ^= genseed (); +} + +/* Force another attempt to open /dev/urandom on the next call to get_urandom32 */ +void +urandom_close () +{ + if (urandfd >= 0) + close (urandfd); + urandfd = -1; +} + +#if !defined (HAVE_GETRANDOM) +/* Imperfect emulation of getrandom(2). */ +#ifndef GRND_NONBLOCK +# define GRND_NONBLOCK 1 +# define GRND_RANDOM 2 +#endif + +static ssize_t +getrandom (buf, len, flags) + void *buf; + size_t len; + unsigned int flags; +{ + int oflags; + ssize_t r; + static int urand_unavail = 0; + +#if HAVE_GETENTROPY + r = getentropy (buf, len); + return (r == 0) ? len : -1; +#endif + + if (urandfd == -1 && urand_unavail == 0) + { + oflags = O_RDONLY; + if (flags & GRND_NONBLOCK) + oflags |= O_NONBLOCK; + urandfd = open ("/dev/urandom", oflags, 0); + if (urandfd >= 0) + SET_CLOSE_ON_EXEC (urandfd); + else + { + urand_unavail = 1; + return -1; + } + } + if (urandfd >= 0 && (r = read (urandfd, buf, len)) == len) + return (r); + return -1; +} +#endif + +u_bits32_t +get_urandom32 () +{ + u_bits32_t ret; + + if (getrandom ((void *)&ret, sizeof (ret), GRND_NONBLOCK) == sizeof (ret)) + return (last_rand32 = ret); + +#if defined (HAVE_ARC4RANDOM) + ret = arc4random (); +#else + if (subshell_environment) + perturb_rand32 (); + do + ret = brand32 (); + while (ret == last_rand32); +#endif + return (last_rand32 = ret); +} diff --git a/third_party/bash/read.c b/third_party/bash/read.c new file mode 100644 index 000000000..fc4346dda --- /dev/null +++ b/third_party/bash/read.c @@ -0,0 +1,1205 @@ +/* read.c, created from read.def. */ +#line 22 "./read.def" + +#line 70 "./read.def" + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" + +#include + +#include "bashansi.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include + +#ifdef __CYGWIN__ +# include +# include +#endif + +#include "bashintl.h" + +#include "shell.h" +#include "common.h" +#include "bashgetopt.h" +#include "trap.h" + +#include "shtty.h" + +#if defined (READLINE) +#include "bashline.h" +#include "third_party/readline/readline.h" +#endif + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +#include "shmbutil.h" +#include "timer.h" + +#if !defined(errno) +extern int errno; +#endif + +struct ttsave +{ + int fd; + TTYSTRUCT attrs; +}; + +#if defined (READLINE) +static void reset_attempted_completion_function PARAMS((char *)); +static int set_itext PARAMS((void)); +static char *edit_line PARAMS((char *, char *)); +static void set_eol_delim PARAMS((int)); +static void reset_eol_delim PARAMS((char *)); +static void set_readline_timeout PARAMS((sh_timer *t, time_t, long)); +#endif +static SHELL_VAR *bind_read_variable PARAMS((char *, char *, int)); +#if defined (HANDLE_MULTIBYTE) +static int read_mbchar PARAMS((int, char *, int, int, int)); +#endif +static void ttyrestore PARAMS((struct ttsave *)); + +static sighandler sigalrm PARAMS((int)); +static void reset_timeout PARAMS((void)); + +/* Try this to see what the rest of the shell can do with the information. */ +sh_timer *read_timeout; + +static int reading, tty_modified; +static SigHandler *old_alrm; +static unsigned char delim; + +static struct ttsave termsave; + +/* In all cases, SIGALRM just sets a flag that we check periodically. This + avoids problems with the semi-tricky stuff we do with the xfree of + input_string at the top of the unwind-protect list (see below). */ + +/* Set a flag that check_read_timeout can check. This relies on zread or + read_builtin calling trap.c:check_signals() (which calls check_read_timeout()) */ +static sighandler +sigalrm (s) + int s; +{ + /* Display warning if this is called without read_timeout set? */ + if (read_timeout) + read_timeout->alrmflag = 1; +} + +static void +reset_timeout () +{ + /* Cancel alarm before restoring signal handler. */ + if (read_timeout) + shtimer_clear (read_timeout); + read_timeout = 0; +} + +void +check_read_timeout () +{ + if (read_timeout && shtimer_chktimeout (read_timeout)) + sh_longjmp (read_timeout->jmpenv, 1); +} + +int +read_builtin_timeout (fd) + int fd; +{ + if ((read_timeout == 0) || + (read_timeout->fd != fd) || + (read_timeout->tmout.tv_sec == 0 && read_timeout->tmout.tv_usec == 0)) + return 0; + + return ((read_timeout->flags & SHTIMER_ALARM) ? shtimer_alrm (read_timeout) + : shtimer_select (read_timeout)); +} + +/* Read the value of the shell variables whose names follow. + The reading is done from the current input stream, whatever + that may be. Successive words of the input line are assigned + to the variables mentioned in LIST. The last variable in LIST + gets the remainder of the words on the line. If no variables + are mentioned in LIST, then the default variable is $REPLY. */ +int +read_builtin (list) + WORD_LIST *list; +{ + register char *varname; + int size, nr, pass_next, saw_escape, eof, opt, retval, code, print_ps2, nflag; + volatile int i; + int input_is_tty, input_is_pipe, unbuffered_read, skip_ctlesc, skip_ctlnul; + int raw, edit, nchars, silent, have_timeout, ignore_delim, fd; + int lastsig, t_errno; + int mb_cur_max; + unsigned int tmsec, tmusec; + long ival, uval; + intmax_t intval; + char c; + char *input_string, *orig_input_string, *ifs_chars, *prompt, *arrayname; + char *e, *t, *t1, *ps2, *tofree; + struct stat tsb; + SHELL_VAR *var; + TTYSTRUCT ttattrs, ttset; + sigset_t chldset, prevset; +#if defined (ARRAY_VARS) + WORD_LIST *alist; + int vflags; +#endif + int bindflags; +#if defined (READLINE) + char *rlbuf, *itext; + int rlind; + FILE *save_instream; +#endif + + USE_VAR(size); + USE_VAR(i); + USE_VAR(pass_next); + USE_VAR(print_ps2); + USE_VAR(saw_escape); + USE_VAR(input_is_pipe); +/* USE_VAR(raw); */ + USE_VAR(edit); + USE_VAR(tmsec); + USE_VAR(tmusec); + USE_VAR(nchars); + USE_VAR(silent); + USE_VAR(ifs_chars); + USE_VAR(prompt); + USE_VAR(arrayname); +#if defined (READLINE) + USE_VAR(rlbuf); + USE_VAR(rlind); + USE_VAR(itext); +#endif + USE_VAR(list); + USE_VAR(ps2); + USE_VAR(lastsig); + + reading = tty_modified = 0; + read_timeout = 0; + + i = 0; /* Index into the string that we are reading. */ + raw = edit = 0; /* Not reading raw input by default. */ + silent = 0; + arrayname = prompt = (char *)NULL; + fd = 0; /* file descriptor to read from */ + +#if defined (READLINE) + rlbuf = itext = (char *)0; + rlind = 0; +#endif + + mb_cur_max = MB_CUR_MAX; + tmsec = tmusec = 0; /* no timeout */ + nr = nchars = input_is_tty = input_is_pipe = unbuffered_read = have_timeout = 0; + delim = '\n'; /* read until newline */ + ignore_delim = nflag = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "ersa:d:i:n:p:t:u:N:")) != -1) + { + switch (opt) + { + case 'r': + raw = 1; + break; + case 'p': + prompt = list_optarg; + break; + case 's': + silent = 1; + break; + case 'e': +#if defined (READLINE) + edit = 1; +#endif + break; + case 'i': +#if defined (READLINE) + itext = list_optarg; +#endif + break; +#if defined (ARRAY_VARS) + case 'a': + arrayname = list_optarg; + break; +#endif + case 't': + code = uconvert (list_optarg, &ival, &uval, (char **)NULL); + if (code == 0 || ival < 0 || uval < 0) + { + builtin_error (_("%s: invalid timeout specification"), list_optarg); + return (EXECUTION_FAILURE); + } + else + { + have_timeout = 1; + tmsec = ival; + tmusec = uval; + } + break; + case 'N': + ignore_delim = 1; + delim = -1; + case 'n': + nflag = 1; + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (int)intval) + { + sh_invalidnum (list_optarg); + return (EXECUTION_FAILURE); + } + else + nchars = intval; + break; + case 'u': + code = legal_number (list_optarg, &intval); + if (code == 0 || intval < 0 || intval != (int)intval) + { + builtin_error (_("%s: invalid file descriptor specification"), list_optarg); + return (EXECUTION_FAILURE); + } + else + fd = intval; + if (sh_validfd (fd) == 0) + { + builtin_error (_("%d: invalid file descriptor: %s"), fd, strerror (errno)); + return (EXECUTION_FAILURE); + } + break; + case 'd': + delim = *list_optarg; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + list = loptend; + + /* `read -t 0 var' tests whether input is available with select/FIONREAD, + and fails if those are unavailable */ + if (have_timeout && tmsec == 0 && tmusec == 0) + return (input_avail (fd) ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + + /* Convenience: check early whether or not the first of possibly several + variable names is a valid identifier, and bail early if so. */ +#if defined (ARRAY_VARS) + if (list) + SET_VFLAGS (list->word->flags, vflags, bindflags); + if (list && legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word, vflags) == 0) +#else + bindflags = 0; + if (list && legal_identifier (list->word->word) == 0) +#endif + { + sh_invalidid (list->word->word); + return (EXECUTION_FAILURE); + } + + /* If we're asked to ignore the delimiter, make sure we do. */ + if (ignore_delim) + delim = -1; + + /* IF IFS is unset, we use the default of " \t\n". */ + ifs_chars = getifs (); + if (ifs_chars == 0) /* XXX - shouldn't happen */ + ifs_chars = ""; + /* If we want to read exactly NCHARS chars, don't split on IFS */ + if (ignore_delim) + ifs_chars = ""; + for (skip_ctlesc = skip_ctlnul = 0, e = ifs_chars; *e; e++) + skip_ctlesc |= *e == CTLESC, skip_ctlnul |= *e == CTLNUL; + + input_string = (char *)xmalloc (size = 112); /* XXX was 128 */ + input_string[0] = '\0'; + + /* More input and options validation */ + if (nflag == 1 && nchars == 0) + { + retval = read (fd, &c, 0); + retval = (retval >= 0) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; + goto assign_vars; /* bail early if asked to read 0 chars */ + } + + /* $TMOUT, if set, is the default timeout for read. */ + if (have_timeout == 0 && (e = get_string_value ("TMOUT"))) + { + code = uconvert (e, &ival, &uval, (char **)NULL); + if (code == 0 || ival < 0 || uval < 0) + tmsec = tmusec = 0; + else + { + tmsec = ival; + tmusec = uval; + } + } + +#if defined (SIGCHLD) + sigemptyset (&chldset); + sigprocmask (SIG_BLOCK, (sigset_t *)0, &chldset); + sigaddset (&chldset, SIGCHLD); +#endif + + begin_unwind_frame ("read_builtin"); + +#if defined (BUFFERED_INPUT) + if (interactive == 0 && default_buffered_input >= 0 && fd_is_bash_input (fd)) + sync_buffered_stream (default_buffered_input); +#endif + +#if 1 + input_is_tty = isatty (fd); +#else + input_is_tty = 1; +#endif + if (input_is_tty == 0) +#ifndef __CYGWIN__ + input_is_pipe = (lseek (fd, 0L, SEEK_CUR) < 0) && (errno == ESPIPE); +#else + input_is_pipe = 1; +#endif + + /* If the -p, -e or -s flags were given, but input is not coming from the + terminal, turn them off. */ + if ((prompt || edit || silent) && input_is_tty == 0) + { + prompt = (char *)NULL; +#if defined (READLINE) + itext = (char *)NULL; +#endif + edit = silent = 0; + } + +#if defined (READLINE) + if (edit) + add_unwind_protect (xfree, rlbuf); +#endif + + pass_next = 0; /* Non-zero signifies last char was backslash. */ + saw_escape = 0; /* Non-zero signifies that we saw an escape char */ + + if (tmsec > 0 || tmusec > 0) + { + /* Turn off the timeout if stdin is a regular file (e.g. from + input redirection). */ + if ((fstat (fd, &tsb) < 0) || S_ISREG (tsb.st_mode)) + tmsec = tmusec = 0; + } + + if (tmsec > 0 || tmusec > 0) + { + read_timeout = shtimer_alloc (); + read_timeout->flags = SHTIMER_LONGJMP; + +#if defined (HAVE_SELECT) + read_timeout->flags |= (edit || posixly_correct) ? SHTIMER_ALARM : SHTIMER_SELECT; +#else + read_timeout->flags |= SHTIMER_ALARM; +#endif + read_timeout->fd = fd; + + read_timeout->alrm_handler = sigalrm; + } + + if (tmsec > 0 || tmusec > 0) + { + code = setjmp_nosigs (read_timeout->jmpenv); + if (code) + { + reset_timeout (); + sigprocmask (SIG_SETMASK, &prevset, (sigset_t *)0); + + /* Tricky. The top of the unwind-protect stack is the free of + input_string. We want to run all the rest and use input_string, + so we have to save input_string temporarily, run the unwind- + protects, then restore input_string so we can use it later */ + orig_input_string = 0; + input_string[i] = '\0'; /* make sure it's terminated */ + if (i == 0) + { + t = (char *)xmalloc (1); + t[0] = 0; + } + else + t = savestring (input_string); + + run_unwind_frame ("read_builtin"); + input_string = t; + retval = 128+SIGALRM; + goto assign_vars; + } + if (interactive_shell == 0) + initialize_terminating_signals (); + add_unwind_protect (reset_timeout, (char *)NULL); +#if defined (READLINE) + if (edit) + { + add_unwind_protect (reset_attempted_completion_function, (char *)NULL); + add_unwind_protect (bashline_reset_event_hook, (char *)NULL); + set_readline_timeout (read_timeout, tmsec, tmusec); + } + else +#endif + shtimer_set (read_timeout, tmsec, tmusec); + } + + /* If we've been asked to read only NCHARS chars, or we're using some + character other than newline to terminate the line, do the right + thing to readline or the tty. */ + if (nchars > 0 || delim != '\n') + { +#if defined (READLINE) + if (edit) + { + if (nchars > 0) + { + unwind_protect_int (rl_num_chars_to_read); + rl_num_chars_to_read = nchars; + } + if (delim != '\n') + { + set_eol_delim (delim); + add_unwind_protect (reset_eol_delim, (char *)NULL); + } + } + else +#endif + if (input_is_tty) + { + /* ttsave() */ + termsave.fd = fd; + ttgetattr (fd, &ttattrs); + termsave.attrs = ttattrs; + + ttset = ttattrs; + i = silent ? ttfd_cbreak (fd, &ttset) : ttfd_onechar (fd, &ttset); + if (i < 0) + sh_ttyerror (1); + tty_modified = 1; + add_unwind_protect ((Function *)ttyrestore, (char *)&termsave); + if (interactive_shell == 0) + initialize_terminating_signals (); + } + } + else if (silent) /* turn off echo but leave term in canonical mode */ + { + /* ttsave (); */ + termsave.fd = fd; + ttgetattr (fd, &ttattrs); + termsave.attrs = ttattrs; + + ttset = ttattrs; + i = ttfd_noecho (fd, &ttset); /* ttnoecho (); */ + if (i < 0) + sh_ttyerror (1); + + tty_modified = 1; + add_unwind_protect ((Function *)ttyrestore, (char *)&termsave); + if (interactive_shell == 0) + initialize_terminating_signals (); + } + +#if defined (READLINE) + save_instream = 0; + if (edit && fd != 0) + { + if (bash_readline_initialized == 0) + initialize_readline (); + + unwind_protect_var (rl_instream); + save_instream = rl_instream; + rl_instream = fdopen (fd, "r"); + } +#endif + + /* This *must* be the top unwind-protect on the stack, so the manipulation + of the unwind-protect stack after the realloc() works right. */ + add_unwind_protect (xfree, input_string); + + check_read_timeout (); + /* These only matter if edit == 0 */ + if ((nchars > 0) && (input_is_tty == 0) && ignore_delim) /* read -N */ + unbuffered_read = 2; +#if 0 + else if ((nchars > 0) || (delim != '\n') || input_is_pipe) +#else + else if (((nchars > 0 || delim != '\n') && input_is_tty) || input_is_pipe) + unbuffered_read = 1; +#endif + if (prompt && edit == 0) + { + fprintf (stderr, "%s", prompt); + fflush (stderr); + } + +#if defined (__CYGWIN__) && defined (O_TEXT) + setmode (0, O_TEXT); +#endif + + ps2 = 0; + for (print_ps2 = eof = retval = 0;;) + { + check_read_timeout (); + +#if defined (READLINE) + if (edit) + { + /* If we have a null delimiter, don't treat NULL as ending the line */ + if (rlbuf && rlbuf[rlind] == '\0' && delim != '\0') + { + free (rlbuf); + rlbuf = (char *)0; + } +#if defined (SIGCHLD) + if (tmsec > 0 || tmusec > 0) + sigprocmask (SIG_SETMASK, &chldset, &prevset); +#endif + if (rlbuf == 0) + { + reading = 1; + rlbuf = edit_line (prompt ? prompt : "", itext); + reading = 0; + rlind = 0; + } +#if defined (SIGCHLD) + if (tmsec > 0 || tmusec > 0) + sigprocmask (SIG_SETMASK, &prevset, (sigset_t *)0); +#endif + if (rlbuf == 0) + { + eof = 1; + break; + } + c = rlbuf[rlind++]; + } + else + { +#endif + + if (print_ps2) + { + if (ps2 == 0) + ps2 = get_string_value ("PS2"); + fprintf (stderr, "%s", ps2 ? ps2 : ""); + fflush (stderr); + print_ps2 = 0; + } + + reading = 1; + check_read_timeout (); + errno = 0; + +#if defined (SIGCHLD) + if (tmsec > 0 || tmusec > 0) + sigprocmask (SIG_SETMASK, &chldset, &prevset); +#endif + if (unbuffered_read == 2) + retval = posixly_correct ? zreadintr (fd, &c, 1) : zreadn (fd, &c, nchars - nr); + else if (unbuffered_read) + retval = posixly_correct ? zreadintr (fd, &c, 1) : zread (fd, &c, 1); + else + retval = posixly_correct ? zreadcintr (fd, &c) : zreadc (fd, &c); +#if defined (SIGCHLD) + if (tmsec > 0 || tmusec > 0) + sigprocmask (SIG_SETMASK, &prevset, (sigset_t *)0); +#endif + + reading = 0; + + if (retval <= 0) + { + int t; + + t = errno; + if (retval < 0 && errno == EINTR) + { + check_signals (); /* in case we didn't call zread via zreadc */ + lastsig = LASTSIG(); + if (lastsig == 0) + lastsig = trapped_signal_received; +#if 0 + run_pending_traps (); /* because interrupt_immediately is not set */ +#endif + } + else + lastsig = 0; + if (terminating_signal && tty_modified) + ttyrestore (&termsave); /* fix terminal before exiting */ + CHECK_TERMSIG; + eof = 1; + errno = t; /* preserve it for the error message below */ + break; + } + + QUIT; /* in case we didn't call check_signals() */ +#if defined (READLINE) + } +#endif + + if (retval <= 0) /* XXX shouldn't happen */ + check_read_timeout (); + + /* XXX -- use i + mb_cur_max (at least 4) for multibyte/read_mbchar */ + if (i + (mb_cur_max > 4 ? mb_cur_max : 4) >= size) + { + char *t; + t = (char *)xrealloc (input_string, size += 128); + + /* Only need to change unwind-protect if input_string changes */ + if (t != input_string) + { + input_string = t; + remove_unwind_protect (); + add_unwind_protect (xfree, input_string); + } + } + + /* If the next character is to be accepted verbatim, a backslash + newline pair still disappears from the input. */ + if (pass_next) + { + pass_next = 0; + if (c == '\n') + { + if (skip_ctlesc == 0 && i > 0) + i--; /* back up over the CTLESC */ + if (interactive && input_is_tty && raw == 0) + print_ps2 = 1; + } + else + goto add_char; + continue; + } + + /* This may cause problems if IFS contains CTLESC */ + if (c == '\\' && raw == 0) + { + pass_next++; + if (skip_ctlesc == 0) + { + saw_escape++; + input_string[i++] = CTLESC; + } + continue; + } + + if (ignore_delim == 0 && (unsigned char)c == delim) + break; + + if (c == '\0' && delim != '\0') + continue; /* skip NUL bytes in input */ + + if ((skip_ctlesc == 0 && c == CTLESC) || (skip_ctlnul == 0 && c == CTLNUL)) + { + saw_escape++; + input_string[i++] = CTLESC; + } + +add_char: + input_string[i++] = c; + check_read_timeout (); + +#if defined (HANDLE_MULTIBYTE) + /* XXX - what if C == 127? Can DEL introduce a multibyte sequence? */ + if (mb_cur_max > 1 && is_basic (c) == 0) + { + input_string[i] = '\0'; /* for simplicity and debugging */ + /* If we got input from readline, grab the next multibyte char from + rlbuf. */ +# if defined (READLINE) + if (edit) + { + size_t clen; + clen = mbrlen (rlbuf + rlind - 1, mb_cur_max, (mbstate_t *)NULL); + /* We only deal with valid multibyte sequences longer than one + byte. If we get anything else, we leave the one character + copied and move on to the next. */ + if ((int)clen > 1) + { + memcpy (input_string+i, rlbuf+rlind, clen-1); + i += clen - 1; + rlind += clen - 1; + } + } + else +# endif + if (locale_utf8locale == 0 || ((c & 0x80) != 0)) + i += read_mbchar (fd, input_string, i, c, unbuffered_read); + } +#endif + + nr++; + + if (nchars > 0 && nr >= nchars) + break; + } + input_string[i] = '\0'; + check_read_timeout (); + +#if defined (READLINE) + if (edit) + free (rlbuf); +#endif + + if (retval < 0) + { + t_errno = errno; + if (errno != EINTR) + builtin_error (_("read error: %d: %s"), fd, strerror (errno)); + run_unwind_frame ("read_builtin"); + return ((t_errno != EINTR) ? EXECUTION_FAILURE : 128+lastsig); + } + + if (tmsec > 0 || tmusec > 0) + reset_timeout (); + + if (nchars > 0 || delim != '\n') + { +#if defined (READLINE) + if (edit) + { + if (nchars > 0) + rl_num_chars_to_read = 0; + if (delim != '\n') + reset_eol_delim ((char *)NULL); + } + else +#endif + if (input_is_tty) + ttyrestore (&termsave); + } + else if (silent) + ttyrestore (&termsave); + + if (unbuffered_read == 0) + zsyncfd (fd); + +#if defined (READLINE) + if (save_instream) + rl_instream = save_instream; /* can't portably free it */ +#endif + + discard_unwind_frame ("read_builtin"); + + retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS; + +assign_vars: + +#if defined (ARRAY_VARS) + /* If -a was given, take the string read, break it into a list of words, + an assign them to `arrayname' in turn. */ + if (arrayname) + { + /* pass 1 for flags arg to clear the existing array + 2 to check for a + valid identifier. */ + var = builtin_find_indexed_array (arrayname, 3); + if (var == 0) + { + free (input_string); + return EXECUTION_FAILURE; /* readonly or noassign */ + } + + alist = list_string (input_string, ifs_chars, 0); + if (alist) + { + if (saw_escape) + dequote_list (alist); + else + word_list_remove_quoted_nulls (alist); + assign_array_var_from_word_list (var, alist, 0); + dispose_words (alist); + } + free (input_string); + return (retval); + } +#endif /* ARRAY_VARS */ + + /* If there are no variables, save the text of the line read to the + variable $REPLY. ksh93 strips leading and trailing IFS whitespace, + so that `read x ; echo "$x"' and `read ; echo "$REPLY"' behave the + same way, but I believe that the difference in behaviors is useful + enough to not do it. Without the bash behavior, there is no way + to read a line completely without interpretation or modification + unless you mess with $IFS (e.g., setting it to the empty string). + If you disagree, change the occurrences of `#if 0' to `#if 1' below. */ + if (list == 0) + { +#if 0 + orig_input_string = input_string; + for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++) + ; + input_string = t; + input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape); +#endif + + if (saw_escape) + { + t = dequote_string (input_string); + var = bind_variable ("REPLY", t, 0); + free (t); + } + else + var = bind_variable ("REPLY", input_string, 0); + if (var == 0 || readonly_p (var) || noassign_p (var)) + retval = EXECUTION_FAILURE; + else + VUNSETATTR (var, att_invisible); + + free (input_string); + return (retval); + } + + /* This code implements the Posix.2 spec for splitting the words + read and assigning them to variables. */ + orig_input_string = input_string; + + /* Remove IFS white space at the beginning of the input string. If + $IFS is null, no field splitting is performed. */ + for (t = input_string; ifs_chars && *ifs_chars && spctabnl(*t) && isifs(*t); t++) + ; + input_string = t; + for (; list->next; list = list->next) + { + varname = list->word->word; +#if defined (ARRAY_VARS) + SET_VFLAGS (list->word->flags, vflags, bindflags); + if (legal_identifier (varname) == 0 && valid_array_reference (varname, vflags) == 0) +#else + if (legal_identifier (varname) == 0) +#endif + { + sh_invalidid (varname); + free (orig_input_string); + return (EXECUTION_FAILURE); + } + + /* If there are more variables than words read from the input, + the remaining variables are set to the empty string. */ + if (*input_string) + { + /* This call updates INPUT_STRING. */ + t = get_word_from_string (&input_string, ifs_chars, &e); + if (t) + *e = '\0'; + /* Don't bother to remove the CTLESC unless we added one + somewhere while reading the string. */ + if (t && saw_escape) + { + t1 = dequote_string (t); + var = bind_read_variable (varname, t1, bindflags); + free (t1); + } + else + var = bind_read_variable (varname, t ? t : "", bindflags); + } + else + { + t = (char *)0; + var = bind_read_variable (varname, "", bindflags); + } + + FREE (t); + if (var == 0) + { + free (orig_input_string); + return (EXECUTION_FAILURE); + } + + stupidly_hack_special_variables (varname); + VUNSETATTR (var, att_invisible); + } + + /* Now assign the rest of the line to the last variable argument. */ +#if defined (ARRAY_VARS) + SET_VFLAGS (list->word->flags, vflags, bindflags); + if (legal_identifier (list->word->word) == 0 && valid_array_reference (list->word->word, vflags) == 0) +#else + if (legal_identifier (list->word->word) == 0) +#endif + { + sh_invalidid (list->word->word); + free (orig_input_string); + return (EXECUTION_FAILURE); + } + +#if 0 + /* This has to be done this way rather than using string_list + and list_string because Posix.2 says that the last variable gets the + remaining words and their intervening separators. */ + input_string = strip_trailing_ifs_whitespace (input_string, ifs_chars, saw_escape); +#else + /* Check whether or not the number of fields is exactly the same as the + number of variables. */ + tofree = NULL; + if (*input_string) + { + t1 = input_string; + t = get_word_from_string (&input_string, ifs_chars, &e); + if (*input_string == 0) + tofree = input_string = t; + else + { + input_string = strip_trailing_ifs_whitespace (t1, ifs_chars, saw_escape); + tofree = t; + } + } +#endif + + if (saw_escape && input_string && *input_string) + { + t = dequote_string (input_string); + var = bind_read_variable (list->word->word, t, bindflags); + free (t); + } + else + var = bind_read_variable (list->word->word, input_string ? input_string : "", bindflags); + + if (var) + { + stupidly_hack_special_variables (list->word->word); + VUNSETATTR (var, att_invisible); + } + else + retval = EXECUTION_FAILURE; + + FREE (tofree); + free (orig_input_string); + + return (retval); +} + +static SHELL_VAR * +bind_read_variable (name, value, flags) + char *name, *value; + int flags; +{ + SHELL_VAR *v; + + v = builtin_bind_variable (name, value, flags); + return (v == 0 ? v + : ((readonly_p (v) || noassign_p (v)) ? (SHELL_VAR *)NULL : v)); +} + +#if defined (HANDLE_MULTIBYTE) +static int +read_mbchar (fd, string, ind, ch, unbuffered) + int fd; + char *string; + int ind, ch, unbuffered; +{ + char mbchar[MB_LEN_MAX + 1]; + int i, n, r; + char c; + size_t ret; + mbstate_t ps, ps_back; + wchar_t wc; + + memset (&ps, '\0', sizeof (mbstate_t)); + memset (&ps_back, '\0', sizeof (mbstate_t)); + + mbchar[0] = ch; + i = 1; + for (n = 0; n <= MB_LEN_MAX; n++) + { + ps_back = ps; + ret = mbrtowc (&wc, mbchar, i, &ps); + if (ret == (size_t)-2) + { + ps = ps_back; + + /* We don't want to be interrupted during a multibyte char read */ + if (unbuffered == 2) + r = zreadn (fd, &c, 1); + else if (unbuffered) + r = zread (fd, &c, 1); + else + r = zreadc (fd, &c); + if (r <= 0) + goto mbchar_return; + mbchar[i++] = c; + continue; + } + else if (ret == (size_t)-1 || ret == (size_t)0 || ret > (size_t)0) + break; + } + +mbchar_return: + if (i > 1) /* read a multibyte char */ + /* mbchar[0] is already string[ind-1] */ + for (r = 1; r < i; r++) + string[ind+r-1] = mbchar[r]; + return i - 1; +} +#endif + + +static void +ttyrestore (ttp) + struct ttsave *ttp; +{ + ttsetattr (ttp->fd, &(ttp->attrs)); + tty_modified = 0; +} + +void +read_tty_cleanup () +{ + if (tty_modified) + ttyrestore (&termsave); +} + +int +read_tty_modified () +{ + return (tty_modified); +} + +#if defined (READLINE) +static rl_completion_func_t *old_attempted_completion_function = 0; +static rl_hook_func_t *old_startup_hook; +static char *deftext; + +static void +reset_attempted_completion_function (cp) + char *cp; +{ + if (rl_attempted_completion_function == 0 && old_attempted_completion_function) + rl_attempted_completion_function = old_attempted_completion_function; +} + +static int +set_itext () +{ + int r1, r2; + + r1 = r2 = 0; + if (old_startup_hook) + r1 = (*old_startup_hook) (); + if (deftext) + { + r2 = rl_insert_text (deftext); + deftext = (char *)NULL; + rl_startup_hook = old_startup_hook; + old_startup_hook = (rl_hook_func_t *)NULL; + } + return (r1 || r2); +} + +static char * +edit_line (p, itext) + char *p; + char *itext; +{ + char *ret; + int len; + + if (bash_readline_initialized == 0) + initialize_readline (); + + old_attempted_completion_function = rl_attempted_completion_function; + rl_attempted_completion_function = (rl_completion_func_t *)NULL; + bashline_set_event_hook (); + if (itext) + { + old_startup_hook = rl_startup_hook; + rl_startup_hook = set_itext; + deftext = itext; + } + + ret = readline (p); + + rl_attempted_completion_function = old_attempted_completion_function; + old_attempted_completion_function = (rl_completion_func_t *)NULL; + bashline_reset_event_hook (); + + if (ret == 0) + { + if (RL_ISSTATE (RL_STATE_TIMEOUT)) + { + sigalrm (SIGALRM); /* simulate receiving SIGALRM */ + check_read_timeout (); + } + return ret; + } + + len = strlen (ret); + ret = (char *)xrealloc (ret, len + 2); + ret[len++] = delim; + ret[len] = '\0'; + return ret; +} + +static void +set_readline_timeout (t, sec, usec) + sh_timer *t; + time_t sec; + long usec; +{ + t->tmout.tv_sec = sec; + t->tmout.tv_usec = usec; + rl_set_timeout (sec, usec); +} + +static int old_delim_ctype; +static rl_command_func_t *old_delim_func; +static int old_newline_ctype; +static rl_command_func_t *old_newline_func; + +static unsigned char delim_char; + +static void +set_eol_delim (c) + int c; +{ + Keymap cmap; + + if (bash_readline_initialized == 0) + initialize_readline (); + cmap = rl_get_keymap (); + + /* Save the old delimiter char binding */ + old_newline_ctype = cmap[RETURN].type; + old_newline_func = cmap[RETURN].function; + old_delim_ctype = cmap[c].type; + old_delim_func = cmap[c].function; + + /* Change newline to self-insert */ + cmap[RETURN].type = ISFUNC; + cmap[RETURN].function = rl_insert; + + /* Bind the delimiter character to accept-line. */ + cmap[c].type = ISFUNC; + cmap[c].function = rl_newline; + + delim_char = c; +} + +static void +reset_eol_delim (cp) + char *cp; +{ + Keymap cmap; + + cmap = rl_get_keymap (); + + cmap[RETURN].type = old_newline_ctype; + cmap[RETURN].function = old_newline_func; + + cmap[delim_char].type = old_delim_ctype; + cmap[delim_char].function = old_delim_func; +} +#endif diff --git a/third_party/bash/redir.c b/third_party/bash/redir.c new file mode 100644 index 000000000..6a9feac99 --- /dev/null +++ b/third_party/bash/redir.c @@ -0,0 +1,1528 @@ +/* redir.c -- Functions to perform input and output redirection. */ + +/* Copyright (C) 1997-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) + #pragma alloca +#endif /* _AIX && RISC6000 && !__GNUC__ */ + +#include +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "filecntl.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#if !defined (errno) +extern int errno; +#endif + +#include "bashansi.h" +#include "bashintl.h" +#include "memalloc.h" + +#define NEED_FPURGE_DECL + +#include "shell.h" +#include "flags.h" +#include "execute_cmd.h" +#include "redir.h" +#include "trap.h" + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +#include "pipesize.h" + +/* FreeBSD 13 can reliably handle atomic writes at this capacity without + hanging. */ +#if __FreeBSD__ && !defined (HEREDOC_PIPESIZE) +# define HEREDOC_PIPESIZE 4096 +#endif + +/* Normally set by a build process command that computes pipe capacity */ +#ifndef PIPESIZE +# ifdef PIPE_BUF +# define PIPESIZE PIPE_BUF +# else +# define PIPESIZE 4096 +# endif +#endif + +#ifndef HEREDOC_PIPESIZE +# define HEREDOC_PIPESIZE PIPESIZE +#endif + +#if defined (HEREDOC_PIPEMAX) +# if HEREDOC_PIPESIZE > HEREDOC_PIPEMAX +# define HEREDOC_PIPESIZE HEREDOC_PIPEMAX +# endif +#endif + +#define SHELL_FD_BASE 10 + +int expanding_redir; +int varassign_redir_autoclose = 0; + +extern REDIRECT *redirection_undo_list; +extern REDIRECT *exec_redirection_undo_list; + +/* Static functions defined and used in this file. */ +static void add_exec_redirect PARAMS((REDIRECT *)); +static int add_undo_redirect PARAMS((int, enum r_instruction, int)); +static int add_undo_close_redirect PARAMS((int)); +static int expandable_redirection_filename PARAMS((REDIRECT *)); +static int stdin_redirection PARAMS((enum r_instruction, int)); +static int undoablefd PARAMS((int)); +static int do_redirection_internal PARAMS((REDIRECT *, int, char **)); + +static char *heredoc_expand PARAMS((WORD_DESC *, enum r_instruction, size_t *)); +static int heredoc_write PARAMS((int, char *, size_t)); +static int here_document_to_fd PARAMS((WORD_DESC *, enum r_instruction)); + +static int redir_special_open PARAMS((int, char *, int, int, enum r_instruction)); +static int noclobber_open PARAMS((char *, int, int, enum r_instruction)); +static int redir_open PARAMS((char *, int, int, enum r_instruction)); + +static int redir_varassign PARAMS((REDIRECT *, int)); +static int redir_varvalue PARAMS((REDIRECT *)); + +/* Spare redirector used when translating [N]>&WORD[-] or [N]<&WORD[-] to + a new redirection and when creating the redirection undo list. */ +static REDIRECTEE rd; + +/* Set to errno when a here document cannot be created for some reason. + Used to print a reasonable error message. */ +static int heredoc_errno; + +#define REDIRECTION_ERROR(r, e, fd) \ +do { \ + if ((r) < 0) \ + { \ + if (fd >= 0) \ + close (fd); \ + set_exit_status (EXECUTION_FAILURE);\ + return ((e) == 0 ? EINVAL : (e));\ + } \ +} while (0) + +void +redirection_error (temp, error, fn) + REDIRECT *temp; + int error; + char *fn; /* already-expanded filename */ +{ + char *filename, *allocname; + int oflags; + + allocname = 0; + if ((temp->rflags & REDIR_VARASSIGN) && error < 0) + filename = allocname = savestring (temp->redirector.filename->word); + else if ((temp->rflags & REDIR_VARASSIGN) == 0 && temp->redirector.dest < 0) + /* This can happen when read_token_word encounters overflow, like in + exec 4294967297>x */ + filename = _("file descriptor out of range"); +#ifdef EBADF + /* This error can never involve NOCLOBBER */ + else if (error != NOCLOBBER_REDIRECT && temp->redirector.dest >= 0 && error == EBADF) + { + /* If we're dealing with two file descriptors, we have to guess about + which one is invalid; in the cases of r_{duplicating,move}_input and + r_{duplicating,move}_output we're here because dup2() failed. */ + switch (temp->instruction) + { + case r_duplicating_input: + case r_duplicating_output: + case r_move_input: + case r_move_output: + filename = allocname = itos (temp->redirectee.dest); + break; + case r_duplicating_input_word: + if (temp->redirector.dest == 0) /* Guess */ + filename = temp->redirectee.filename->word; /* XXX */ + else + filename = allocname = itos (temp->redirector.dest); + break; + case r_duplicating_output_word: + if (temp->redirector.dest == 1) /* Guess */ + filename = temp->redirectee.filename->word; /* XXX */ + else + filename = allocname = itos (temp->redirector.dest); + break; + default: + filename = allocname = itos (temp->redirector.dest); + break; + } + } +#endif + else if (fn) + filename = fn; + else if (expandable_redirection_filename (temp)) + { + oflags = temp->redirectee.filename->flags; + if (posixly_correct && interactive_shell == 0) + temp->redirectee.filename->flags |= W_NOGLOB; + temp->redirectee.filename->flags |= W_NOCOMSUB; + filename = allocname = redirection_expand (temp->redirectee.filename); + temp->redirectee.filename->flags = oflags; + if (filename == 0) + filename = temp->redirectee.filename->word; + } + else if (temp->redirectee.dest < 0) + filename = _("file descriptor out of range"); + else + filename = allocname = itos (temp->redirectee.dest); + + switch (error) + { + case AMBIGUOUS_REDIRECT: + internal_error (_("%s: ambiguous redirect"), filename); + break; + + case NOCLOBBER_REDIRECT: + internal_error (_("%s: cannot overwrite existing file"), filename); + break; + +#if defined (RESTRICTED_SHELL) + case RESTRICTED_REDIRECT: + internal_error (_("%s: restricted: cannot redirect output"), filename); + break; +#endif /* RESTRICTED_SHELL */ + + case HEREDOC_REDIRECT: + internal_error (_("cannot create temp file for here-document: %s"), strerror (heredoc_errno)); + break; + + case BADVAR_REDIRECT: + internal_error (_("%s: cannot assign fd to variable"), filename); + break; + + default: + internal_error ("%s: %s", filename, strerror (error)); + break; + } + + FREE (allocname); +} + +/* Perform the redirections on LIST. If flags & RX_ACTIVE, then actually + make input and output file descriptors, otherwise just do whatever is + necessary for side effecting. flags & RX_UNDOABLE says to remember + how to undo the redirections later, if non-zero. If flags & RX_CLEXEC + is non-zero, file descriptors opened in do_redirection () have their + close-on-exec flag set. */ +int +do_redirections (list, flags) + REDIRECT *list; + int flags; +{ + int error; + REDIRECT *temp; + char *fn; + + if (flags & RX_UNDOABLE) + { + if (redirection_undo_list) + { + dispose_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + } + if (exec_redirection_undo_list) + dispose_exec_redirects (); + } + + for (temp = list; temp; temp = temp->next) + { + fn = 0; + error = do_redirection_internal (temp, flags, &fn); + if (error) + { + redirection_error (temp, error, fn); + FREE (fn); + return (error); + } + FREE (fn); + } + return (0); +} + +/* Return non-zero if the redirection pointed to by REDIRECT has a + redirectee.filename that can be expanded. */ +static int +expandable_redirection_filename (redirect) + REDIRECT *redirect; +{ + switch (redirect->instruction) + { + case r_output_direction: + case r_appending_to: + case r_input_direction: + case r_inputa_direction: + case r_err_and_out: + case r_append_err_and_out: + case r_input_output: + case r_output_force: + case r_duplicating_input_word: + case r_duplicating_output_word: + case r_move_input_word: + case r_move_output_word: + return 1; + + default: + return 0; + } +} + +/* Expand the word in WORD returning a string. If WORD expands to + multiple words (or no words), then return NULL. */ +char * +redirection_expand (word) + WORD_DESC *word; +{ + char *result; + WORD_LIST *tlist1, *tlist2; + WORD_DESC *w; + int old; + + w = copy_word (word); + if (posixly_correct) + w->flags |= W_NOSPLIT; + + tlist1 = make_word_list (w, (WORD_LIST *)NULL); + expanding_redir = 1; + /* Now that we've changed the variable search order to ignore the temp + environment, see if we need to change the cached IFS values. */ + sv_ifs ("IFS"); + tlist2 = expand_words_no_vars (tlist1); + expanding_redir = 0; + /* Now we need to change the variable search order back to include the temp + environment. We force the temp environment search by forcing + executing_builtin to 1. This is what makes `read' get the right values + for the IFS-related cached variables, for example. */ + old = executing_builtin; + executing_builtin = 1; + sv_ifs ("IFS"); + executing_builtin = old; + dispose_words (tlist1); + + if (tlist2 == 0 || tlist2->next) + { + /* We expanded to no words, or to more than a single word. + Dispose of the word list and return NULL. */ + if (tlist2) + dispose_words (tlist2); + return ((char *)NULL); + } + result = string_list (tlist2); /* XXX savestring (tlist2->word->word)? */ + dispose_words (tlist2); + return (result); +} + +/* Expand a here-document or here-string (determined by RI) contained in + REDIRECTEE and return the expanded document. If LENP is non-zero, put + the length of the returned string into *LENP. + + This captures everything about expanding here-documents and here-strings: + the returned document should be written directly to whatever file + descriptor is specified. In particular, it adds a newline to the end of + a here-string to preserve previous semantics. */ +static char * +heredoc_expand (redirectee, ri, lenp) + WORD_DESC *redirectee; + enum r_instruction ri; + size_t *lenp; +{ + char *document; + size_t dlen; + int old; + + if (redirectee->word == 0 || redirectee->word[0] == '\0') + { + if (lenp) + *lenp = 0; + return (redirectee->word); + } + + /* Quoted here documents are not expanded */ + if (ri != r_reading_string && (redirectee->flags & W_QUOTED)) + { + if (lenp) + *lenp = STRLEN (redirectee->word); + return (redirectee->word); + } + + expanding_redir = 1; + /* Now that we've changed the variable search order to ignore the temp + environment, see if we need to change the cached IFS values. */ + sv_ifs ("IFS"); + document = (ri == r_reading_string) ? expand_assignment_string_to_string (redirectee->word, 0) + : expand_string_to_string (redirectee->word, Q_HERE_DOCUMENT); + expanding_redir = 0; + /* Now we need to change the variable search order back to include the temp + environment. We force the temp environment search by forcing + executing_builtin to 1. This is what makes `read' get the right values + for the IFS-related cached variables, for example. */ + old = executing_builtin; + executing_builtin = 1; + sv_ifs ("IFS"); + executing_builtin = old; + + dlen = STRLEN (document); + /* XXX - Add trailing newline to here-string */ + if (ri == r_reading_string) + { + document = xrealloc (document, dlen + 2); + document[dlen++] = '\n'; + document[dlen] = '\0'; + } + if (lenp) + *lenp = dlen; + + return document; +} + +/* Write HEREDOC (of length HDLEN) to FD, returning 0 on success and ERRNO on + error. Don't handle interrupts. */ +static int +heredoc_write (fd, heredoc, herelen) + int fd; + char *heredoc; + size_t herelen; +{ + ssize_t nw; + int e; + + errno = 0; + nw = write (fd, heredoc, herelen); + e = errno; + if (nw != herelen) + { + if (e == 0) + e = ENOSPC; + return e; + } + return 0; +} + +/* Create a temporary file or pipe holding the text of the here document + pointed to by REDIRECTEE, and return a file descriptor open for reading + to it. Return -1 on any error, and make sure errno is set appropriately. */ +static int +here_document_to_fd (redirectee, ri) + WORD_DESC *redirectee; + enum r_instruction ri; +{ + char *filename; + int r, fd, fd2, herepipe[2]; + char *document; + size_t document_len; +#if HEREDOC_PARANOID + struct stat st1, st2; +#endif + + /* Expand the here-document/here-string first and then decide what to do. */ + document = heredoc_expand (redirectee, ri, &document_len); + + /* If we have a zero-length document, don't mess with a temp file */ + if (document_len == 0) + { + fd = open ("/dev/null", O_RDONLY); + r = errno; + if (document != redirectee->word) + FREE (document); + errno = r; + return fd; + } + + if (shell_compatibility_level <= 50) + goto use_tempfile; + +#if HEREDOC_PIPESIZE + /* Try to use a pipe internal to this process if the document is shorter + than the system's pipe capacity (computed at build time). We want to + write the entire document without write blocking. */ + if (document_len <= HEREDOC_PIPESIZE) + { + if (pipe (herepipe) < 0) + { + /* XXX - goto use_tempfile; ? */ + r = errno; + if (document != redirectee->word) + free (document); + errno = r; + return (-1); + } + +#if defined (F_GETPIPE_SZ) + if (fcntl (herepipe[1], F_GETPIPE_SZ, 0) < document_len) + goto use_tempfile; +#endif + + r = heredoc_write (herepipe[1], document, document_len); + if (document != redirectee->word) + free (document); + close (herepipe[1]); + if (r) /* write error */ + { + close (herepipe[0]); + errno = r; + return (-1); + } + return (herepipe[0]); + } +#endif + +use_tempfile: + + fd = sh_mktmpfd ("sh-thd", MT_USERANDOM|MT_USETMPDIR, &filename); + + /* If we failed for some reason other than the file existing, abort */ + if (fd < 0) + { + r = errno; + FREE (filename); + if (document != redirectee->word) + FREE (document); + errno = r; + return (fd); + } + + fchmod (fd, S_IRUSR | S_IWUSR); + SET_CLOSE_ON_EXEC (fd); + + errno = r = 0; /* XXX */ + r = heredoc_write (fd, document, document_len); + if (document != redirectee->word) + FREE (document); + + if (r) + { + close (fd); + unlink (filename); + free (filename); + errno = r; + return (-1); + } + + /* In an attempt to avoid races, we close the first fd only after opening + the second. */ + /* Make the document really temporary. Also make it the input. */ + fd2 = open (filename, O_RDONLY|O_BINARY, 0600); + + if (fd2 < 0) + { + r = errno; + unlink (filename); + free (filename); + close (fd); + errno = r; + return -1; + } + +#if HEREDOC_PARANOID + /* We can use same_file here to check whether or not fd and fd2 refer to + the same file, but we don't do that unless HEREDOC_PARANOID is defined. */ + if (fstat (fd, &st1) < 0 || S_ISREG (st1.st_mode) == 0 || + fstat (fd2, &st2) < 0 || S_ISREG (st2.st_mode) == 0 || + same_file (filename, filename, &st1, &st2) == 0) + { + unlink (filename); + free (filename); + close (fd); + close (fd2); + errno = EEXIST; + return -1; + } +#endif + + close (fd); + if (unlink (filename) < 0) + { + r = errno; + close (fd2); + free (filename); + errno = r; + return (-1); + } + + free (filename); + + fchmod (fd2, S_IRUSR); + return (fd2); +} + +#define RF_DEVFD 1 +#define RF_DEVSTDERR 2 +#define RF_DEVSTDIN 3 +#define RF_DEVSTDOUT 4 +#define RF_DEVTCP 5 +#define RF_DEVUDP 6 + +/* A list of pattern/value pairs for filenames that the redirection + code handles specially. */ +static STRING_INT_ALIST _redir_special_filenames[] = { +#if !defined (HAVE_DEV_FD) + { "/dev/fd/[0-9]*", RF_DEVFD }, +#endif +#if !defined (HAVE_DEV_STDIN) + { "/dev/stderr", RF_DEVSTDERR }, + { "/dev/stdin", RF_DEVSTDIN }, + { "/dev/stdout", RF_DEVSTDOUT }, +#endif +#if defined (NETWORK_REDIRECTIONS) + { "/dev/tcp/*/*", RF_DEVTCP }, + { "/dev/udp/*/*", RF_DEVUDP }, +#endif + { (char *)NULL, -1 } +}; + +static int +redir_special_open (spec, filename, flags, mode, ri) + int spec; + char *filename; + int flags, mode; + enum r_instruction ri; +{ + int fd; +#if !defined (HAVE_DEV_FD) + intmax_t lfd; +#endif + + fd = -1; + switch (spec) + { +#if !defined (HAVE_DEV_FD) + case RF_DEVFD: + if (all_digits (filename+8) && legal_number (filename+8, &lfd) && lfd == (int)lfd) + { + fd = lfd; + fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE); + } + else + fd = AMBIGUOUS_REDIRECT; + break; +#endif + +#if !defined (HAVE_DEV_STDIN) + case RF_DEVSTDIN: + fd = fcntl (0, F_DUPFD, SHELL_FD_BASE); + break; + case RF_DEVSTDOUT: + fd = fcntl (1, F_DUPFD, SHELL_FD_BASE); + break; + case RF_DEVSTDERR: + fd = fcntl (2, F_DUPFD, SHELL_FD_BASE); + break; +#endif + +#if defined (NETWORK_REDIRECTIONS) + case RF_DEVTCP: + case RF_DEVUDP: +#if defined (RESTRICTED_SHELL) + if (restricted) + return (RESTRICTED_REDIRECT); +#endif +#if defined (HAVE_NETWORK) + fd = netopen (filename); +#else + internal_warning (_("/dev/(tcp|udp)/host/port not supported without networking")); + fd = open (filename, flags, mode); +#endif + break; +#endif /* NETWORK_REDIRECTIONS */ + } + + return fd; +} + +/* Open FILENAME with FLAGS in noclobber mode, hopefully avoiding most + race conditions and avoiding the problem where the file is replaced + between the stat(2) and open(2). */ +static int +noclobber_open (filename, flags, mode, ri) + char *filename; + int flags, mode; + enum r_instruction ri; +{ + int r, fd; + struct stat finfo, finfo2; + + /* If the file exists and is a regular file, return an error + immediately. */ + r = stat (filename, &finfo); + if (r == 0 && (S_ISREG (finfo.st_mode))) + return (NOCLOBBER_REDIRECT); + + /* If the file was not present (r != 0), make sure we open it + exclusively so that if it is created before we open it, our open + will fail. Make sure that we do not truncate an existing file. + Note that we don't turn on O_EXCL unless the stat failed -- if + the file was not a regular file, we leave O_EXCL off. */ + flags &= ~O_TRUNC; + if (r != 0) + { + fd = open (filename, flags|O_EXCL, mode); + return ((fd < 0 && errno == EEXIST) ? NOCLOBBER_REDIRECT : fd); + } + fd = open (filename, flags, mode); + + /* If the open failed, return the file descriptor right away. */ + if (fd < 0) + return (errno == EEXIST ? NOCLOBBER_REDIRECT : fd); + + /* OK, the open succeeded, but the file may have been changed from a + non-regular file to a regular file between the stat and the open. + We are assuming that the O_EXCL open handles the case where FILENAME + did not exist and is symlinked to an existing file between the stat + and open. */ + + /* If we can open it and fstat the file descriptor, and neither check + revealed that it was a regular file, and the file has not been replaced, + return the file descriptor. */ + if ((fstat (fd, &finfo2) == 0) && (S_ISREG (finfo2.st_mode) == 0) && + r == 0 && (S_ISREG (finfo.st_mode) == 0) && + same_file (filename, filename, &finfo, &finfo2)) + return fd; + + /* The file has been replaced. badness. */ + close (fd); + errno = EEXIST; + return (NOCLOBBER_REDIRECT); +} + +static int +redir_open (filename, flags, mode, ri) + char *filename; + int flags, mode; + enum r_instruction ri; +{ + int fd, r, e; + + r = find_string_in_alist (filename, _redir_special_filenames, 1); + if (r >= 0) + return (redir_special_open (r, filename, flags, mode, ri)); + + /* If we are in noclobber mode, you are not allowed to overwrite + existing files. Check before opening. */ + if (noclobber && CLOBBERING_REDIRECT (ri)) + { + fd = noclobber_open (filename, flags, mode, ri); + if (fd == NOCLOBBER_REDIRECT) + return (NOCLOBBER_REDIRECT); + } + else + { + do + { + fd = open (filename, flags, mode); + e = errno; + if (fd < 0 && e == EINTR) + { + QUIT; + run_pending_traps (); + } + errno = e; + } + while (fd < 0 && errno == EINTR); + +#if defined (AFS) + if ((fd < 0) && (errno == EACCES)) + { + fd = open (filename, flags & ~O_CREAT, mode); + errno = EACCES; /* restore errno */ + } +#endif /* AFS */ + } + + return fd; +} + +static int +undoablefd (fd) + int fd; +{ + int clexec; + + clexec = fcntl (fd, F_GETFD, 0); + if (clexec == -1 || (fd >= SHELL_FD_BASE && clexec == 1)) + return 0; + return 1; +} + +/* Do the specific redirection requested. Returns errno or one of the + special redirection errors (*_REDIRECT) in case of error, 0 on success. + If flags & RX_ACTIVE is zero, then just do whatever is necessary to + produce the appropriate side effects. flags & RX_UNDOABLE, if non-zero, + says to remember how to undo each redirection. If flags & RX_CLEXEC is + non-zero, then we set all file descriptors > 2 that we open to be + close-on-exec. FNP, if non-null is a pointer to a location where the + expanded filename is stored. The caller will free it. */ +static int +do_redirection_internal (redirect, flags, fnp) + REDIRECT *redirect; + int flags; + char **fnp; +{ + WORD_DESC *redirectee; + int redir_fd, fd, redirector, r, oflags; + intmax_t lfd; + char *redirectee_word; + enum r_instruction ri; + REDIRECT *new_redirect; + REDIRECTEE sd; + + redirectee = redirect->redirectee.filename; + redir_fd = redirect->redirectee.dest; + redirector = redirect->redirector.dest; + ri = redirect->instruction; + + if (redirect->flags & RX_INTERNAL) + flags |= RX_INTERNAL; + + if (TRANSLATE_REDIRECT (ri)) + { + /* We have [N]>&WORD[-] or [N]<&WORD[-] (or {V}>&WORD[-] or {V}<&WORD-). + and WORD, then translate the redirection into a new one and + continue. */ + redirectee_word = redirection_expand (redirectee); + + /* XXX - what to do with [N]<&$w- where w is unset or null? ksh93 + turns it into [N]<&- or [N]>&- and closes N. */ + if ((ri == r_move_input_word || ri == r_move_output_word) && redirectee_word == 0) + { + sd = redirect->redirector; + rd.dest = 0; + new_redirect = make_redirection (sd, r_close_this, rd, 0); + } + else if (redirectee_word == 0) + return (AMBIGUOUS_REDIRECT); + else if (redirectee_word[0] == '-' && redirectee_word[1] == '\0') + { + sd = redirect->redirector; + rd.dest = 0; + new_redirect = make_redirection (sd, r_close_this, rd, 0); + } + else if (all_digits (redirectee_word)) + { + sd = redirect->redirector; + if (legal_number (redirectee_word, &lfd) && (int)lfd == lfd) + rd.dest = lfd; + else + rd.dest = -1; /* XXX */ + switch (ri) + { + case r_duplicating_input_word: + new_redirect = make_redirection (sd, r_duplicating_input, rd, 0); + break; + case r_duplicating_output_word: + new_redirect = make_redirection (sd, r_duplicating_output, rd, 0); + break; + case r_move_input_word: + new_redirect = make_redirection (sd, r_move_input, rd, 0); + break; + case r_move_output_word: + new_redirect = make_redirection (sd, r_move_output, rd, 0); + break; + default: + break; /* shut up gcc */ + } + } + else if (ri == r_duplicating_output_word && (redirect->rflags & REDIR_VARASSIGN) == 0 && redirector == 1) + { + sd = redirect->redirector; + rd.filename = make_bare_word (redirectee_word); + new_redirect = make_redirection (sd, r_err_and_out, rd, 0); + } + else + { + free (redirectee_word); + return (AMBIGUOUS_REDIRECT); + } + + free (redirectee_word); + + /* Set up the variables needed by the rest of the function from the + new redirection. */ + if (new_redirect->instruction == r_err_and_out) + { + char *alloca_hack; + + /* Copy the word without allocating any memory that must be + explicitly freed. */ + redirectee = (WORD_DESC *)alloca (sizeof (WORD_DESC)); + xbcopy ((char *)new_redirect->redirectee.filename, + (char *)redirectee, sizeof (WORD_DESC)); + + alloca_hack = (char *) + alloca (1 + strlen (new_redirect->redirectee.filename->word)); + redirectee->word = alloca_hack; + strcpy (redirectee->word, new_redirect->redirectee.filename->word); + } + else + /* It's guaranteed to be an integer, and shouldn't be freed. */ + redirectee = new_redirect->redirectee.filename; + + redir_fd = new_redirect->redirectee.dest; + redirector = new_redirect->redirector.dest; + ri = new_redirect->instruction; + + /* Overwrite the flags element of the old redirect with the new value. */ + redirect->flags = new_redirect->flags; + dispose_redirects (new_redirect); + } + + switch (ri) + { + case r_output_direction: + case r_appending_to: + case r_input_direction: + case r_inputa_direction: + case r_err_and_out: /* command &>filename */ + case r_append_err_and_out: /* command &>> filename */ + case r_input_output: + case r_output_force: + if (posixly_correct && interactive_shell == 0) + { + oflags = redirectee->flags; + redirectee->flags |= W_NOGLOB; + } + redirectee_word = redirection_expand (redirectee); + if (posixly_correct && interactive_shell == 0) + redirectee->flags = oflags; + + if (redirectee_word == 0) + return (AMBIGUOUS_REDIRECT); + +#if defined (RESTRICTED_SHELL) + if (restricted && (WRITE_REDIRECT (ri))) + { + free (redirectee_word); + return (RESTRICTED_REDIRECT); + } +#endif /* RESTRICTED_SHELL */ + + fd = redir_open (redirectee_word, redirect->flags, 0666, ri); + if (fnp) + *fnp = redirectee_word; + else + free (redirectee_word); + + if (fd == NOCLOBBER_REDIRECT || fd == RESTRICTED_REDIRECT) + return (fd); + + if (fd < 0) + return (errno); + + if (flags & RX_ACTIVE) + { + if (redirect->rflags & REDIR_VARASSIGN) + { + redirector = fcntl (fd, F_DUPFD, SHELL_FD_BASE); /* XXX try this for now */ + r = errno; + if (redirector < 0) + sys_error (_("redirection error: cannot duplicate fd")); + REDIRECTION_ERROR (redirector, r, fd); + } + + if ((flags & RX_UNDOABLE) && ((redirect->rflags & REDIR_VARASSIGN) == 0 || varassign_redir_autoclose)) + { + /* Only setup to undo it if the thing to undo is active. We want + to autoclose if we are doing a varassign redirection and the + varredir_close shell option is set, and we can't test + redirector in this case since we just assigned it above. */ + if (fd != redirector && (redirect->rflags & REDIR_VARASSIGN) && varassign_redir_autoclose) + r = add_undo_close_redirect (redirector); + else if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1)) + r = add_undo_redirect (redirector, ri, -1); + else + r = add_undo_close_redirect (redirector); + REDIRECTION_ERROR (r, errno, fd); + } + +#if defined (BUFFERED_INPUT) + /* inhibit call to sync_buffered_stream() for async processes */ + if (redirector != 0 || (subshell_environment & SUBSHELL_ASYNC) == 0) + check_bash_input (redirector); +#endif + + /* Make sure there is no pending output before we change the state + of the underlying file descriptor, since the builtins use stdio + for output. */ + if (redirector == 1 && fileno (stdout) == redirector) + { + fflush (stdout); + fpurge (stdout); + } + else if (redirector == 2 && fileno (stderr) == redirector) + { + fflush (stderr); + fpurge (stderr); + } + + if (redirect->rflags & REDIR_VARASSIGN) + { + if ((r = redir_varassign (redirect, redirector)) < 0) + { + close (redirector); + close (fd); + return (r); /* XXX */ + } + } + else if ((fd != redirector) && (dup2 (fd, redirector) < 0)) + { + close (fd); /* dup2 failed? must be fd limit issue */ + return (errno); + } + +#if defined (BUFFERED_INPUT) + /* Do not change the buffered stream for an implicit redirection + of /dev/null to fd 0 for asynchronous commands without job + control (r_inputa_direction). */ + if (ri == r_input_direction || ri == r_input_output) + duplicate_buffered_stream (fd, redirector); +#endif /* BUFFERED_INPUT */ + + /* + * If we're remembering, then this is the result of a while, for + * or until loop with a loop redirection, or a function/builtin + * executing in the parent shell with a redirection. In the + * function/builtin case, we want to set all file descriptors > 2 + * to be close-on-exec to duplicate the effect of the old + * for i = 3 to NOFILE close(i) loop. In the case of the loops, + * both sh and ksh leave the file descriptors open across execs. + * The Posix standard mentions only the exec builtin. + */ + if ((flags & RX_CLEXEC) && (redirector > 2)) + SET_CLOSE_ON_EXEC (redirector); + } + + if (fd != redirector) + { +#if defined (BUFFERED_INPUT) + if (INPUT_REDIRECT (ri)) + close_buffered_fd (fd); + else +#endif /* !BUFFERED_INPUT */ + close (fd); /* Don't close what we just opened! */ + } + + /* If we are hacking both stdout and stderr, do the stderr + redirection here. XXX - handle {var} here? */ + if (ri == r_err_and_out || ri == r_append_err_and_out) + { + if (flags & RX_ACTIVE) + { + if (flags & RX_UNDOABLE) + add_undo_redirect (2, ri, -1); + if (dup2 (1, 2) < 0) + return (errno); + } + } + break; + + case r_reading_until: + case r_deblank_reading_until: + case r_reading_string: + /* REDIRECTEE is a pointer to a WORD_DESC containing the text of + the new input. Place it in a temporary file. */ + if (redirectee) + { + fd = here_document_to_fd (redirectee, ri); + + if (fd < 0) + { + heredoc_errno = errno; + return (HEREDOC_REDIRECT); + } + + if (redirect->rflags & REDIR_VARASSIGN) + { + redirector = fcntl (fd, F_DUPFD, SHELL_FD_BASE); /* XXX try this for now */ + r = errno; + if (redirector < 0) + sys_error (_("redirection error: cannot duplicate fd")); + REDIRECTION_ERROR (redirector, r, fd); + } + + if (flags & RX_ACTIVE) + { + if ((flags & RX_UNDOABLE) && ((redirect->rflags & REDIR_VARASSIGN) == 0 || varassign_redir_autoclose)) + { + /* Only setup to undo it if the thing to undo is active. + Close if the right option is set and we are doing a + varassign redirection. */ + if (fd != redirector && (redirect->rflags & REDIR_VARASSIGN) && varassign_redir_autoclose) + r = add_undo_close_redirect (redirector); + else if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1)) + r = add_undo_redirect (redirector, ri, -1); + else + r = add_undo_close_redirect (redirector); + REDIRECTION_ERROR (r, errno, fd); + } + +#if defined (BUFFERED_INPUT) + check_bash_input (redirector); +#endif + if (redirect->rflags & REDIR_VARASSIGN) + { + if ((r = redir_varassign (redirect, redirector)) < 0) + { + close (redirector); + close (fd); + return (r); /* XXX */ + } + } + else if (fd != redirector && dup2 (fd, redirector) < 0) + { + r = errno; + close (fd); + return (r); + } + +#if defined (BUFFERED_INPUT) + duplicate_buffered_stream (fd, redirector); +#endif + + if ((flags & RX_CLEXEC) && (redirector > 2)) + SET_CLOSE_ON_EXEC (redirector); + } + + if (fd != redirector) +#if defined (BUFFERED_INPUT) + close_buffered_fd (fd); +#else + close (fd); +#endif + } + break; + + case r_duplicating_input: + case r_duplicating_output: + case r_move_input: + case r_move_output: + if ((flags & RX_ACTIVE) && (redirect->rflags & REDIR_VARASSIGN)) + { + redirector = fcntl (redir_fd, F_DUPFD, SHELL_FD_BASE); /* XXX try this for now */ + r = errno; + if (redirector < 0) + sys_error (_("redirection error: cannot duplicate fd")); + REDIRECTION_ERROR (redirector, r, -1); + } + + if ((flags & RX_ACTIVE) && (redir_fd != redirector)) + { + if ((flags & RX_UNDOABLE) && ((redirect->rflags & REDIR_VARASSIGN) == 0 || varassign_redir_autoclose)) + { + /* Only setup to undo it if the thing to undo is active. + Close if the right option is set and we are doing a + varassign redirection. */ + if ((redirect->rflags & REDIR_VARASSIGN) && varassign_redir_autoclose) + r = add_undo_close_redirect (redirector); + else if (fcntl (redirector, F_GETFD, 0) != -1) + r = add_undo_redirect (redirector, ri, redir_fd); + else + r = add_undo_close_redirect (redirector); + REDIRECTION_ERROR (r, errno, -1); + } + if ((flags & RX_UNDOABLE) && (ri == r_move_input || ri == r_move_output)) + { + /* r_move_input and r_move_output add an additional close() + that needs to be undone */ + if (fcntl (redirector, F_GETFD, 0) != -1) + { + r = add_undo_redirect (redir_fd, r_close_this, -1); + REDIRECTION_ERROR (r, errno, -1); + } + } +#if defined (BUFFERED_INPUT) + /* inhibit call to sync_buffered_stream() for async processes */ + if (redirector != 0 || (subshell_environment & SUBSHELL_ASYNC) == 0) + check_bash_input (redirector); +#endif + if (redirect->rflags & REDIR_VARASSIGN) + { + if ((r = redir_varassign (redirect, redirector)) < 0) + { + close (redirector); + return (r); /* XXX */ + } + } + /* This is correct. 2>&1 means dup2 (1, 2); */ + else if (dup2 (redir_fd, redirector) < 0) + return (errno); + +#if defined (BUFFERED_INPUT) + if (ri == r_duplicating_input || ri == r_move_input) + duplicate_buffered_stream (redir_fd, redirector); +#endif /* BUFFERED_INPUT */ + + /* First duplicate the close-on-exec state of redirectee. dup2 + leaves the flag unset on the new descriptor, which means it + stays open. Only set the close-on-exec bit for file descriptors + greater than 2 in any case, since 0-2 should always be open + unless closed by something like `exec 2<&-'. It should always + be safe to set fds > 2 to close-on-exec if they're being used to + save file descriptors < 2, since we don't need to preserve the + state of the close-on-exec flag for those fds -- they should + always be open. */ + /* if ((already_set || set_unconditionally) && (ok_to_set)) + set_it () */ +#if 0 + if (((fcntl (redir_fd, F_GETFD, 0) == 1) || redir_fd < 2 || (flags & RX_CLEXEC)) && + (redirector > 2)) +#else + if (((fcntl (redir_fd, F_GETFD, 0) == 1) || (redir_fd < 2 && (flags & RX_INTERNAL)) || (flags & RX_CLEXEC)) && + (redirector > 2)) +#endif + SET_CLOSE_ON_EXEC (redirector); + + /* When undoing saving of non-standard file descriptors (>=3) using + file descriptors >= SHELL_FD_BASE, we set the saving fd to be + close-on-exec and use a flag to decide how to set close-on-exec + when the fd is restored. */ + if ((redirect->flags & RX_INTERNAL) && (redirect->flags & RX_SAVCLEXEC) && redirector >= 3 && (redir_fd >= SHELL_FD_BASE || (redirect->flags & RX_SAVEFD))) + SET_OPEN_ON_EXEC (redirector); + + /* dup-and-close redirection */ + if (ri == r_move_input || ri == r_move_output) + { + xtrace_fdchk (redir_fd); + + close (redir_fd); +#if defined (COPROCESS_SUPPORT) + coproc_fdchk (redir_fd); /* XXX - loses coproc fds */ +#endif + } + } + break; + + case r_close_this: + if (flags & RX_ACTIVE) + { + if (redirect->rflags & REDIR_VARASSIGN) + { + redirector = redir_varvalue (redirect); + if (redirector < 0) + return AMBIGUOUS_REDIRECT; + } + + r = 0; + if (flags & RX_UNDOABLE) + { + if (fcntl (redirector, F_GETFD, 0) != -1) + r = add_undo_redirect (redirector, ri, -1); + else + r = add_undo_close_redirect (redirector); + REDIRECTION_ERROR (r, errno, redirector); + } + +#if defined (COPROCESS_SUPPORT) + coproc_fdchk (redirector); +#endif + xtrace_fdchk (redirector); + +#if defined (BUFFERED_INPUT) + /* inhibit call to sync_buffered_stream() for async processes */ + if (redirector != 0 || (subshell_environment & SUBSHELL_ASYNC) == 0) + check_bash_input (redirector); + r = close_buffered_fd (redirector); +#else /* !BUFFERED_INPUT */ + r = close (redirector); +#endif /* !BUFFERED_INPUT */ + + if (r < 0 && (flags & RX_INTERNAL) && (errno == EIO || errno == ENOSPC)) + REDIRECTION_ERROR (r, errno, -1); + } + break; + + case r_duplicating_input_word: + case r_duplicating_output_word: + case r_move_input_word: + case r_move_output_word: + break; + } + return (0); +} + +/* Remember the file descriptor associated with the slot FD, + on REDIRECTION_UNDO_LIST. Note that the list will be reversed + before it is executed. Any redirections that need to be undone + even if REDIRECTION_UNDO_LIST is discarded by the exec builtin + are also saved on EXEC_REDIRECTION_UNDO_LIST. FDBASE says where to + start the duplicating. If it's less than SHELL_FD_BASE, we're ok, + and can use SHELL_FD_BASE (-1 == don't care). If it's >= SHELL_FD_BASE, + we have to make sure we don't use fdbase to save a file descriptor, + since we're going to use it later (e.g., make sure we don't save fd 0 + to fd 10 if we have a redirection like 0<&10). If the value of fdbase + puts the process over its fd limit, causing fcntl to fail, we try + again with SHELL_FD_BASE. Return 0 on success, -1 on error. */ +static int +add_undo_redirect (fd, ri, fdbase) + int fd; + enum r_instruction ri; + int fdbase; +{ + int new_fd, clexec_flag, savefd_flag; + REDIRECT *new_redirect, *closer, *dummy_redirect; + REDIRECTEE sd; + + savefd_flag = 0; + new_fd = fcntl (fd, F_DUPFD, (fdbase < SHELL_FD_BASE) ? SHELL_FD_BASE : fdbase+1); + if (new_fd < 0) + new_fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE); + if (new_fd < 0) + { + new_fd = fcntl (fd, F_DUPFD, 0); + savefd_flag = 1; + } + + if (new_fd < 0) + { + sys_error (_("redirection error: cannot duplicate fd")); + return (-1); + } + + clexec_flag = fcntl (fd, F_GETFD, 0); + + sd.dest = new_fd; + rd.dest = 0; + closer = make_redirection (sd, r_close_this, rd, 0); + closer->flags |= RX_INTERNAL; + dummy_redirect = copy_redirects (closer); + + sd.dest = fd; + rd.dest = new_fd; + if (fd == 0) + new_redirect = make_redirection (sd, r_duplicating_input, rd, 0); + else + new_redirect = make_redirection (sd, r_duplicating_output, rd, 0); + new_redirect->flags |= RX_INTERNAL; + if (savefd_flag) + new_redirect->flags |= RX_SAVEFD; + if (clexec_flag == 0 && fd >= 3 && (new_fd >= SHELL_FD_BASE || savefd_flag)) + new_redirect->flags |= RX_SAVCLEXEC; + new_redirect->next = closer; + + closer->next = redirection_undo_list; + redirection_undo_list = new_redirect; + + /* Save redirections that need to be undone even if the undo list + is thrown away by the `exec' builtin. */ + add_exec_redirect (dummy_redirect); + + /* experimental: if we're saving a redirection to undo for a file descriptor + above SHELL_FD_BASE, add a redirection to be undone if the exec builtin + causes redirections to be discarded. There needs to be a difference + between fds that are used to save other fds and then are the target of + user redirections and fds that are just the target of user redirections. + We use the close-on-exec flag to tell the difference; fds > SHELL_FD_BASE + that have the close-on-exec flag set are assumed to be fds used internally + to save others. */ + if (fd >= SHELL_FD_BASE && ri != r_close_this && clexec_flag) + { + sd.dest = fd; + rd.dest = new_fd; + new_redirect = make_redirection (sd, r_duplicating_output, rd, 0); + new_redirect->flags |= RX_INTERNAL; + + add_exec_redirect (new_redirect); + } + + /* File descriptors used only for saving others should always be + marked close-on-exec. Unfortunately, we have to preserve the + close-on-exec state of the file descriptor we are saving, since + fcntl (F_DUPFD) sets the new file descriptor to remain open + across execs. If, however, the file descriptor whose state we + are saving is <= 2, we can just set the close-on-exec flag, + because file descriptors 0-2 should always be open-on-exec, + and the restore above in do_redirection() will take care of it. */ + if (clexec_flag || fd < 3) + SET_CLOSE_ON_EXEC (new_fd); + else if (redirection_undo_list->flags & RX_SAVCLEXEC) + SET_CLOSE_ON_EXEC (new_fd); + + return (0); +} + +/* Set up to close FD when we are finished with the current command + and its redirections. Return 0 on success, -1 on error. */ +static int +add_undo_close_redirect (fd) + int fd; +{ + REDIRECT *closer; + REDIRECTEE sd; + + sd.dest = fd; + rd.dest = 0; + closer = make_redirection (sd, r_close_this, rd, 0); + closer->flags |= RX_INTERNAL; + closer->next = redirection_undo_list; + redirection_undo_list = closer; + + return 0; +} + +static void +add_exec_redirect (dummy_redirect) + REDIRECT *dummy_redirect; +{ + dummy_redirect->next = exec_redirection_undo_list; + exec_redirection_undo_list = dummy_redirect; +} + +/* Return 1 if the redirection specified by RI and REDIRECTOR alters the + standard input. */ +static int +stdin_redirection (ri, redirector) + enum r_instruction ri; + int redirector; +{ + switch (ri) + { + case r_input_direction: + case r_inputa_direction: + case r_input_output: + case r_reading_until: + case r_deblank_reading_until: + case r_reading_string: + return (1); + case r_duplicating_input: + case r_duplicating_input_word: + case r_close_this: + return (redirector == 0); + case r_output_direction: + case r_appending_to: + case r_duplicating_output: + case r_err_and_out: + case r_append_err_and_out: + case r_output_force: + case r_duplicating_output_word: + case r_move_input: + case r_move_output: + case r_move_input_word: + case r_move_output_word: + return (0); + } + return (0); +} + +/* Return non-zero if any of the redirections in REDIRS alter the standard + input. */ +int +stdin_redirects (redirs) + REDIRECT *redirs; +{ + REDIRECT *rp; + int n; + + for (n = 0, rp = redirs; rp; rp = rp->next) + if ((rp->rflags & REDIR_VARASSIGN) == 0) + n += stdin_redirection (rp->instruction, rp->redirector.dest); + return n; +} +/* bind_var_to_int handles array references */ +static int +redir_varassign (redir, fd) + REDIRECT *redir; + int fd; +{ + WORD_DESC *w; + SHELL_VAR *v; + + w = redir->redirector.filename; + v = bind_var_to_int (w->word, fd, 0); + if (v == 0 || readonly_p (v) || noassign_p (v)) + return BADVAR_REDIRECT; + + stupidly_hack_special_variables (w->word); + return 0; +} + +/* Handles {array[ind]} for redirection words */ +static int +redir_varvalue (redir) + REDIRECT *redir; +{ + SHELL_VAR *v; + char *val, *w; + intmax_t vmax; + int i; +#if defined (ARRAY_VARS) + char *sub; + int len, vr; +#endif + + w = redir->redirector.filename->word; /* shorthand */ + /* XXX - handle set -u here? */ +#if defined (ARRAY_VARS) + if (vr = valid_array_reference (w, 0)) + { + v = array_variable_part (w, 0, &sub, &len); + } + else +#endif + { + v = find_variable (w); +#if defined (ARRAY_VARS) + if (v == 0) + { + v = find_variable_last_nameref (w, 0); + if (v && nameref_p (v)) + { + w = nameref_cell (v); + if (vr = valid_array_reference (w, 0)) + v = array_variable_part (w, 0, &sub, &len); + else + v = find_variable (w); + } + } +#endif + } + + if (v == 0 || invisible_p (v)) + return -1; + +#if defined (ARRAY_VARS) + /* get_variable_value handles references to array variables without + subscripts */ + if (vr && (array_p (v) || assoc_p (v))) + val = get_array_value (w, 0, (array_eltstate_t *)NULL); + else +#endif + val = get_variable_value (v); + if (val == 0 || *val == 0) + return -1; + + if (legal_number (val, &vmax) < 0) + return -1; + + i = vmax; /* integer truncation */ + return i; +} diff --git a/third_party/bash/redir.h b/third_party/bash/redir.h new file mode 100644 index 000000000..340dc1c02 --- /dev/null +++ b/third_party/bash/redir.h @@ -0,0 +1,43 @@ +/* redir.h - functions from redir.c. */ + +/* Copyright (C) 1997, 2001, 2005, 2008, 2009-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_REDIR_H_) +#define _REDIR_H_ + +#include "stdc.h" + +/* Values for flags argument to do_redirections */ +#define RX_ACTIVE 0x01 /* do it; don't just go through the motions */ +#define RX_UNDOABLE 0x02 /* make a list to undo these redirections */ +#define RX_CLEXEC 0x04 /* set close-on-exec for opened fds > 2 */ +#define RX_INTERNAL 0x08 +#define RX_USER 0x10 +#define RX_SAVCLEXEC 0x20 /* set close-on-exec off in restored fd even though saved on has it on */ +#define RX_SAVEFD 0x40 /* fd used to save another even if < SHELL_FD_BASE */ + +extern void redirection_error PARAMS((REDIRECT *, int, char *)); +extern int do_redirections PARAMS((REDIRECT *, int)); +extern char *redirection_expand PARAMS((WORD_DESC *)); +extern int stdin_redirects PARAMS((REDIRECT *)); + +/* in builtins/evalstring.c for now, could move later */ +extern int open_redir_file PARAMS((REDIRECT *, char **)); + +#endif /* _REDIR_H_ */ diff --git a/third_party/bash/rename.c b/third_party/bash/rename.c new file mode 100644 index 000000000..1fa90a159 --- /dev/null +++ b/third_party/bash/rename.c @@ -0,0 +1,76 @@ +/* + * rename - rename a file + */ + +/* Copyright (C) 1999 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if !defined (HAVE_RENAME) + +#include "bashtypes.h" +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif +#include + +#include "stdc.h" + +#ifndef errno +extern int errno; +#endif + +int +rename (from, to) + const char *from, *to; +{ + struct stat fb, tb; + + if (stat (from, &fb) < 0) + return -1; + + if (stat (to, &tb) < 0) + { + if (errno != ENOENT) + return -1; + } + else + { + if (fb.st_dev == tb.st_dev && fb.st_ino == tb.st_ino) + return 0; /* same file */ + if (unlink (to) < 0 && errno != ENOENT) + return -1; + } + + if (link (from, to) < 0) + return (-1); + + if (unlink (from) < 0 && errno != ENOENT) + { + int e = errno; + unlink (to); + errno = e; + return (-1); + } + + return (0); +} +#endif /* !HAVE_RENAME */ diff --git a/third_party/bash/setlinebuf.c b/third_party/bash/setlinebuf.c new file mode 100644 index 000000000..3c9afb8c1 --- /dev/null +++ b/third_party/bash/setlinebuf.c @@ -0,0 +1,66 @@ +/* setlinebuf.c - line-buffer a stdio stream. */ + +/* Copyright (C) 1997,2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include + +#include "xmalloc.h" + +#if defined (USING_BASH_MALLOC) +# define LBUF_BUFSIZE 2016 +#else +# define LBUF_BUFSIZE BUFSIZ +#endif + +static char *stdoutbuf = 0; +static char *stderrbuf = 0; + +/* Cause STREAM to buffer lines as opposed to characters or blocks. */ +int +sh_setlinebuf (stream) + FILE *stream; +{ +#if !defined (HAVE_SETLINEBUF) && !defined (HAVE_SETVBUF) + return (0); +#endif + +#if defined (HAVE_SETVBUF) + char *local_linebuf; + +#if defined (USING_BASH_MALLOC) + if (stream == stdout && stdoutbuf == 0) + local_linebuf = stdoutbuf = (char *)xmalloc (LBUF_BUFSIZE); + else if (stream == stderr && stderrbuf == 0) + local_linebuf = stderrbuf = (char *)xmalloc (LBUF_BUFSIZE); + else + local_linebuf = (char *)NULL; /* let stdio handle it */ +#else + local_linebuf = (char *)NULL; +#endif + + return (setvbuf (stream, local_linebuf, _IOLBF, LBUF_BUFSIZE)); +#else /* !HAVE_SETVBUF */ + + setlinebuf (stream); + return (0); + +#endif /* !HAVE_SETVBUF */ +} diff --git a/third_party/bash/shell.c b/third_party/bash/shell.c new file mode 100644 index 000000000..da5ffb58b --- /dev/null +++ b/third_party/bash/shell.c @@ -0,0 +1,2136 @@ +/* shell.c -- GNU's idea of the POSIX shell specification. */ + +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* + Birthdate: + Sunday, January 10th, 1988. + Initial author: Brian Fox +*/ +#define INSTALL_DEBUG_MODE + +#include "config.h" + +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "posixstat.h" +#include "posixtime.h" +#include "bashansi.h" +#include +#include +#include +#include "filecntl.h" +#if defined (HAVE_PWD_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashintl.h" + +#define NEED_SH_SETLINEBUF_DECL /* used in externs.h */ + +#include "shell.h" +#include "parser.h" +#include "flags.h" +#include "trap.h" +#include "mailcheck.h" +#include "builtins.h" +#include "common.h" + +#if defined (JOB_CONTROL) +#include "jobs.h" +#else +extern int running_in_background; +extern int initialize_job_control PARAMS((int)); +extern int get_tty_state PARAMS((void)); +#endif /* JOB_CONTROL */ + +#include "input.h" +#include "execute_cmd.h" +#include "findcmd.h" + +#if defined (USING_BASH_MALLOC) && defined (DEBUG) && !defined (DISABLE_MALLOC_WRAPPERS) +# include +#elif defined (MALLOC_DEBUG) && defined (USING_BASH_MALLOC) +# include +#endif + +#if defined (HISTORY) +# include "bashhist.h" +# include "third_party/readline/history.h" +#endif + +#if defined (READLINE) +# include "third_party/readline/readline.h" +# include "bashline.h" +#endif + +#include "tilde.h" +#include "strmatch.h" + +#if defined (__OPENNT) +# include +#endif + +#if !defined (HAVE_GETPW_DECLS) +extern struct passwd *getpwuid (); +#endif /* !HAVE_GETPW_DECLS */ + +#if !defined (errno) +extern int errno; +#endif + +#if defined (NO_MAIN_ENV_ARG) +extern char **environ; /* used if no third argument to main() */ +#endif + +extern int gnu_error_format; + +/* Non-zero means that this shell has already been run; i.e. you should + call shell_reinitialize () if you need to start afresh. */ +int shell_initialized = 0; +int bash_argv_initialized = 0; + +COMMAND *global_command = (COMMAND *)NULL; + +/* Information about the current user. */ +struct user_info current_user = +{ + (uid_t)-1, (uid_t)-1, (gid_t)-1, (gid_t)-1, + (char *)NULL, (char *)NULL, (char *)NULL +}; + +/* The current host's name. */ +char *current_host_name = (char *)NULL; + +/* Non-zero means that this shell is a login shell. + Specifically: + 0 = not login shell. + 1 = login shell from getty (or equivalent fake out) + -1 = login shell from "--login" (or -l) flag. + -2 = both from getty, and from flag. + */ +int login_shell = 0; + +/* Non-zero means that at this moment, the shell is interactive. In + general, this means that the shell is at this moment reading input + from the keyboard. */ +int interactive = 0; + +/* Non-zero means that the shell was started as an interactive shell. */ +int interactive_shell = 0; + +/* Non-zero means to send a SIGHUP to all jobs when an interactive login + shell exits. */ +int hup_on_exit = 0; + +/* Non-zero means to list status of running and stopped jobs at shell exit */ +int check_jobs_at_exit = 0; + +/* Non-zero means to change to a directory name supplied as a command name */ +int autocd = 0; + +/* Tells what state the shell was in when it started: + 0 = non-interactive shell script + 1 = interactive + 2 = -c command + 3 = wordexp evaluation + This is a superset of the information provided by interactive_shell. +*/ +int startup_state = 0; +int reading_shell_script = 0; + +/* Special debugging helper. */ +int debugging_login_shell = 0; + +/* The environment that the shell passes to other commands. */ +char **shell_environment; + +/* Non-zero when we are executing a top-level command. */ +int executing = 0; + +/* The number of commands executed so far. */ +int current_command_number = 1; + +/* Non-zero is the recursion depth for commands. */ +int indirection_level = 0; + +/* The name of this shell, as taken from argv[0]. */ +char *shell_name = (char *)NULL; + +/* time in seconds when the shell was started */ +time_t shell_start_time; +struct timeval shellstart; + +/* Are we running in an emacs shell window? */ +int running_under_emacs; + +/* Do we have /dev/fd? */ +#ifdef HAVE_DEV_FD +int have_devfd = HAVE_DEV_FD; +#else +int have_devfd = 0; +#endif + +/* The name of the .(shell)rc file. */ +static char *bashrc_file = DEFAULT_BASHRC; + +/* Non-zero means to act more like the Bourne shell on startup. */ +static int act_like_sh; + +/* Non-zero if this shell is being run by `su'. */ +static int su_shell; + +/* Non-zero if we have already expanded and sourced $ENV. */ +static int sourced_env; + +/* Is this shell running setuid? */ +static int running_setuid; + +/* Values for the long-winded argument names. */ +static int debugging; /* Do debugging things. */ +static int no_rc; /* Don't execute ~/.bashrc */ +static int no_profile; /* Don't execute .profile */ +static int do_version; /* Display interesting version info. */ +static int make_login_shell; /* Make this shell be a `-bash' shell. */ +static int want_initial_help; /* --help option */ + +int debugging_mode = 0; /* In debugging mode with --debugger */ +#if defined (READLINE) +int no_line_editing = 0; /* non-zero -> don't do fancy line editing. */ +#else +int no_line_editing = 1; /* can't have line editing without readline */ +#endif +#if defined (TRANSLATABLE_STRINGS) +int dump_translatable_strings; /* Dump strings in $"...", don't execute. */ +int dump_po_strings; /* Dump strings in $"..." in po format */ +#endif +int wordexp_only = 0; /* Do word expansion only */ +int protected_mode = 0; /* No command substitution with --wordexp */ + +int pretty_print_mode = 0; /* pretty-print a shell script */ + +#if defined (STRICT_POSIX) +int posixly_correct = 1; /* Non-zero means posix.2 superset. */ +#else +int posixly_correct = 0; /* Non-zero means posix.2 superset. */ +#endif + +/* Some long-winded argument names. These are obviously new. */ +#define Int 1 +#define Charp 2 +static const struct { + const char *name; + int type; + int *int_value; + char **char_value; +} long_args[] = { + { "debug", Int, &debugging, (char **)0x0 }, +#if defined (DEBUGGER) + { "debugger", Int, &debugging_mode, (char **)0x0 }, +#endif +#if defined (TRANSLATABLE_STRINGS) + { "dump-po-strings", Int, &dump_po_strings, (char **)0x0 }, + { "dump-strings", Int, &dump_translatable_strings, (char **)0x0 }, +#endif + { "help", Int, &want_initial_help, (char **)0x0 }, + { "init-file", Charp, (int *)0x0, &bashrc_file }, + { "login", Int, &make_login_shell, (char **)0x0 }, + { "noediting", Int, &no_line_editing, (char **)0x0 }, + { "noprofile", Int, &no_profile, (char **)0x0 }, + { "norc", Int, &no_rc, (char **)0x0 }, + { "posix", Int, &posixly_correct, (char **)0x0 }, + { "pretty-print", Int, &pretty_print_mode, (char **)0x0 }, +#if defined (WORDEXP_OPTION) + { "protected", Int, &protected_mode, (char **)0x0 }, +#endif + { "rcfile", Charp, (int *)0x0, &bashrc_file }, +#if defined (RESTRICTED_SHELL) + { "restricted", Int, &restricted, (char **)0x0 }, +#endif + { "verbose", Int, &verbose_flag, (char **)0x0 }, + { "version", Int, &do_version, (char **)0x0 }, +#if defined (WORDEXP_OPTION) + { "wordexp", Int, &wordexp_only, (char **)0x0 }, +#endif + { (char *)0x0, Int, (int *)0x0, (char **)0x0 } +}; + +/* These are extern so execute_simple_command can set them, and then + longjmp back to main to execute a shell script, instead of calling + main () again and resulting in indefinite, possibly fatal, stack + growth. */ +procenv_t subshell_top_level; +int subshell_argc; +char **subshell_argv; +char **subshell_envp; + +char *exec_argv0; + +#if defined (BUFFERED_INPUT) +/* The file descriptor from which the shell is reading input. */ +int default_buffered_input = -1; +#endif + +/* The following two variables are not static so they can show up in $-. */ +int read_from_stdin; /* -s flag supplied */ +int want_pending_command; /* -c flag supplied */ + +/* This variable is not static so it can be bound to $BASH_EXECUTION_STRING */ +char *command_execution_string; /* argument to -c option */ +char *shell_script_filename; /* shell script */ + +int malloc_trace_at_exit = 0; + +static int shell_reinitialized = 0; + +static FILE *default_input; + +static STRING_INT_ALIST *shopt_alist; +static int shopt_ind = 0, shopt_len = 0; + +static int parse_long_options PARAMS((char **, int, int)); +static int parse_shell_options PARAMS((char **, int, int)); +static int bind_args PARAMS((char **, int, int, int)); + +static void start_debugger PARAMS((void)); + +static void add_shopt_to_alist PARAMS((char *, int)); +static void run_shopt_alist PARAMS((void)); + +static void execute_env_file PARAMS((char *)); +static void run_startup_files PARAMS((void)); +static int open_shell_script PARAMS((char *)); +static void set_bash_input PARAMS((void)); +static int run_one_command PARAMS((char *)); +#if defined (WORDEXP_OPTION) +static int run_wordexp PARAMS((char *)); +#endif + +static int uidget PARAMS((void)); + +static void set_option_defaults PARAMS((void)); +static void reset_option_defaults PARAMS((void)); + +static void init_interactive PARAMS((void)); +static void init_noninteractive PARAMS((void)); +static void init_interactive_script PARAMS((void)); + +static void set_shell_name PARAMS((char *)); +static void shell_initialize PARAMS((void)); +static void shell_reinitialize PARAMS((void)); + +static void show_shell_usage PARAMS((FILE *, int)); + +#ifdef __CYGWIN__ +static void +_cygwin32_check_tmp () +{ + struct stat sb; + + if (stat ("/tmp", &sb) < 0) + internal_warning (_("could not find /tmp, please create!")); + else + { + if (S_ISDIR (sb.st_mode) == 0) + internal_warning (_("/tmp must be a valid directory name")); + } +} +#endif /* __CYGWIN__ */ + +#if defined (NO_MAIN_ENV_ARG) +/* systems without third argument to main() */ +int +main (argc, argv) + int argc; + char **argv; +#else /* !NO_MAIN_ENV_ARG */ +int +main (argc, argv, env) + int argc; + char **argv, **env; +#endif /* !NO_MAIN_ENV_ARG */ +{ + register int i; + int code, old_errexit_flag; +#if defined (RESTRICTED_SHELL) + int saverst; +#endif + volatile int locally_skip_execution; + volatile int arg_index, top_level_arg_index; +#ifdef __OPENNT + char **env; + + env = environ; +#endif /* __OPENNT */ + + USE_VAR(argc); + USE_VAR(argv); + USE_VAR(env); + USE_VAR(code); + USE_VAR(old_errexit_flag); +#if defined (RESTRICTED_SHELL) + USE_VAR(saverst); +#endif + + /* Catch early SIGINTs. */ + code = setjmp_nosigs (top_level); + if (code) + exit (2); + + xtrace_init (); + +#if defined (USING_BASH_MALLOC) && defined (DEBUG) && !defined (DISABLE_MALLOC_WRAPPERS) + malloc_set_register (1); /* XXX - change to 1 for malloc debugging */ +#endif + + check_dev_tty (); + +#ifdef __CYGWIN__ + _cygwin32_check_tmp (); +#endif /* __CYGWIN__ */ + + /* Wait forever if we are debugging a login shell. */ + while (debugging_login_shell) sleep (3); + + set_default_locale (); + + running_setuid = uidget (); + + if (getenv ("POSIXLY_CORRECT") || getenv ("POSIX_PEDANTIC")) + posixly_correct = 1; + +#if defined (USE_GNU_MALLOC_LIBRARY) + mcheck (programming_error, (void (*) ())0); +#endif /* USE_GNU_MALLOC_LIBRARY */ + + if (setjmp_sigs (subshell_top_level)) + { + argc = subshell_argc; + argv = subshell_argv; + env = subshell_envp; + sourced_env = 0; + } + + shell_reinitialized = 0; + + /* Initialize `local' variables for all `invocations' of main (). */ + arg_index = 1; + if (arg_index > argc) + arg_index = argc; + command_execution_string = shell_script_filename = (char *)NULL; + want_pending_command = locally_skip_execution = read_from_stdin = 0; + default_input = stdin; +#if defined (BUFFERED_INPUT) + default_buffered_input = -1; +#endif + + /* Fix for the `infinite process creation' bug when running shell scripts + from startup files on System V. */ + login_shell = make_login_shell = 0; + + /* If this shell has already been run, then reinitialize it to a + vanilla state. */ + if (shell_initialized || shell_name) + { + /* Make sure that we do not infinitely recurse as a login shell. */ + if (*shell_name == '-') + shell_name++; + + shell_reinitialize (); + if (setjmp_nosigs (top_level)) + exit (2); + } + + shell_environment = env; + set_shell_name (argv[0]); + + gettimeofday (&shellstart, 0); + shell_start_time = shellstart.tv_sec; + + /* Parse argument flags from the input line. */ + + /* Find full word arguments first. */ + arg_index = parse_long_options (argv, arg_index, argc); + + if (want_initial_help) + { + show_shell_usage (stdout, 1); + exit (EXECUTION_SUCCESS); + } + + if (do_version) + { + show_shell_version (1); + exit (EXECUTION_SUCCESS); + } + + echo_input_at_read = verbose_flag; /* --verbose given */ + + /* All done with full word options; do standard shell option parsing.*/ + this_command_name = shell_name; /* for error reporting */ + arg_index = parse_shell_options (argv, arg_index, argc); + + /* If user supplied the "--login" (or -l) flag, then set and invert + LOGIN_SHELL. */ + if (make_login_shell) + { + login_shell++; + login_shell = -login_shell; + } + + set_login_shell ("login_shell", login_shell != 0); + +#if defined (TRANSLATABLE_STRINGS) + if (dump_po_strings) + dump_translatable_strings = 1; + + if (dump_translatable_strings) + read_but_dont_execute = 1; +#endif + + if (running_setuid && privileged_mode == 0) + disable_priv_mode (); + + /* Need to get the argument to a -c option processed in the + above loop. The next arg is a command to execute, and the + following args are $0...$n respectively. */ + if (want_pending_command) + { + command_execution_string = argv[arg_index]; + if (command_execution_string == 0) + { + report_error (_("%s: option requires an argument"), "-c"); + exit (EX_BADUSAGE); + } + arg_index++; + } + this_command_name = (char *)NULL; + + /* First, let the outside world know about our interactive status. + A shell is interactive if the `-i' flag was given, or if all of + the following conditions are met: + no -c command + no arguments remaining or the -s flag given + standard input is a terminal + standard error is a terminal + Refer to Posix.2, the description of the `sh' utility. */ + + if (forced_interactive || /* -i flag */ + (!command_execution_string && /* No -c command and ... */ + wordexp_only == 0 && /* No --wordexp and ... */ + ((arg_index == argc) || /* no remaining args or... */ + read_from_stdin) && /* -s flag with args, and */ + isatty (fileno (stdin)) && /* Input is a terminal and */ + isatty (fileno (stderr)))) /* error output is a terminal. */ + init_interactive (); + else + init_noninteractive (); + + /* + * Some systems have the bad habit of starting login shells with lots of open + * file descriptors. For instance, most systems that have picked up the + * pre-4.0 Sun YP code leave a file descriptor open each time you call one + * of the getpw* functions, and it's set to be open across execs. That + * means one for login, one for xterm, one for shelltool, etc. There are + * also systems that open persistent FDs to other agents or files as part + * of process startup; these need to be set to be close-on-exec. + */ + if (login_shell && interactive_shell) + { + for (i = 3; i < 20; i++) + SET_CLOSE_ON_EXEC (i); + } + + /* If we're in a strict Posix.2 mode, turn on interactive comments, + alias expansion in non-interactive shells, and other Posix.2 things. */ + if (posixly_correct) + { + bind_variable ("POSIXLY_CORRECT", "y", 0); + sv_strict_posix ("POSIXLY_CORRECT"); + } + + /* Now we run the shopt_alist and process the options. */ + if (shopt_alist) + run_shopt_alist (); + + /* From here on in, the shell must be a normal functioning shell. + Variables from the environment are expected to be set, etc. */ + shell_initialize (); + + set_default_lang (); + set_default_locale_vars (); + + /* + * M-x term -> TERM=eterm-color INSIDE_EMACS='251,term:0.96' (eterm) + * M-x shell -> TERM='dumb' INSIDE_EMACS='25.1,comint' (no line editing) + * + * Older versions of Emacs may set EMACS to 't' or to something like + * '22.1 (term:0.96)' instead of (or in addition to) setting INSIDE_EMACS. + * They may set TERM to 'eterm' instead of 'eterm-color'. They may have + * a now-obsolete command that sets neither EMACS nor INSIDE_EMACS: + * M-x terminal -> TERM='emacs-em7955' (line editing) + */ + if (interactive_shell) + { + char *term, *emacs, *inside_emacs; + int emacs_term, in_emacs; + + term = get_string_value ("TERM"); + emacs = get_string_value ("EMACS"); + inside_emacs = get_string_value ("INSIDE_EMACS"); + + if (inside_emacs) + { + emacs_term = strstr (inside_emacs, ",term:") != 0; + in_emacs = 1; + } + else if (emacs) + { + /* Infer whether we are in an older Emacs. */ + emacs_term = strstr (emacs, " (term:") != 0; + in_emacs = emacs_term || STREQ (emacs, "t"); + } + else + in_emacs = emacs_term = 0; + + /* Not sure any emacs terminal emulator sets TERM=emacs any more */ + no_line_editing |= STREQ (term, "emacs"); + no_line_editing |= in_emacs && STREQ (term, "dumb"); + + /* running_under_emacs == 2 for `eterm' */ + running_under_emacs = in_emacs || STREQN (term, "emacs", 5); + running_under_emacs += emacs_term && STREQN (term, "eterm", 5); + + if (running_under_emacs) + gnu_error_format = 1; + } + + top_level_arg_index = arg_index; + old_errexit_flag = exit_immediately_on_error; + + /* Give this shell a place to longjmp to before executing the + startup files. This allows users to press C-c to abort the + lengthy startup. */ + code = setjmp_sigs (top_level); + if (code) + { + if (code == EXITPROG || code == ERREXIT || code == EXITBLTIN) + exit_shell (last_command_exit_value); + else + { +#if defined (JOB_CONTROL) + /* Reset job control, since run_startup_files turned it off. */ + set_job_control (interactive_shell); +#endif + /* Reset value of `set -e', since it's turned off before running + the startup files. */ + exit_immediately_on_error += old_errexit_flag; + locally_skip_execution++; + } + } + + arg_index = top_level_arg_index; + + /* Execute the start-up scripts. */ + + if (interactive_shell == 0) + { + unbind_variable ("PS1"); + unbind_variable ("PS2"); + interactive = 0; +#if 0 + /* This has already been done by init_noninteractive */ + expand_aliases = posixly_correct; +#endif + } + else + { + change_flag ('i', FLAG_ON); + interactive = 1; + } + +#if defined (RESTRICTED_SHELL) + /* Set restricted_shell based on whether the basename of $0 indicates that + the shell should be restricted or if the `-r' option was supplied at + startup. */ + restricted_shell = shell_is_restricted (shell_name); + + /* If the `-r' option is supplied at invocation, make sure that the shell + is not in restricted mode when running the startup files. */ + saverst = restricted; + restricted = 0; +#endif + + /* Set positional parameters before running startup files. top_level_arg_index + holds the index of the current argument before setting the positional + parameters, so any changes performed in the startup files won't affect + later option processing. */ + if (wordexp_only) + ; /* nothing yet */ + else if (command_execution_string) + arg_index = bind_args (argv, arg_index, argc, 0); /* $0 ... $n */ + else if (arg_index != argc && read_from_stdin == 0) + { + shell_script_filename = argv[arg_index++]; + arg_index = bind_args (argv, arg_index, argc, 1); /* $1 ... $n */ + } + else + arg_index = bind_args (argv, arg_index, argc, 1); /* $1 ... $n */ + + /* The startup files are run with `set -e' temporarily disabled. */ + if (locally_skip_execution == 0 && running_setuid == 0) + { + char *t; + + old_errexit_flag = exit_immediately_on_error; + exit_immediately_on_error = 0; + + /* Temporarily set $0 while running startup files, then restore it so + we get better error messages when trying to open script files. */ + if (shell_script_filename) + { + t = dollar_vars[0]; + dollar_vars[0] = exec_argv0 ? savestring (exec_argv0) : savestring (shell_script_filename); + } + run_startup_files (); + if (shell_script_filename) + { + free (dollar_vars[0]); + dollar_vars[0] = t; + } + exit_immediately_on_error += old_errexit_flag; + } + + /* If we are invoked as `sh', turn on Posix mode. */ + if (act_like_sh) + { + bind_variable ("POSIXLY_CORRECT", "y", 0); + sv_strict_posix ("POSIXLY_CORRECT"); + } + +#if defined (RESTRICTED_SHELL) + /* Turn on the restrictions after executing the startup files. This + means that `bash -r' or `set -r' invoked from a startup file will + turn on the restrictions after the startup files are executed. */ + restricted = saverst || restricted; + if (shell_reinitialized == 0) + maybe_make_restricted (shell_name); +#endif /* RESTRICTED_SHELL */ + +#if defined (WORDEXP_OPTION) + if (wordexp_only) + { + startup_state = 3; + last_command_exit_value = run_wordexp (argv[top_level_arg_index]); + exit_shell (last_command_exit_value); + } +#endif + + cmd_init (); /* initialize the command object caches */ + uwp_init (); + + if (command_execution_string) + { + startup_state = 2; + + if (debugging_mode) + start_debugger (); + +#if defined (ONESHOT) + executing = 1; + run_one_command (command_execution_string); + exit_shell (last_command_exit_value); +#else /* ONESHOT */ + with_input_from_string (command_execution_string, "-c"); + goto read_and_execute; +#endif /* !ONESHOT */ + } + + /* Get possible input filename and set up default_buffered_input or + default_input as appropriate. */ + if (shell_script_filename) + open_shell_script (shell_script_filename); + else if (interactive == 0) + { + /* In this mode, bash is reading a script from stdin, which is a + pipe or redirected file. */ +#if defined (BUFFERED_INPUT) + default_buffered_input = fileno (stdin); /* == 0 */ +#else + setbuf (default_input, (char *)NULL); +#endif /* !BUFFERED_INPUT */ + read_from_stdin = 1; + } + else if (top_level_arg_index == argc) /* arg index before startup files */ + /* "If there are no operands and the -c option is not specified, the -s + option shall be assumed." */ + read_from_stdin = 1; + + set_bash_input (); + + if (debugging_mode && locally_skip_execution == 0 && running_setuid == 0 && (reading_shell_script || interactive_shell == 0)) + start_debugger (); + + /* Do the things that should be done only for interactive shells. */ + if (interactive_shell) + { + /* Set up for checking for presence of mail. */ + reset_mail_timer (); + init_mail_dates (); + +#if defined (HISTORY) + /* Initialize the interactive history stuff. */ + bash_initialize_history (); + /* Don't load the history from the history file if we've already + saved some lines in this session (e.g., by putting `history -s xx' + into one of the startup files). */ + if (shell_initialized == 0 && history_lines_this_session == 0) + load_history (); +#endif /* HISTORY */ + + /* Initialize terminal state for interactive shells after the + .bash_profile and .bashrc are interpreted. */ + get_tty_state (); + } + +#if !defined (ONESHOT) + read_and_execute: +#endif /* !ONESHOT */ + + shell_initialized = 1; + + if (pretty_print_mode && interactive_shell) + { + internal_warning (_("pretty-printing mode ignored in interactive shells")); + pretty_print_mode = 0; + } + if (pretty_print_mode) + exit_shell (pretty_print_loop ()); + + /* Read commands until exit condition. */ + reader_loop (); + exit_shell (last_command_exit_value); +} + +static int +parse_long_options (argv, arg_start, arg_end) + char **argv; + int arg_start, arg_end; +{ + int arg_index, longarg, i; + char *arg_string; + + arg_index = arg_start; + while ((arg_index != arg_end) && (arg_string = argv[arg_index]) && + (*arg_string == '-')) + { + longarg = 0; + + /* Make --login equivalent to -login. */ + if (arg_string[1] == '-' && arg_string[2]) + { + longarg = 1; + arg_string++; + } + + for (i = 0; long_args[i].name; i++) + { + if (STREQ (arg_string + 1, long_args[i].name)) + { + if (long_args[i].type == Int) + *long_args[i].int_value = 1; + else if (argv[++arg_index] == 0) + { + report_error (_("%s: option requires an argument"), long_args[i].name); + exit (EX_BADUSAGE); + } + else + *long_args[i].char_value = argv[arg_index]; + + break; + } + } + if (long_args[i].name == 0) + { + if (longarg) + { + report_error (_("%s: invalid option"), argv[arg_index]); + show_shell_usage (stderr, 0); + exit (EX_BADUSAGE); + } + break; /* No such argument. Maybe flag arg. */ + } + + arg_index++; + } + + return (arg_index); +} + +static int +parse_shell_options (argv, arg_start, arg_end) + char **argv; + int arg_start, arg_end; +{ + int arg_index; + int arg_character, on_or_off, next_arg, i; + char *o_option, *arg_string; + + arg_index = arg_start; + while (arg_index != arg_end && (arg_string = argv[arg_index]) && + (*arg_string == '-' || *arg_string == '+')) + { + /* There are flag arguments, so parse them. */ + next_arg = arg_index + 1; + + /* A single `-' signals the end of options. From the 4.3 BSD sh. + An option `--' means the same thing; this is the standard + getopt(3) meaning. */ + if (arg_string[0] == '-' && + (arg_string[1] == '\0' || + (arg_string[1] == '-' && arg_string[2] == '\0'))) + return (next_arg); + + i = 1; + on_or_off = arg_string[0]; + while (arg_character = arg_string[i++]) + { + switch (arg_character) + { + case 'c': + want_pending_command = 1; + break; + + case 'l': + make_login_shell = 1; + break; + + case 's': + read_from_stdin = 1; + break; + + case 'o': + o_option = argv[next_arg]; + if (o_option == 0) + { + set_option_defaults (); + list_minus_o_opts (-1, (on_or_off == '-') ? 0 : 1); + reset_option_defaults (); + break; + } + if (set_minus_o_option (on_or_off, o_option) != EXECUTION_SUCCESS) + exit (EX_BADUSAGE); + next_arg++; + break; + + case 'O': + /* Since some of these can be overridden by the normal + interactive/non-interactive shell initialization or + initializing posix mode, we save the options and process + them after initialization. */ + o_option = argv[next_arg]; + if (o_option == 0) + { + shopt_listopt (o_option, (on_or_off == '-') ? 0 : 1); + break; + } + add_shopt_to_alist (o_option, on_or_off); + next_arg++; + break; + + case 'D': +#if defined (TRANSLATABLE_STRINGS) + dump_translatable_strings = 1; +#endif + break; + + default: + if (change_flag (arg_character, on_or_off) == FLAG_ERROR) + { + report_error (_("%c%c: invalid option"), on_or_off, arg_character); + show_shell_usage (stderr, 0); + exit (EX_BADUSAGE); + } + } + } + /* Can't do just a simple increment anymore -- what about + "bash -abouo emacs ignoreeof -hP"? */ + arg_index = next_arg; + } + + return (arg_index); +} + +/* Exit the shell with status S. */ +void +exit_shell (s) + int s; +{ + fflush (stdout); /* XXX */ + fflush (stderr); + + /* Clean up the terminal if we are in a state where it's been modified. */ +#if defined (READLINE) + if (RL_ISSTATE (RL_STATE_TERMPREPPED) && rl_deprep_term_function) + (*rl_deprep_term_function) (); +#endif + if (read_tty_modified ()) + read_tty_cleanup (); + + /* Do trap[0] if defined. Allow it to override the exit status + passed to us. */ + if (signal_is_trapped (0)) + s = run_exit_trap (); + +#if defined (PROCESS_SUBSTITUTION) + unlink_all_fifos (); +#endif /* PROCESS_SUBSTITUTION */ + +#if defined (HISTORY) + if (remember_on_history) + maybe_save_shell_history (); +#endif /* HISTORY */ + +#if defined (COPROCESS_SUPPORT) + coproc_flush (); +#endif + +#if defined (JOB_CONTROL) + /* If the user has run `shopt -s huponexit', hangup all jobs when we exit + an interactive login shell. ksh does this unconditionally. */ + if (interactive_shell && login_shell && hup_on_exit) + hangup_all_jobs (); + + /* If this shell is interactive, or job control is active, terminate all + stopped jobs and restore the original terminal process group. Don't do + this if we're in a subshell and calling exit_shell after, for example, + a failed word expansion. We want to do this even if the shell is not + interactive because we set the terminal's process group when job control + is enabled regardless of the interactive status. */ + if (subshell_environment == 0) + end_job_control (); +#endif /* JOB_CONTROL */ + + /* Always return the exit status of the last command to our parent. */ + sh_exit (s); +} + +/* A wrapper for exit that (optionally) can do other things, like malloc + statistics tracing. */ +void +sh_exit (s) + int s; +{ +#if defined (MALLOC_DEBUG) && defined (USING_BASH_MALLOC) + if (malloc_trace_at_exit && (subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PROCSUB)) == 0) + trace_malloc_stats (get_name_for_error (), (char *)NULL); + /* mlocation_write_table (); */ +#endif + + exit (s); +} + +/* Exit a subshell, which includes calling the exit trap. We don't want to + do any more cleanup, since a subshell is created as an exact copy of its + parent. */ +void +subshell_exit (s) + int s; +{ + fflush (stdout); + fflush (stderr); + + /* Do trap[0] if defined. Allow it to override the exit status + passed to us. */ + last_command_exit_value = s; + if (signal_is_trapped (0)) + s = run_exit_trap (); + + sh_exit (s); +} + +void +set_exit_status (s) + int s; +{ + set_pipestatus_from_exit (last_command_exit_value = s); +} + +/* Source the bash startup files. If POSIXLY_CORRECT is non-zero, we obey + the Posix.2 startup file rules: $ENV is expanded, and if the file it + names exists, that file is sourced. The Posix.2 rules are in effect + for interactive shells only. (section 4.56.5.3) */ + +/* Execute ~/.bashrc for most shells. Never execute it if + ACT_LIKE_SH is set, or if NO_RC is set. + + If the executable file "/usr/gnu/src/bash/foo" contains: + + #!/usr/gnu/bin/bash + echo hello + + then: + + COMMAND EXECUTE BASHRC + -------------------------------- + bash -c foo NO + bash foo NO + foo NO + rsh machine ls YES (for rsh, which calls `bash -c') + rsh machine foo YES (for shell started by rsh) NO (for foo!) + echo ls | bash NO + login NO + bash YES +*/ + +static void +execute_env_file (env_file) + char *env_file; +{ + char *fn; + + if (env_file && *env_file) + { + fn = expand_string_unsplit_to_string (env_file, Q_DOUBLE_QUOTES); + if (fn && *fn) + maybe_execute_file (fn, 1); + FREE (fn); + } +} + +static void +run_startup_files () +{ +#if defined (JOB_CONTROL) + int old_job_control; +#endif + int sourced_login, run_by_ssh; + +#if 1 /* TAG:bash-5.3 andrew.gregory.8@gmail.com 2/21/2022 */ + /* get the rshd/sshd case out of the way first. */ + if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 && + act_like_sh == 0 && command_execution_string) + { +#ifdef SSH_SOURCE_BASHRC + run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) || + (find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0); +#else + run_by_ssh = 0; +#endif +#endif + + /* If we were run by sshd or we think we were run by rshd, execute + ~/.bashrc if we are a top-level shell. */ +#if 1 /* TAG:bash-5.3 */ + if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2) +#else + if (isnetconn (fileno (stdin) && shell_level < 2) +#endif + { +#ifdef SYS_BASHRC +# if defined (__OPENNT) + maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); +# else + maybe_execute_file (SYS_BASHRC, 1); +# endif +#endif + maybe_execute_file (bashrc_file, 1); + return; + } + } + +#if defined (JOB_CONTROL) + /* Startup files should be run without job control enabled. */ + old_job_control = interactive_shell ? set_job_control (0) : 0; +#endif + + sourced_login = 0; + + /* A shell begun with the --login (or -l) flag that is not in posix mode + runs the login shell startup files, no matter whether or not it is + interactive. If NON_INTERACTIVE_LOGIN_SHELLS is defined, run the + startup files if argv[0][0] == '-' as well. */ +#if defined (NON_INTERACTIVE_LOGIN_SHELLS) + if (login_shell && posixly_correct == 0) +#else + if (login_shell < 0 && posixly_correct == 0) +#endif + { + /* We don't execute .bashrc for login shells. */ + no_rc++; + + /* Execute /etc/profile and one of the personal login shell + initialization files. */ + if (no_profile == 0) + { + maybe_execute_file (SYS_PROFILE, 1); + + if (act_like_sh) /* sh */ + maybe_execute_file ("~/.profile", 1); + else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && + (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ + maybe_execute_file ("~/.profile", 1); + } + + sourced_login = 1; + } + + /* A non-interactive shell not named `sh' and not in posix mode reads and + executes commands from $BASH_ENV. If `su' starts a shell with `-c cmd' + and `-su' as the name of the shell, we want to read the startup files. + No other non-interactive shells read any startup files. */ + if (interactive_shell == 0 && !(su_shell && login_shell)) + { + if (posixly_correct == 0 && act_like_sh == 0 && privileged_mode == 0 && + sourced_env++ == 0) + execute_env_file (get_string_value ("BASH_ENV")); + return; + } + + /* Interactive shell or `-su' shell. */ + if (posixly_correct == 0) /* bash, sh */ + { + if (login_shell && sourced_login++ == 0) + { + /* We don't execute .bashrc for login shells. */ + no_rc++; + + /* Execute /etc/profile and one of the personal login shell + initialization files. */ + if (no_profile == 0) + { + maybe_execute_file (SYS_PROFILE, 1); + + if (act_like_sh) /* sh */ + maybe_execute_file ("~/.profile", 1); + else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && + (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ + maybe_execute_file ("~/.profile", 1); + } + } + + /* bash */ + if (act_like_sh == 0 && no_rc == 0) + { +#ifdef SYS_BASHRC +# if defined (__OPENNT) + maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); +# else + maybe_execute_file (SYS_BASHRC, 1); +# endif +#endif + maybe_execute_file (bashrc_file, 1); + } + /* sh */ + else if (act_like_sh && privileged_mode == 0 && sourced_env++ == 0) + execute_env_file (get_string_value ("ENV")); + } + else /* bash --posix, sh --posix */ + { + /* bash and sh */ + if (interactive_shell && privileged_mode == 0 && sourced_env++ == 0) + execute_env_file (get_string_value ("ENV")); + } + +#if defined (JOB_CONTROL) + set_job_control (old_job_control); +#endif +} + +#if defined (RESTRICTED_SHELL) +/* Return 1 if the shell should be a restricted one based on NAME or the + value of `restricted'. Don't actually do anything, just return a + boolean value. */ +int +shell_is_restricted (name) + char *name; +{ + char *temp; + + if (restricted) + return 1; + temp = base_pathname (name); + if (*temp == '-') + temp++; + return (STREQ (temp, RESTRICTED_SHELL_NAME)); +} + +/* Perhaps make this shell a `restricted' one, based on NAME. If the + basename of NAME is "rbash", then this shell is restricted. The + name of the restricted shell is a configurable option, see config.h. + In a restricted shell, PATH, SHELL, ENV, and BASH_ENV are read-only + and non-unsettable. + Do this also if `restricted' is already set to 1; maybe the shell was + started with -r. */ +int +maybe_make_restricted (name) + char *name; +{ + char *temp; + + temp = base_pathname (name); + if (*temp == '-') + temp++; + if (restricted || (STREQ (temp, RESTRICTED_SHELL_NAME))) + { +#if defined (RBASH_STATIC_PATH_VALUE) + bind_variable ("PATH", RBASH_STATIC_PATH_VALUE, 0); + stupidly_hack_special_variables ("PATH"); /* clear hash table */ +#endif + set_var_read_only ("PATH"); + set_var_read_only ("SHELL"); + set_var_read_only ("ENV"); + set_var_read_only ("BASH_ENV"); + set_var_read_only ("HISTFILE"); + restricted = 1; + } + return (restricted); +} +#endif /* RESTRICTED_SHELL */ + +/* Fetch the current set of uids and gids and return 1 if we're running + setuid or setgid. */ +static int +uidget () +{ + uid_t u; + + u = getuid (); + if (current_user.uid != u) + { + FREE (current_user.user_name); + FREE (current_user.shell); + FREE (current_user.home_dir); + current_user.user_name = current_user.shell = current_user.home_dir = (char *)NULL; + } + current_user.uid = u; + current_user.gid = getgid (); + current_user.euid = geteuid (); + current_user.egid = getegid (); + + /* See whether or not we are running setuid or setgid. */ + return (current_user.uid != current_user.euid) || + (current_user.gid != current_user.egid); +} + +void +disable_priv_mode () +{ + int e; + +#if HAVE_SETRESUID + if (setresuid (current_user.uid, current_user.uid, current_user.uid) < 0) +#else + if (setuid (current_user.uid) < 0) +#endif + { + e = errno; + sys_error (_("cannot set uid to %d: effective uid %d"), current_user.uid, current_user.euid); +#if defined (EXIT_ON_SETUID_FAILURE) + if (e == EAGAIN) + exit (e); +#endif + } +#if HAVE_SETRESGID + if (setresgid (current_user.gid, current_user.gid, current_user.gid) < 0) +#else + if (setgid (current_user.gid) < 0) +#endif + sys_error (_("cannot set gid to %d: effective gid %d"), current_user.gid, current_user.egid); + + current_user.euid = current_user.uid; + current_user.egid = current_user.gid; +} + +#if defined (WORDEXP_OPTION) +static int +run_wordexp (words) + char *words; +{ + int code, nw, nb; + WORD_LIST *wl, *tl, *result; + + code = setjmp_nosigs (top_level); + + if (code != NOT_JUMPED) + { + switch (code) + { + /* Some kind of throw to top_level has occurred. */ + case FORCE_EOF: + return last_command_exit_value = 127; + case ERREXIT: + case EXITPROG: + case EXITBLTIN: + return last_command_exit_value; + case DISCARD: + return last_command_exit_value = 1; + default: + command_error ("run_wordexp", CMDERR_BADJUMP, code, 0); + } + } + + /* Run it through the parser to get a list of words and expand them */ + if (words && *words) + { + with_input_from_string (words, "--wordexp"); + if (parse_command () != 0) + return (126); + if (global_command == 0) + { + printf ("0\n0\n"); + return (0); + } + if (global_command->type != cm_simple) + return (126); + wl = global_command->value.Simple->words; + if (protected_mode) + for (tl = wl; tl; tl = tl->next) + tl->word->flags |= W_NOCOMSUB|W_NOPROCSUB; + result = wl ? expand_words_no_vars (wl) : (WORD_LIST *)0; + } + else + result = (WORD_LIST *)0; + + last_command_exit_value = 0; + + if (result == 0) + { + printf ("0\n0\n"); + return (0); + } + + /* Count up the number of words and bytes, and print them. Don't count + the trailing NUL byte. */ + for (nw = nb = 0, wl = result; wl; wl = wl->next) + { + nw++; + nb += strlen (wl->word->word); + } + printf ("%u\n%u\n", nw, nb); + /* Print each word on a separate line. This will have to be changed when + the interface to glibc is completed. */ + for (wl = result; wl; wl = wl->next) + printf ("%s\n", wl->word->word); + + return (0); +} +#endif + +#if defined (ONESHOT) +/* Run one command, given as the argument to the -c option. Tell + parse_and_execute not to fork for a simple command. */ +static int +run_one_command (command) + char *command; +{ + int code; + + code = setjmp_nosigs (top_level); + + if (code != NOT_JUMPED) + { +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + switch (code) + { + /* Some kind of throw to top_level has occurred. */ + case FORCE_EOF: + return last_command_exit_value = 127; + case ERREXIT: + case EXITPROG: + case EXITBLTIN: + return last_command_exit_value; + case DISCARD: + return last_command_exit_value = 1; + default: + command_error ("run_one_command", CMDERR_BADJUMP, code, 0); + } + } + return (parse_and_execute (savestring (command), "-c", SEVAL_NOHIST|SEVAL_RESETLINE)); +} +#endif /* ONESHOT */ + +static int +bind_args (argv, arg_start, arg_end, start_index) + char **argv; + int arg_start, arg_end, start_index; +{ + register int i; + WORD_LIST *args, *tl; + + for (i = arg_start, args = tl = (WORD_LIST *)NULL; i < arg_end; i++) + { + if (args == 0) + args = tl = make_word_list (make_word (argv[i]), args); + else + { + tl->next = make_word_list (make_word (argv[i]), (WORD_LIST *)NULL); + tl = tl->next; + } + } + + if (args) + { + if (start_index == 0) /* bind to $0...$n for sh -c command */ + { + /* Posix.2 4.56.3 says that the first argument after sh -c command + becomes $0, and the rest of the arguments become $1...$n */ + shell_name = savestring (args->word->word); + FREE (dollar_vars[0]); + dollar_vars[0] = savestring (args->word->word); + remember_args (args->next, 1); + if (debugging_mode) + { + push_args (args->next); /* BASH_ARGV and BASH_ARGC */ + bash_argv_initialized = 1; + } + } + else /* bind to $1...$n for shell script */ + { + remember_args (args, 1); + /* We do this unconditionally so something like -O extdebug doesn't + do it first. We're setting the definitive positional params + here. */ + if (debugging_mode) + { + push_args (args); /* BASH_ARGV and BASH_ARGC */ + bash_argv_initialized = 1; + } + } + + dispose_words (args); + } + + return (i); +} + +void +unbind_args () +{ + remember_args ((WORD_LIST *)NULL, 1); + pop_args (); /* Reset BASH_ARGV and BASH_ARGC */ +} + +static void +start_debugger () +{ +#if defined (DEBUGGER) && defined (DEBUGGER_START_FILE) + int old_errexit; + int r; + + old_errexit = exit_immediately_on_error; + exit_immediately_on_error = 0; + + r = force_execute_file (DEBUGGER_START_FILE, 1); + if (r < 0) + { + internal_warning (_("cannot start debugger; debugging mode disabled")); + debugging_mode = 0; + } + error_trace_mode = function_trace_mode = debugging_mode; + + set_shellopts (); + set_bashopts (); + + exit_immediately_on_error += old_errexit; +#endif +} + +static int +open_shell_script (script_name) + char *script_name; +{ + int fd, e, fd_is_tty; + char *filename, *path_filename, *t; + char sample[80]; + int sample_len; + struct stat sb; +#if defined (ARRAY_VARS) + SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v; + ARRAY *funcname_a, *bash_source_a, *bash_lineno_a; +#endif + + filename = savestring (script_name); + + fd = open (filename, O_RDONLY); + if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0)) + { + e = errno; + /* If it's not in the current directory, try looking through PATH + for it. */ + path_filename = find_path_file (script_name); + if (path_filename) + { + free (filename); + filename = path_filename; + fd = open (filename, O_RDONLY); + } + else + errno = e; + } + + if (fd < 0) + { + e = errno; + file_error (filename); +#if defined (JOB_CONTROL) + end_job_control (); /* just in case we were run as bash -i script */ +#endif + sh_exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT); + } + + free (dollar_vars[0]); + dollar_vars[0] = exec_argv0 ? savestring (exec_argv0) : savestring (script_name); + if (exec_argv0) + { + free (exec_argv0); + exec_argv0 = (char *)NULL; + } + + if (file_isdir (filename)) + { +#if defined (EISDIR) + errno = EISDIR; +#else + errno = EINVAL; +#endif + file_error (filename); +#if defined (JOB_CONTROL) + end_job_control (); /* just in case we were run as bash -i script */ +#endif + sh_exit (EX_NOINPUT); + } + +#if defined (ARRAY_VARS) + GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a); + GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a); + GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a); + + array_push (bash_source_a, filename); + if (bash_lineno_a) + { + t = itos (executing_line_number ()); + array_push (bash_lineno_a, t); + free (t); + } + array_push (funcname_a, "main"); +#endif + +#ifdef HAVE_DEV_FD + fd_is_tty = isatty (fd); +#else + fd_is_tty = 0; +#endif + + /* Only do this with non-tty file descriptors we can seek on. */ + if (fd_is_tty == 0 && (lseek (fd, 0L, 1) != -1)) + { + /* Check to see if the `file' in `bash file' is a binary file + according to the same tests done by execute_simple_command (), + and report an error and exit if it is. */ + sample_len = read (fd, sample, sizeof (sample)); + if (sample_len < 0) + { + e = errno; + if ((fstat (fd, &sb) == 0) && S_ISDIR (sb.st_mode)) + { +#if defined (EISDIR) + errno = EISDIR; + file_error (filename); +#else + internal_error (_("%s: Is a directory"), filename); +#endif + } + else + { + errno = e; + file_error (filename); + } +#if defined (JOB_CONTROL) + end_job_control (); /* just in case we were run as bash -i script */ +#endif + exit (EX_NOEXEC); + } + else if (sample_len > 0 && (check_binary_file (sample, sample_len))) + { + internal_error (_("%s: cannot execute binary file"), filename); +#if defined (JOB_CONTROL) + end_job_control (); /* just in case we were run as bash -i script */ +#endif + exit (EX_BINARY_FILE); + } + /* Now rewind the file back to the beginning. */ + lseek (fd, 0L, 0); + } + + /* Open the script. But try to move the file descriptor to a randomly + large one, in the hopes that any descriptors used by the script will + not match with ours. */ + fd = move_to_high_fd (fd, 1, -1); + +#if defined (BUFFERED_INPUT) + default_buffered_input = fd; + SET_CLOSE_ON_EXEC (default_buffered_input); +#else /* !BUFFERED_INPUT */ + default_input = fdopen (fd, "r"); + + if (default_input == 0) + { + file_error (filename); + exit (EX_NOTFOUND); + } + + SET_CLOSE_ON_EXEC (fd); + if (fileno (default_input) != fd) + SET_CLOSE_ON_EXEC (fileno (default_input)); +#endif /* !BUFFERED_INPUT */ + + /* Just about the only way for this code to be executed is if something + like `bash -i /dev/stdin' is executed. */ + if (interactive_shell && fd_is_tty) + { + dup2 (fd, 0); + close (fd); + fd = 0; +#if defined (BUFFERED_INPUT) + default_buffered_input = 0; +#else + fclose (default_input); + default_input = stdin; +#endif + } + else if (forced_interactive && fd_is_tty == 0) + /* But if a script is called with something like `bash -i scriptname', + we need to do a non-interactive setup here, since we didn't do it + before. */ + init_interactive_script (); + + free (filename); + + reading_shell_script = 1; + return (fd); +} + +/* Initialize the input routines for the parser. */ +static void +set_bash_input () +{ + /* Make sure the fd from which we are reading input is not in + no-delay mode. */ +#if defined (BUFFERED_INPUT) + if (interactive == 0) + sh_unset_nodelay_mode (default_buffered_input); + else +#endif /* !BUFFERED_INPUT */ + sh_unset_nodelay_mode (fileno (stdin)); + + /* with_input_from_stdin really means `with_input_from_readline' */ + if (interactive && no_line_editing == 0) + with_input_from_stdin (); +#if defined (BUFFERED_INPUT) + else if (interactive == 0) + with_input_from_buffered_stream (default_buffered_input, dollar_vars[0]); +#endif /* BUFFERED_INPUT */ + else + with_input_from_stream (default_input, dollar_vars[0]); +} + +/* Close the current shell script input source and forget about it. This is + extern so execute_cmd.c:initialize_subshell() can call it. If CHECK_ZERO + is non-zero, we close default_buffered_input even if it's the standard + input (fd 0). */ +void +unset_bash_input (check_zero) + int check_zero; +{ +#if defined (BUFFERED_INPUT) + if ((check_zero && default_buffered_input >= 0) || + (check_zero == 0 && default_buffered_input > 0)) + { + close_buffered_fd (default_buffered_input); + default_buffered_input = bash_input.location.buffered_fd = -1; + bash_input.type = st_none; /* XXX */ + } +#else /* !BUFFERED_INPUT */ + if (default_input) + { + fclose (default_input); + default_input = (FILE *)NULL; + } +#endif /* !BUFFERED_INPUT */ +} + + +#if !defined (PROGRAM) +# define PROGRAM "bash" +#endif + +static void +set_shell_name (argv0) + char *argv0; +{ + /* Here's a hack. If the name of this shell is "sh", then don't do + any startup files; just try to be more like /bin/sh. */ + shell_name = argv0 ? base_pathname (argv0) : PROGRAM; + + if (argv0 && *argv0 == '-') + { + if (*shell_name == '-') + shell_name++; + login_shell = 1; + } + + if (shell_name[0] == 's' && shell_name[1] == 'h' && shell_name[2] == '\0') + act_like_sh++; + if (shell_name[0] == 's' && shell_name[1] == 'u' && shell_name[2] == '\0') + su_shell++; + + shell_name = argv0 ? argv0 : PROGRAM; + FREE (dollar_vars[0]); + dollar_vars[0] = savestring (shell_name); + + /* A program may start an interactive shell with + "execl ("/bin/bash", "-", NULL)". + If so, default the name of this shell to our name. */ + if (!shell_name || !*shell_name || (shell_name[0] == '-' && !shell_name[1])) + shell_name = PROGRAM; +} + +/* Some options are initialized to -1 so we have a way to determine whether + they were set on the command line. This is an issue when listing the option + values at invocation (`bash -o'), so we set the defaults here and reset + them after the call to list_minus_o_options (). */ +/* XXX - could also do this for histexp_flag, jobs_m_flag */ +static void +set_option_defaults () +{ +#if defined (HISTORY) + enable_history_list = 0; +#endif +} + +static void +reset_option_defaults () +{ +#if defined (HISTORY) + enable_history_list = -1; +#endif +} + +static void +init_interactive () +{ + expand_aliases = interactive_shell = startup_state = 1; + interactive = 1; +#if defined (HISTORY) + if (enable_history_list == -1) + enable_history_list = 1; /* set default */ + remember_on_history = enable_history_list; +# if defined (BANG_HISTORY) + histexp_flag = history_expansion; /* XXX */ +# endif +#endif +} + +static void +init_noninteractive () +{ +#if defined (HISTORY) + if (enable_history_list == -1) /* set default */ + enable_history_list = 0; + bash_history_reinit (0); +#endif /* HISTORY */ + interactive_shell = startup_state = interactive = 0; + expand_aliases = posixly_correct; /* XXX - was 0 not posixly_correct */ + no_line_editing = 1; +#if defined (JOB_CONTROL) + /* Even if the shell is not interactive, enable job control if the -i or + -m option is supplied at startup. */ + set_job_control (forced_interactive||jobs_m_flag); +#endif /* JOB_CONTROL */ +} + +static void +init_interactive_script () +{ +#if defined (HISTORY) + if (enable_history_list == -1) + enable_history_list = 1; +#endif + init_noninteractive (); + expand_aliases = interactive_shell = startup_state = 1; +#if defined (HISTORY) + remember_on_history = enable_history_list; /* XXX */ +#endif +} + +void +get_current_user_info () +{ + struct passwd *entry; + + /* Don't fetch this more than once. */ + if (current_user.user_name == 0) + { +#if defined (__TANDEM) + entry = getpwnam (getlogin ()); +#else + entry = getpwuid (current_user.uid); +#endif + if (entry) + { + current_user.user_name = savestring (entry->pw_name); + current_user.shell = (entry->pw_shell && entry->pw_shell[0]) + ? savestring (entry->pw_shell) + : savestring ("/bin/sh"); + current_user.home_dir = savestring (entry->pw_dir); + } + else + { + current_user.user_name = _("I have no name!"); + current_user.user_name = savestring (current_user.user_name); + current_user.shell = savestring ("/bin/sh"); + current_user.home_dir = savestring ("/"); + } +#if defined (HAVE_GETPWENT) + endpwent (); +#endif + } +} + +/* Do whatever is necessary to initialize the shell. + Put new initializations in here. */ +static void +shell_initialize () +{ + char hostname[256]; + int should_be_restricted; + + /* Line buffer output for stderr and stdout. */ + if (shell_initialized == 0) + { + sh_setlinebuf (stderr); + sh_setlinebuf (stdout); + } + + /* Sort the array of shell builtins so that the binary search in + find_shell_builtin () works correctly. */ + initialize_shell_builtins (); + + /* Initialize the trap signal handlers before installing our own + signal handlers. traps.c:restore_original_signals () is responsible + for restoring the original default signal handlers. That function + is called when we make a new child. */ + initialize_traps (); + initialize_signals (0); + + /* It's highly unlikely that this will change. */ + if (current_host_name == 0) + { + /* Initialize current_host_name. */ + if (gethostname (hostname, 255) < 0) + current_host_name = "??host??"; + else + current_host_name = savestring (hostname); + } + + /* Initialize the stuff in current_user that comes from the password + file. We don't need to do this right away if the shell is not + interactive. */ + if (interactive_shell) + get_current_user_info (); + + /* Initialize our interface to the tilde expander. */ + tilde_initialize (); + +#if defined (RESTRICTED_SHELL) + should_be_restricted = shell_is_restricted (shell_name); +#endif + + /* Initialize internal and environment variables. Don't import shell + functions from the environment if we are running in privileged or + restricted mode or if the shell is running setuid. */ +#if defined (RESTRICTED_SHELL) + initialize_shell_variables (shell_environment, privileged_mode||restricted||should_be_restricted||running_setuid); +#else + initialize_shell_variables (shell_environment, privileged_mode||running_setuid); +#endif + + /* Initialize the data structures for storing and running jobs. */ + initialize_job_control (jobs_m_flag); + + /* Initialize input streams to null. */ + initialize_bash_input (); + + initialize_flags (); + + /* Initialize the shell options. Don't import the shell options + from the environment variables $SHELLOPTS or $BASHOPTS if we are + running in privileged or restricted mode or if the shell is running + setuid. */ +#if defined (RESTRICTED_SHELL) + initialize_shell_options (privileged_mode||restricted||should_be_restricted||running_setuid); + initialize_bashopts (privileged_mode||restricted||should_be_restricted||running_setuid); +#else + initialize_shell_options (privileged_mode||running_setuid); + initialize_bashopts (privileged_mode||running_setuid); +#endif +} + +/* Function called by main () when it appears that the shell has already + had some initialization performed. This is supposed to reset the world + back to a pristine state, as if we had been exec'ed. */ +static void +shell_reinitialize () +{ + /* The default shell prompts. */ + primary_prompt = PPROMPT; + secondary_prompt = SPROMPT; + + /* Things that get 1. */ + current_command_number = 1; + + /* We have decided that the ~/.bashrc file should not be executed + for the invocation of each shell script. If the variable $ENV + (or $BASH_ENV) is set, its value is used as the name of a file + to source. */ + no_rc = no_profile = 1; + + /* Things that get 0. */ + login_shell = make_login_shell = interactive = executing = 0; + debugging = do_version = line_number = last_command_exit_value = 0; + forced_interactive = interactive_shell = 0; + subshell_environment = running_in_background = 0; + expand_aliases = 0; + bash_argv_initialized = 0; + + /* XXX - should we set jobs_m_flag to 0 here? */ + +#if defined (HISTORY) + bash_history_reinit (enable_history_list = 0); +#endif /* HISTORY */ + +#if defined (RESTRICTED_SHELL) + restricted = 0; +#endif /* RESTRICTED_SHELL */ + + /* Ensure that the default startup file is used. (Except that we don't + execute this file for reinitialized shells). */ + bashrc_file = DEFAULT_BASHRC; + + /* Delete all variables and functions. They will be reinitialized when + the environment is parsed. */ + delete_all_contexts (shell_variables); + delete_all_variables (shell_functions); + + reinit_special_variables (); + +#if defined (READLINE) + bashline_reinitialize (); +#endif + + shell_reinitialized = 1; +} + +static void +show_shell_usage (fp, extra) + FILE *fp; + int extra; +{ + int i; + char *set_opts, *s, *t; + + if (extra) + fprintf (fp, _("GNU bash, version %s-(%s)\n"), shell_version_string (), MACHTYPE); + fprintf (fp, _("Usage:\t%s [GNU long option] [option] ...\n\t%s [GNU long option] [option] script-file ...\n"), + shell_name, shell_name); + fputs (_("GNU long options:\n"), fp); + for (i = 0; long_args[i].name; i++) + fprintf (fp, "\t--%s\n", long_args[i].name); + + fputs (_("Shell options:\n"), fp); + fputs (_("\t-ilrsD or -c command or -O shopt_option\t\t(invocation only)\n"), fp); + + for (i = 0, set_opts = 0; shell_builtins[i].name; i++) + if (STREQ (shell_builtins[i].name, "set")) + { + set_opts = savestring (shell_builtins[i].short_doc); + break; + } + + if (set_opts) + { + s = strchr (set_opts, '['); + if (s == 0) + s = set_opts; + while (*++s == '-') + ; + t = strchr (s, ']'); + if (t) + *t = '\0'; + fprintf (fp, _("\t-%s or -o option\n"), s); + free (set_opts); + } + + if (extra) + { + fprintf (fp, _("Type `%s -c \"help set\"' for more information about shell options.\n"), shell_name); + fprintf (fp, _("Type `%s -c help' for more information about shell builtin commands.\n"), shell_name); + fprintf (fp, _("Use the `bashbug' command to report bugs.\n")); + fprintf (fp, "\n"); + fprintf (fp, _("bash home page: \n")); + fprintf (fp, _("General help using GNU software: \n")); + } +} + +static void +add_shopt_to_alist (opt, on_or_off) + char *opt; + int on_or_off; +{ + if (shopt_ind >= shopt_len) + { + shopt_len += 8; + shopt_alist = (STRING_INT_ALIST *)xrealloc (shopt_alist, shopt_len * sizeof (shopt_alist[0])); + } + shopt_alist[shopt_ind].word = opt; + shopt_alist[shopt_ind].token = on_or_off; + shopt_ind++; +} + +static void +run_shopt_alist () +{ + register int i; + + for (i = 0; i < shopt_ind; i++) + if (shopt_setopt (shopt_alist[i].word, (shopt_alist[i].token == '-')) != EXECUTION_SUCCESS) + exit (EX_BADUSAGE); + free (shopt_alist); + shopt_alist = 0; + shopt_ind = shopt_len = 0; +} diff --git a/third_party/bash/shell.h b/third_party/bash/shell.h new file mode 100644 index 000000000..6e44bca69 --- /dev/null +++ b/third_party/bash/shell.h @@ -0,0 +1,240 @@ +/* shell.h -- The data structures used by the shell */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "bashjmp.h" + +#include "command.h" +#include "syntax.h" +#include "general.h" +#include "error.h" +#include "variables.h" +#include "arrayfunc.h" +#include "quit.h" +#include "maxpath.h" +#include "unwind_prot.h" +#include "dispose_cmd.h" +#include "make_cmd.h" +#include "ocache.h" +#include "subst.h" +#include "sig.h" +#include "pathnames.h" +#include "externs.h" + +extern int EOF_Reached; + +#define NO_PIPE -1 +#define REDIRECT_BOTH -2 + +#define NO_VARIABLE -1 + +/* Values that can be returned by execute_command (). */ +#define EXECUTION_FAILURE 1 +#define EXECUTION_SUCCESS 0 + +/* Usage messages by builtins result in a return status of 2. */ +#define EX_BADUSAGE 2 + +#define EX_MISCERROR 2 + +/* Special exit statuses used by the shell, internally and externally. */ +#define EX_RETRYFAIL 124 +#define EX_WEXPCOMSUB 125 +#define EX_BINARY_FILE 126 +#define EX_NOEXEC 126 +#define EX_NOINPUT 126 +#define EX_NOTFOUND 127 + +#define EX_SHERRBASE 256 /* all special error values are > this. */ + +#define EX_BADSYNTAX 257 /* shell syntax error */ +#define EX_USAGE 258 /* syntax error in usage */ +#define EX_REDIRFAIL 259 /* redirection failed */ +#define EX_BADASSIGN 260 /* variable assignment error */ +#define EX_EXPFAIL 261 /* word expansion failed */ +#define EX_DISKFALLBACK 262 /* fall back to disk command from builtin */ + +/* Flag values that control parameter pattern substitution. */ +#define MATCH_ANY 0x000 +#define MATCH_BEG 0x001 +#define MATCH_END 0x002 + +#define MATCH_TYPEMASK 0x003 + +#define MATCH_GLOBREP 0x010 +#define MATCH_QUOTED 0x020 +#define MATCH_ASSIGNRHS 0x040 +#define MATCH_STARSUB 0x080 +#define MATCH_EXPREP 0x100 /* for pattern substitution, expand replacement */ + +/* Some needed external declarations. */ +extern char **shell_environment; +extern WORD_LIST *rest_of_args; + +/* Generalized global variables. */ +extern char *command_execution_string; + +extern int debugging_mode; +extern int executing, login_shell; +extern int interactive, interactive_shell; +extern int startup_state; +extern int reading_shell_script; +extern int shell_initialized; +extern int bash_argv_initialized; +extern int subshell_environment; +extern int current_command_number; +extern int indirection_level; +extern int shell_compatibility_level; +extern int running_under_emacs; + +extern int posixly_correct; +extern int no_line_editing; + +extern char *shell_name; +extern char *current_host_name; + +extern int subshell_argc; +extern char **subshell_argv; +extern char **subshell_envp; + +/* variables managed using shopt */ +extern int hup_on_exit; +extern int check_jobs_at_exit; +extern int autocd; +extern int check_window_size; + +/* from version.c */ +extern int build_version, patch_level; +extern char *dist_version, *release_status; + +extern int locale_mb_cur_max; +extern int locale_utf8locale; + +/* Structure to pass around that holds a bitmap of file descriptors + to close, and the size of that structure. Used in execute_cmd.c. */ +struct fd_bitmap { + int size; + char *bitmap; +}; + +#define FD_BITMAP_SIZE 32 + +#define CTLESC '\001' +#define CTLNUL '\177' + +/* Information about the current user. */ +struct user_info { + uid_t uid, euid; + gid_t gid, egid; + char *user_name; + char *shell; /* shell from the password file */ + char *home_dir; +}; + +extern struct user_info current_user; + +/* Force gcc to not clobber X on a longjmp(). Old versions of gcc mangle + this badly. */ +#if (__GNUC__ > 2) || (__GNUC__ == 2 && __GNUC_MINOR__ > 8) +# define USE_VAR(x) ((void) &(x)) +#else +# define USE_VAR(x) +#endif + +#define HEREDOC_MAX 16 + +/* Structure in which to save partial parsing state when doing things like + PROMPT_COMMAND and bash_execute_unix_command execution. */ + +typedef struct _sh_parser_state_t +{ + /* parsing state */ + int parser_state; + int *token_state; + + char *token; + size_t token_buffer_size; + int eof_token; + + /* input line state -- line number saved elsewhere */ + int input_line_terminator; + int eof_encountered; + int eol_lookahead; + +#if defined (HANDLE_MULTIBYTE) + /* Nothing right now for multibyte state, but might want something later. */ +#endif + + char **prompt_string_pointer; + + /* history state affecting or modified by the parser */ + int current_command_line_count; +#if defined (HISTORY) + int remember_on_history; + int history_expansion_inhibited; +#endif + + /* execution state possibly modified by the parser */ + int last_command_exit_value; +#if defined (ARRAY_VARS) + ARRAY *pipestatus; +#endif + sh_builtin_func_t *last_shell_builtin, *this_shell_builtin; + + /* flags state affecting the parser */ + int expand_aliases; + int echo_input_at_read; + int need_here_doc; + int here_doc_first_line; + + int esacs_needed; + int expecting_in; + + /* structures affecting the parser */ + void *pushed_strings; + REDIRECT *redir_stack[HEREDOC_MAX]; +} sh_parser_state_t; + +typedef struct _sh_input_line_state_t +{ + char *input_line; + size_t input_line_index; + size_t input_line_size; + size_t input_line_len; +#if defined (HANDLE_MULTIBYTE) + char *input_property; + size_t input_propsize; +#endif +} sh_input_line_state_t; + +/* Let's try declaring these here. */ +extern void shell_ungets PARAMS((char *)); +extern void rewind_input_string PARAMS((void)); + +extern char *parser_remaining_input PARAMS((void)); + +extern sh_parser_state_t *save_parser_state PARAMS((sh_parser_state_t *)); +extern void restore_parser_state PARAMS((sh_parser_state_t *)); + +extern sh_input_line_state_t *save_input_line_state PARAMS((sh_input_line_state_t *)); +extern void restore_input_line_state PARAMS((sh_input_line_state_t *)); diff --git a/third_party/bash/shmatch.c b/third_party/bash/shmatch.c new file mode 100644 index 000000000..dd5a2f802 --- /dev/null +++ b/third_party/bash/shmatch.c @@ -0,0 +1,132 @@ +/* + * shmatch.c -- shell interface to posix regular expression matching. + */ + +/* Copyright (C) 2003-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if defined (HAVE_POSIX_REGEXP) + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include "bashansi.h" + +#include +#include + +#include "shell.h" +#include "variables.h" +#include "externs.h" + +extern int glob_ignore_case, match_ignore_case; + +#if defined (ARRAY_VARS) +extern SHELL_VAR *builtin_find_indexed_array (char *, int); +#endif + +int +sh_regmatch (string, pattern, flags) + const char *string; + const char *pattern; + int flags; +{ + regex_t regex = { 0 }; + regmatch_t *matches; + int rflags; +#if defined (ARRAY_VARS) + SHELL_VAR *rematch; + ARRAY *amatch; + int subexp_ind; + char *subexp_str; + int subexp_len; +#endif + int result; + +#if defined (ARRAY_VARS) + rematch = (SHELL_VAR *)NULL; +#endif + + rflags = REG_EXTENDED; + if (match_ignore_case) + rflags |= REG_ICASE; +#if !defined (ARRAY_VARS) + rflags |= REG_NOSUB; +#endif + + if (regcomp (®ex, pattern, rflags)) + return 2; /* flag for printing a warning here. */ + +#if defined (ARRAY_VARS) + matches = (regmatch_t *)malloc (sizeof (regmatch_t) * (regex.re_nsub + 1)); +#else + matches = NULL; +#endif + + /* man regexec: NULL PMATCH ignored if NMATCH == 0 */ + if (regexec (®ex, string, matches ? regex.re_nsub + 1 : 0, matches, 0)) + result = EXECUTION_FAILURE; + else + result = EXECUTION_SUCCESS; /* match */ + +#if defined (ARRAY_VARS) + subexp_len = strlen (string) + 10; + subexp_str = malloc (subexp_len + 1); + + /* Store the parenthesized subexpressions in the array BASH_REMATCH. + Element 0 is the portion that matched the entire regexp. Element 1 + is the part that matched the first subexpression, and so on. */ +#if 1 + unbind_global_variable_noref ("BASH_REMATCH"); + rematch = make_new_array_variable ("BASH_REMATCH"); +#else + /* TAG:bash-5.3 */ + rematch = builtin_find_indexed_array ("BASH_REMATCH", 1); +#endif + amatch = rematch ? array_cell (rematch) : (ARRAY *)0; + + if (matches && amatch && (flags & SHMAT_SUBEXP) && result == EXECUTION_SUCCESS && subexp_str) + { + for (subexp_ind = 0; subexp_ind <= regex.re_nsub; subexp_ind++) + { + memset (subexp_str, 0, subexp_len); + strncpy (subexp_str, string + matches[subexp_ind].rm_so, + matches[subexp_ind].rm_eo - matches[subexp_ind].rm_so); + array_insert (amatch, subexp_ind, subexp_str); + } + } + +#if 0 + VSETATTR (rematch, att_readonly); +#endif + + free (subexp_str); + free (matches); +#endif /* ARRAY_VARS */ + + regfree (®ex); + + return result; +} + +#endif /* HAVE_POSIX_REGEXP */ diff --git a/third_party/bash/shmbchar.c b/third_party/bash/shmbchar.c new file mode 100644 index 000000000..827ee6b39 --- /dev/null +++ b/third_party/bash/shmbchar.c @@ -0,0 +1,137 @@ +/* Copyright (C) 2001, 2006, 2009, 2010, 2012, 2015-2018 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + + +#include "config.h" + +#if defined (HANDLE_MULTIBYTE) +#include +#include + +#include + +#include "shmbutil.h" +#include "shmbchar.h" + +#ifndef errno +extern int errno; +#endif + +#if IS_BASIC_ASCII + +/* Bit table of characters in the ISO C "basic character set". */ +const unsigned int is_basic_table [UCHAR_MAX / 32 + 1] = +{ + 0x00001a00, /* '\t' '\v' '\f' */ + 0xffffffef, /* ' '...'#' '%'...'?' */ + 0xfffffffe, /* 'A'...'Z' '[' '\\' ']' '^' '_' */ + 0x7ffffffe /* 'a'...'z' '{' '|' '}' '~' */ + /* The remaining bits are 0. */ +}; + +#endif /* IS_BASIC_ASCII */ + +extern int locale_utf8locale; + +extern char *utf8_mbsmbchar (const char *); +extern int utf8_mblen (const char *, size_t); + +/* Count the number of characters in S, counting multi-byte characters as a + single character. */ +size_t +mbstrlen (s) + const char *s; +{ + size_t clen, nc; + mbstate_t mbs = { 0 }, mbsbak = { 0 }; + int f, mb_cur_max; + + nc = 0; + mb_cur_max = MB_CUR_MAX; + while (*s && (clen = (f = is_basic (*s)) ? 1 : mbrlen(s, mb_cur_max, &mbs)) != 0) + { + if (MB_INVALIDCH(clen)) + { + clen = 1; /* assume single byte */ + mbs = mbsbak; + } + + if (f == 0) + mbsbak = mbs; + + s += clen; + nc++; + } + return nc; +} + +/* Return pointer to first multibyte char in S, or NULL if none. */ +/* XXX - if we know that the locale is UTF-8, we can just check whether or + not any byte has the eighth bit turned on */ +char * +mbsmbchar (s) + const char *s; +{ + char *t; + size_t clen; + mbstate_t mbs = { 0 }; + int mb_cur_max; + + if (locale_utf8locale) + return (utf8_mbsmbchar (s)); /* XXX */ + + mb_cur_max = MB_CUR_MAX; + for (t = (char *)s; *t; t++) + { + if (is_basic (*t)) + continue; + + if (locale_utf8locale) /* not used if above code active */ + clen = utf8_mblen (t, mb_cur_max); + else + clen = mbrlen (t, mb_cur_max, &mbs); + + if (clen == 0) + return 0; + if (MB_INVALIDCH(clen)) + continue; + + if (clen > 1) + return t; + } + return 0; +} + +int +sh_mbsnlen(src, srclen, maxlen) + const char *src; + size_t srclen; + int maxlen; +{ + int count; + int sind; + DECLARE_MBSTATE; + + for (sind = count = 0; src[sind]; ) + { + count++; /* number of multibyte characters */ + ADVANCE_CHAR (src, srclen, sind); + if (sind > maxlen) + break; + } + + return count; +} +#endif diff --git a/third_party/bash/shmbchar.h b/third_party/bash/shmbchar.h new file mode 100644 index 000000000..27b0024fb --- /dev/null +++ b/third_party/bash/shmbchar.h @@ -0,0 +1,112 @@ +/* Multibyte character data type. + Copyright (C) 2001, 2005-2007, 2009-2010, 2021 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Bruno Haible . */ + +#ifndef _SHMBCHAR_H +#define _SHMBCHAR_H 1 + +#if defined (HANDLE_MULTIBYTE) + +#include + +/* Tru64 with Desktop Toolkit C has a bug: must be included before + . + BSD/OS 4.1 has a bug: and must be included before + . */ +#include +#include +#include +#include + + +/* is_basic(c) tests whether the single-byte character c is in the + ISO C "basic character set". */ + +#if (' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \ + && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \ + && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \ + && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \ + && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \ + && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \ + && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \ + && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \ + && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \ + && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \ + && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \ + && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \ + && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \ + && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \ + && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \ + && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \ + && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \ + && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \ + && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \ + && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \ + && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \ + && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \ + && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126) +/* The character set is ISO-646, not EBCDIC. */ +# define IS_BASIC_ASCII 1 + +extern const unsigned int is_basic_table[]; + +static inline int +is_basic (char c) +{ + return (is_basic_table [(unsigned char) c >> 5] >> ((unsigned char) c & 31)) + & 1; +} + +#else + +static inline int +is_basic (char c) +{ + switch (c) + { + case '\b': case '\r': case '\n': + case '\t': case '\v': case '\f': + case ' ': case '!': case '"': case '#': case '%': + case '&': case '\'': case '(': case ')': case '*': + case '+': case ',': case '-': case '.': case '/': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case ':': case ';': case '<': case '=': case '>': + case '?': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': + case 'Z': + case '[': case '\\': case ']': case '^': case '_': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': + case 'z': case '{': case '|': case '}': case '~': + return 1; + default: + return 0; + } +} + +#endif + +#endif /* HANDLE_MULTIBYTE */ +#endif /* _SHMBCHAR_H */ diff --git a/third_party/bash/shmbutil.h b/third_party/bash/shmbutil.h new file mode 100644 index 000000000..68c457a2f --- /dev/null +++ b/third_party/bash/shmbutil.h @@ -0,0 +1,559 @@ +/* shmbutil.h -- utility functions for multibyte characters. */ + +/* Copyright (C) 2002-2019 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_SH_MBUTIL_H_) +#define _SH_MBUTIL_H_ + +#include "stdc.h" + +/* Include config.h for HANDLE_MULTIBYTE */ +#include "config.h" + +#if defined (HANDLE_MULTIBYTE) +#include "shmbchar.h" + +extern size_t xwcsrtombs PARAMS((char *, const wchar_t **, size_t, mbstate_t *)); +extern size_t xmbsrtowcs PARAMS((wchar_t *, const char **, size_t, mbstate_t *)); +extern size_t xdupmbstowcs PARAMS((wchar_t **, char ***, const char *)); + +extern size_t mbstrlen PARAMS((const char *)); + +extern char *xstrchr PARAMS((const char *, int)); + +extern int locale_mb_cur_max; /* XXX */ +extern int locale_utf8locale; /* XXX */ + +#ifndef MB_INVALIDCH +#define MB_INVALIDCH(x) ((x) == (size_t)-1 || (x) == (size_t)-2) +#define MB_NULLWCH(x) ((x) == 0) +#endif + +#define MBSLEN(s) (((s) && (s)[0]) ? ((s)[1] ? mbstrlen (s) : 1) : 0) +#define MB_STRLEN(s) ((MB_CUR_MAX > 1) ? MBSLEN (s) : STRLEN (s)) + +#define MBLEN(s, n) ((MB_CUR_MAX > 1) ? mblen ((s), (n)) : 1) +#define MBRLEN(s, n, p) ((MB_CUR_MAX > 1) ? mbrlen ((s), (n), (p)) : 1) + +#define UTF8_SINGLEBYTE(c) (((c) & 0x80) == 0) +#define UTF8_MBFIRSTCHAR(c) (((c) & 0xc0) == 0xc0) +#define UTF8_MBCHAR(c) (((c) & 0xc0) == 0x80) + +#else /* !HANDLE_MULTIBYTE */ + +#undef MB_LEN_MAX +#undef MB_CUR_MAX + +#define MB_LEN_MAX 1 +#define MB_CUR_MAX 1 + +#undef xstrchr +#define xstrchr(s, c) strchr(s, c) + +#ifndef MB_INVALIDCH +#define MB_INVALIDCH(x) (0) +#define MB_NULLWCH(x) (0) +#endif + +#define MB_STRLEN(s) (STRLEN(s)) + +#define MBLEN(s, n) 1 +#define MBRLEN(s, n, p) 1 + +#ifndef wchar_t +# define wchar_t int +#endif + +#define UTF8_SINGLEBYTE(c) (1) +#define UTF8_MBFIRSTCHAR(c) (0) + +#endif /* !HANDLE_MULTIBYTE */ + +/* Declare and initialize a multibyte state. Call must be terminated + with `;'. */ +#if defined (HANDLE_MULTIBYTE) +# define DECLARE_MBSTATE \ + mbstate_t state; \ + memset (&state, '\0', sizeof (mbstate_t)) +#else +# define DECLARE_MBSTATE +#endif /* !HANDLE_MULTIBYTE */ + +/* Initialize or reinitialize a multibyte state named `state'. Call must be + terminated with `;'. */ +#if defined (HANDLE_MULTIBYTE) +# define INITIALIZE_MBSTATE memset (&state, '\0', sizeof (mbstate_t)) +#else +# define INITIALIZE_MBSTATE +#endif /* !HANDLE_MULTIBYTE */ + +/* Advance one (possibly multi-byte) character in string _STR of length + _STRSIZE, starting at index _I. STATE must have already been declared. */ +#if defined (HANDLE_MULTIBYTE) +# define ADVANCE_CHAR(_str, _strsize, _i) \ + do \ + { \ + if (locale_mb_cur_max > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _f; \ +\ + _f = is_basic ((_str)[_i]); \ + if (_f) \ + mblength = 1; \ + else if (locale_utf8locale && (((_str)[_i] & 0x80) == 0)) \ + mblength = (_str)[_i] != 0; \ + else \ + { \ + state_bak = state; \ + mblength = mbrlen ((_str) + (_i), (_strsize) - (_i), &state); \ + } \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + (_i)++; \ + } \ + else if (mblength == 0) \ + (_i)++; \ + else \ + (_i) += mblength; \ + } \ + else \ + (_i)++; \ + } \ + while (0) +#else +# define ADVANCE_CHAR(_str, _strsize, _i) (_i)++ +#endif /* !HANDLE_MULTIBYTE */ + +/* Advance one (possibly multibyte) character in the string _STR of length + _STRSIZE. + SPECIAL: assume that _STR will be incremented by 1 after this call. */ +#if defined (HANDLE_MULTIBYTE) +# define ADVANCE_CHAR_P(_str, _strsize) \ + do \ + { \ + if (locale_mb_cur_max > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _f; \ +\ + _f = is_basic (*(_str)); \ + if (_f) \ + mblength = 1; \ + else if (locale_utf8locale && ((*(_str) & 0x80) == 0)) \ + mblength = *(_str) != 0; \ + else \ + { \ + state_bak = state; \ + mblength = mbrlen ((_str), (_strsize), &state); \ + } \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + (_str) += (mblength < 1) ? 0 : (mblength - 1); \ + } \ + } \ + while (0) +#else +# define ADVANCE_CHAR_P(_str, _strsize) +#endif /* !HANDLE_MULTIBYTE */ + +/* Back up one (possibly multi-byte) character in string _STR of length + _STRSIZE, starting at index _I. STATE must have already been declared. */ +#if defined (HANDLE_MULTIBYTE) +# define BACKUP_CHAR(_str, _strsize, _i) \ + do \ + { \ + if (locale_mb_cur_max > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _x, _p; /* _x == temp index into string, _p == prev index */ \ +\ + _x = _p = 0; \ + while (_x < (_i)) \ + { \ + state_bak = state; \ + mblength = mbrlen ((_str) + (_x), (_strsize) - (_x), &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + _x++; \ + } \ + else if (mblength == 0) \ + _x++; \ + else \ + { \ + _p = _x; /* _p == start of prev mbchar */ \ + _x += mblength; \ + } \ + } \ + (_i) = _p; \ + } \ + else \ + (_i)--; \ + } \ + while (0) +#else +# define BACKUP_CHAR(_str, _strsize, _i) (_i)-- +#endif /* !HANDLE_MULTIBYTE */ + +/* Back up one (possibly multibyte) character in the string _BASE of length + _STRSIZE starting at _STR (_BASE <= _STR <= (_BASE + _STRSIZE) ). + SPECIAL: DO NOT assume that _STR will be decremented by 1 after this call. */ +#if defined (HANDLE_MULTIBYTE) +# define BACKUP_CHAR_P(_base, _strsize, _str) \ + do \ + { \ + if (locale_mb_cur_max > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + char *_x, _p; /* _x == temp pointer into string, _p == prev pointer */ \ +\ + _x = _p = _base; \ + while (_x < (_str)) \ + { \ + state_bak = state; \ + mblength = mbrlen (_x, (_strsize) - _x, &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + _x++; \ + } \ + else if (mblength == 0) \ + _x++; \ + else \ + { \ + _p = _x; /* _p == start of prev mbchar */ \ + _x += mblength; \ + } \ + } \ + (_str) = _p; \ + } \ + else \ + (_str)--; \ + } \ + while (0) +#else +# define BACKUP_CHAR_P(_base, _strsize, _str) (_str)-- +#endif /* !HANDLE_MULTIBYTE */ + +/* Copy a single character from the string _SRC to the string _DST. + _SRCEND is a pointer to the end of _SRC. */ +#if defined (HANDLE_MULTIBYTE) +# define COPY_CHAR_P(_dst, _src, _srcend) \ + do \ + { \ + if (locale_mb_cur_max > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _k; \ +\ + _k = is_basic (*(_src)); \ + if (_k) \ + mblength = 1; \ + else if (locale_utf8locale && ((*(_src) & 0x80) == 0)) \ + mblength = *(_src) != 0; \ + else \ + { \ + state_bak = state; \ + mblength = mbrlen ((_src), (_srcend) - (_src), &state); \ + } \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + for (_k = 0; _k < mblength; _k++) \ + *(_dst)++ = *(_src)++; \ + } \ + else \ + *(_dst)++ = *(_src)++; \ + } \ + while (0) +#else +# define COPY_CHAR_P(_dst, _src, _srcend) *(_dst)++ = *(_src)++ +#endif /* !HANDLE_MULTIBYTE */ + +/* Copy a single character from the string _SRC at index _SI to the string + _DST at index _DI. _SRCEND is a pointer to the end of _SRC. */ +#if defined (HANDLE_MULTIBYTE) +# define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) \ + do \ + { \ + if (locale_mb_cur_max > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _k; \ +\ + _k = is_basic ((_src)[(_si)]); \ + if (_k) \ + mblength = 1; \ + else if (locale_utf8locale && ((_src)[(_si)] & 0x80) == 0) \ + mblength = (_src)[(_si)] != 0; \ + else \ + {\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src)+(_si)), &state); \ + } \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + for (_k = 0; _k < mblength; _k++) \ + _dst[_di++] = _src[_si++]; \ + } \ + else \ + _dst[_di++] = _src[_si++]; \ + } \ + while (0) +#else +# define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) _dst[_di++] = _src[_si++] +#endif /* !HANDLE_MULTIBYTE */ + +/**************************************************************** + * * + * The following are only guaranteed to work in subst.c * + * * + ****************************************************************/ + +#if defined (HANDLE_MULTIBYTE) +# define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \ + do \ + { \ + if (locale_mb_cur_max > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _i; \ +\ + _i = is_basic ((_src)[(_si)]); \ + if (_i) \ + mblength = 1; \ + else if (locale_utf8locale && ((_src)[(_si)] & 0x80) == 0) \ + mblength = (_src)[(_si)] != 0; \ + else \ + { \ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_slen) - (_si), &state); \ + } \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + temp = xmalloc (mblength + 2); \ + temp[0] = _escchar; \ + for (_i = 0; _i < mblength; _i++) \ + temp[_i + 1] = _src[_si++]; \ + temp[mblength + 1] = '\0'; \ +\ + goto add_string; \ + } \ + else \ + { \ + _dst[0] = _escchar; \ + _dst[1] = _sc; \ + } \ + } \ + while (0) +#else +# define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \ + _dst[0] = _escchar; \ + _dst[1] = _sc +#endif /* !HANDLE_MULTIBYTE */ + +#if defined (HANDLE_MULTIBYTE) +# define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \ + do \ + { \ + if (locale_mb_cur_max > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _i; \ +\ + _i = is_basic (*((_src) + (_si))); \ + if (_i) \ + mblength = 1; \ + else if (locale_utf8locale && (((_src)[_si] & 0x80) == 0)) \ + mblength = (_src)[_si] != 0; \ + else \ + { \ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src) + (_si)), &state); \ + } \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + FASTCOPY(((_src) + (_si)), (_dst), mblength); \ +\ + (_dst) += mblength; \ + (_si) += mblength; \ + } \ + else \ + { \ + *(_dst)++ = _src[(_si)]; \ + (_si)++; \ + } \ + } \ + while (0) +#else +# define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \ + *(_dst)++ = _src[(_si)]; \ + (_si)++ +#endif /* !HANDLE_MULTIBYTE */ + +#if HANDLE_MULTIBYTE +# define SADD_MBCHAR(_dst, _src, _si, _srcsize) \ + do \ + { \ + if (locale_mb_cur_max > 1) \ + { \ + int i; \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + i = is_basic (*((_src) + (_si))); \ + if (i) \ + mblength = 1; \ + else if (locale_utf8locale && (((_src)[_si] & 0x80) == 0)) \ + mblength = (_src)[_si] != 0; \ + else \ + { \ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \ + } \ + if (mblength == (size_t)-1 || mblength == (size_t)-2) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + if (mblength < 1) \ + mblength = 1; \ +\ + _dst = (char *)xmalloc (mblength + 1); \ + for (i = 0; i < mblength; i++) \ + (_dst)[i] = (_src)[(_si)++]; \ + (_dst)[mblength] = '\0'; \ +\ + goto add_string; \ + } \ + } \ + while (0) + +#else +# define SADD_MBCHAR(_dst, _src, _si, _srcsize) +#endif + +/* Watch out when using this -- it's just straight textual substitution */ +#if defined (HANDLE_MULTIBYTE) +# define SADD_MBQCHAR_BODY(_dst, _src, _si, _srcsize) \ +\ + int i; \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + i = is_basic (*((_src) + (_si))); \ + if (i) \ + mblength = 1; \ + else if (locale_utf8locale && (((_src)[_si] & 0x80) == 0)) \ + mblength = (_src)[_si] != 0; \ + else \ + { \ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \ + } \ + if (mblength == (size_t)-1 || mblength == (size_t)-2) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + if (mblength < 1) \ + mblength = 1; \ +\ + (_dst) = (char *)xmalloc (mblength + 2); \ + (_dst)[0] = CTLESC; \ + for (i = 0; i < mblength; i++) \ + (_dst)[i+1] = (_src)[(_si)++]; \ + (_dst)[mblength+1] = '\0'; \ +\ + goto add_string + +# define SADD_MBCHAR_BODY(_dst, _src, _si, _srcsize) \ +\ + int i; \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + i = is_basic (*((_src) + (_si))); \ + if (i) \ + mblength = 1; \ + else if (locale_utf8locale && (((_src)[_si] & 0x80) == 0)) \ + mblength = (_src)[_si] != 0; \ + else \ + { \ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \ + } \ + if (mblength == (size_t)-1 || mblength == (size_t)-2) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + if (mblength < 1) \ + mblength = 1; \ +\ + (_dst) = (char *)xmalloc (mblength + 1); \ + for (i = 0; i < mblength; i++) \ + (_dst)[i+1] = (_src)[(_si)++]; \ + (_dst)[mblength+1] = '\0'; \ +\ + goto add_string + +#endif /* HANDLE_MULTIBYTE */ +#endif /* _SH_MBUTIL_H_ */ diff --git a/third_party/bash/shquote.c b/third_party/bash/shquote.c new file mode 100644 index 000000000..59436c65f --- /dev/null +++ b/third_party/bash/shquote.c @@ -0,0 +1,432 @@ +/* shquote - functions to quote and dequote strings */ + +/* Copyright (C) 1999-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include "stdc.h" + +#include "syntax.h" +#include "xmalloc.h" + +#include "shmbchar.h" +#include "shmbutil.h" + +extern char *ansic_quote PARAMS((char *, int, int *)); +extern int ansic_shouldquote PARAMS((const char *)); + +/* Default set of characters that should be backslash-quoted in strings */ +static const char bstab[256] = + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 0, 0, 0, 0, 0, /* TAB, NL */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 1, 1, 1, 0, 1, 0, 1, 1, /* SPACE, !, DQUOTE, DOL, AMP, SQUOTE */ + 1, 1, 1, 0, 1, 0, 0, 0, /* LPAR, RPAR, STAR, COMMA */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 0, 1, 1, /* SEMI, LESSTHAN, GREATERTHAN, QUEST */ + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 1, 0, /* LBRACK, BS, RBRACK, CARAT */ + + 1, 0, 0, 0, 0, 0, 0, 0, /* BACKQ */ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 0, 0, /* LBRACE, BAR, RBRACE */ + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + +/* **************************************************************** */ +/* */ +/* Functions for quoting strings to be re-read as input */ +/* */ +/* **************************************************************** */ + +/* Return a new string which is the single-quoted version of STRING. + Used by alias and trap, among others. */ +char * +sh_single_quote (string) + const char *string; +{ + register int c; + char *result, *r; + const char *s; + + result = (char *)xmalloc (3 + (4 * strlen (string))); + r = result; + + if (string[0] == '\'' && string[1] == 0) + { + *r++ = '\\'; + *r++ = '\''; + *r++ = 0; + return result; + } + + *r++ = '\''; + + for (s = string; s && (c = *s); s++) + { + *r++ = c; + + if (c == '\'') + { + *r++ = '\\'; /* insert escaped single quote */ + *r++ = '\''; + *r++ = '\''; /* start new quoted string */ + } + } + + *r++ = '\''; + *r = '\0'; + + return (result); +} + +/* Quote STRING using double quotes. Return a new string. */ +char * +sh_double_quote (string) + const char *string; +{ + register unsigned char c; + int mb_cur_max; + char *result, *r; + size_t slen; + const char *s, *send; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + mb_cur_max = MB_CUR_MAX; + + result = (char *)xmalloc (3 + (2 * strlen (string))); + r = result; + *r++ = '"'; + + for (s = string; s && (c = *s); s++) + { + /* Backslash-newline disappears within double quotes, so don't add one. */ + if ((sh_syntaxtab[c] & CBSDQUOTE) && c != '\n') + *r++ = '\\'; + +#if defined (HANDLE_MULTIBYTE) + if ((locale_utf8locale && (c & 0x80)) || + (locale_utf8locale == 0 && mb_cur_max > 1 && is_basic (c) == 0)) + { + COPY_CHAR_P (r, s, send); + s--; /* compensate for auto-increment in loop above */ + continue; + } +#endif + + /* Assume that the string will not be further expanded, so no need to + add CTLESC to protect CTLESC or CTLNUL. */ + *r++ = c; + } + + *r++ = '"'; + *r = '\0'; + + return (result); +} + +/* Turn S into a simple double-quoted string. If FLAGS is non-zero, quote + double quote characters in S with backslashes. */ +char * +sh_mkdoublequoted (s, slen, flags) + const char *s; + int slen, flags; +{ + char *r, *ret; + const char *send; + int rlen, mb_cur_max; + DECLARE_MBSTATE; + + send = s + slen; + mb_cur_max = flags ? MB_CUR_MAX : 1; + rlen = (flags == 0) ? slen + 3 : (2 * slen) + 1; + ret = r = (char *)xmalloc (rlen); + + *r++ = '"'; + while (*s) + { + if (flags && *s == '"') + *r++ = '\\'; + +#if defined (HANDLE_MULTIBYTE) + if (flags && ((locale_utf8locale && (*s & 0x80)) || + (locale_utf8locale == 0 && mb_cur_max > 1 && is_basic (*s) == 0))) + { + COPY_CHAR_P (r, s, send); + continue; + } +#endif + *r++ = *s++; + } + *r++ = '"'; + *r = '\0'; + + return ret; +} + +/* Remove backslashes that are quoting characters that are special between + double quotes. Return a new string. XXX - should this handle CTLESC + and CTLNUL? */ +char * +sh_un_double_quote (string) + char *string; +{ + register int c, pass_next; + char *result, *r, *s; + + r = result = (char *)xmalloc (strlen (string) + 1); + + for (pass_next = 0, s = string; s && (c = *s); s++) + { + if (pass_next) + { + *r++ = c; + pass_next = 0; + continue; + } + if (c == '\\' && (sh_syntaxtab[(unsigned char) s[1]] & CBSDQUOTE)) + { + pass_next = 1; + continue; + } + *r++ = c; + } + + *r = '\0'; + return result; +} + +/* Quote special characters in STRING using backslashes. Return a new + string. NOTE: if the string is to be further expanded, we need a + way to protect the CTLESC and CTLNUL characters. As I write this, + the current callers will never cause the string to be expanded without + going through the shell parser, which will protect the internal + quoting characters. TABLE, if set, points to a map of the ascii code + set with char needing to be backslash-quoted if table[char]==1. FLAGS, + if 1, causes tildes to be quoted as well. If FLAGS&2, backslash-quote + other shell blank characters. */ + +char * +sh_backslash_quote (string, table, flags) + char *string; + char *table; + int flags; +{ + int c, mb_cur_max; + size_t slen; + char *result, *r, *s, *backslash_table, *send; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + result = (char *)xmalloc (2 * slen + 1); + + backslash_table = table ? table : (char *)bstab; + mb_cur_max = MB_CUR_MAX; + + for (r = result, s = string; s && (c = *s); s++) + { +#if defined (HANDLE_MULTIBYTE) + /* XXX - isascii, even if is_basic(c) == 0 - works in most cases. */ + if (c >= 0 && c <= 127 && backslash_table[(unsigned char)c] == 1) + { + *r++ = '\\'; + *r++ = c; + continue; + } + if ((locale_utf8locale && (c & 0x80)) || + (locale_utf8locale == 0 && mb_cur_max > 1 && is_basic (c) == 0)) + { + COPY_CHAR_P (r, s, send); + s--; /* compensate for auto-increment in loop above */ + continue; + } +#endif + if (backslash_table[(unsigned char)c] == 1) + *r++ = '\\'; + else if (c == '#' && s == string) /* comment char */ + *r++ = '\\'; + else if ((flags&1) && c == '~' && (s == string || s[-1] == ':' || s[-1] == '=')) + /* Tildes are special at the start of a word or after a `:' or `=' + (technically unquoted, but it doesn't make a difference in practice) */ + *r++ = '\\'; + else if ((flags&2) && shellblank((unsigned char)c)) + *r++ = '\\'; + *r++ = c; + } + + *r = '\0'; + return (result); +} + +#if defined (PROMPT_STRING_DECODE) || defined (TRANSLATABLE_STRINGS) +/* Quote characters that get special treatment when in double quotes in STRING + using backslashes. FLAGS is reserved for future use. Return a new string. */ +char * +sh_backslash_quote_for_double_quotes (string, flags) + char *string; + int flags; +{ + unsigned char c; + char *result, *r, *s, *send; + size_t slen; + int mb_cur_max; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + mb_cur_max = MB_CUR_MAX; + result = (char *)xmalloc (2 * slen + 1); + + for (r = result, s = string; s && (c = *s); s++) + { + /* Backslash-newline disappears within double quotes, so don't add one. */ + if ((sh_syntaxtab[c] & CBSDQUOTE) && c != '\n') + *r++ = '\\'; + /* I should probably use the CSPECL flag for these in sh_syntaxtab[] */ + else if (c == CTLESC || c == CTLNUL) + *r++ = CTLESC; /* could be '\\'? */ + +#if defined (HANDLE_MULTIBYTE) + if ((locale_utf8locale && (c & 0x80)) || + (locale_utf8locale == 0 && mb_cur_max > 1 && is_basic (c) == 0)) + { + COPY_CHAR_P (r, s, send); + s--; /* compensate for auto-increment in loop above */ + continue; + } +#endif + + *r++ = c; + } + + *r = '\0'; + return (result); +} +#endif /* PROMPT_STRING_DECODE */ + +char * +sh_quote_reusable (s, flags) + char *s; + int flags; +{ + char *ret; + + if (s == 0) + return s; + else if (*s == 0) + { + ret = (char *)xmalloc (3); + ret[0] = ret[1] = '\''; + ret[2] = '\0'; + } + else if (ansic_shouldquote (s)) + ret = ansic_quote (s, 0, (int *)0); + else if (flags) + ret = sh_backslash_quote (s, 0, 1); + else + ret = sh_single_quote (s); + + return ret; +} + +int +sh_contains_shell_metas (string) + const char *string; +{ + const char *s; + + for (s = string; s && *s; s++) + { + switch (*s) + { + case ' ': case '\t': case '\n': /* IFS white space */ + case '\'': case '"': case '\\': /* quoting chars */ + case '|': case '&': case ';': /* shell metacharacters */ + case '(': case ')': case '<': case '>': + case '!': case '{': case '}': /* reserved words */ + case '*': case '[': case '?': case ']': /* globbing chars */ + case '^': + case '$': case '`': /* expansion chars */ + return (1); + case '~': /* tilde expansion */ + if (s == string || s[-1] == '=' || s[-1] == ':') + return (1); + break; + case '#': + if (s == string) /* comment char */ + return (1); + /* FALLTHROUGH */ + default: + break; + } + } + + return (0); +} + +int +sh_contains_quotes (string) + const char *string; +{ + const char *s; + + for (s = string; s && *s; s++) + { + if (*s == '\'' || *s == '"' || *s == '\\') + return 1; + } + return 0; +} diff --git a/third_party/bash/shtty.c b/third_party/bash/shtty.c new file mode 100644 index 000000000..461e574d4 --- /dev/null +++ b/third_party/bash/shtty.c @@ -0,0 +1,330 @@ +/* + * shtty.c -- abstract interface to the terminal, focusing on capabilities. + */ + +/* Copyright (C) 1999 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include "shtty.h" + +static TTYSTRUCT ttin, ttout; +static int ttsaved = 0; + +int +ttgetattr(fd, ttp) +int fd; +TTYSTRUCT *ttp; +{ +#ifdef TERMIOS_TTY_DRIVER + return tcgetattr(fd, ttp); +#else +# ifdef TERMIO_TTY_DRIVER + return ioctl(fd, TCGETA, ttp); +# else + return ioctl(fd, TIOCGETP, ttp); +# endif +#endif +} + +int +ttsetattr(fd, ttp) +int fd; +TTYSTRUCT *ttp; +{ +#ifdef TERMIOS_TTY_DRIVER + return tcsetattr(fd, TCSADRAIN, ttp); +#else +# ifdef TERMIO_TTY_DRIVER + return ioctl(fd, TCSETAW, ttp); +# else + return ioctl(fd, TIOCSETN, ttp); +# endif +#endif +} + +void +ttsave() +{ + if (ttsaved) + return; + ttgetattr (0, &ttin); + ttgetattr (1, &ttout); + ttsaved = 1; +} + +void +ttrestore() +{ + if (ttsaved == 0) + return; + ttsetattr (0, &ttin); + ttsetattr (1, &ttout); + ttsaved = 0; +} + +/* Retrieve the internally-saved attributes associated with tty fd FD. */ +TTYSTRUCT * +ttattr (fd) + int fd; +{ + if (ttsaved == 0) + return ((TTYSTRUCT *)0); + if (fd == 0) + return &ttin; + else if (fd == 1) + return &ttout; + else + return ((TTYSTRUCT *)0); +} + +/* + * Change attributes in ttp so that when it is installed using + * ttsetattr, the terminal will be in one-char-at-a-time mode. + */ +int +tt_setonechar(ttp) + TTYSTRUCT *ttp; +{ +#if defined (TERMIOS_TTY_DRIVER) || defined (TERMIO_TTY_DRIVER) + + /* XXX - might not want this -- it disables erase and kill processing. */ + ttp->c_lflag &= ~ICANON; + + ttp->c_lflag |= ISIG; +# ifdef IEXTEN + ttp->c_lflag |= IEXTEN; +# endif + + ttp->c_iflag |= ICRNL; /* make sure we get CR->NL on input */ + ttp->c_iflag &= ~INLCR; /* but no NL->CR */ + +# ifdef OPOST + ttp->c_oflag |= OPOST; +# endif +# ifdef ONLCR + ttp->c_oflag |= ONLCR; +# endif +# ifdef OCRNL + ttp->c_oflag &= ~OCRNL; +# endif +# ifdef ONOCR + ttp->c_oflag &= ~ONOCR; +# endif +# ifdef ONLRET + ttp->c_oflag &= ~ONLRET; +# endif + + ttp->c_cc[VMIN] = 1; + ttp->c_cc[VTIME] = 0; + +#else + + ttp->sg_flags |= CBREAK; + +#endif + + return 0; +} + +/* Set the tty associated with FD and TTP into one-character-at-a-time mode */ +int +ttfd_onechar (fd, ttp) + int fd; + TTYSTRUCT *ttp; +{ + if (tt_setonechar(ttp) < 0) + return -1; + return (ttsetattr (fd, ttp)); +} + +/* Set the terminal into one-character-at-a-time mode */ +int +ttonechar () +{ + TTYSTRUCT tt; + + if (ttsaved == 0) + return -1; + tt = ttin; + return (ttfd_onechar (0, &tt)); +} + +/* + * Change attributes in ttp so that when it is installed using + * ttsetattr, the terminal will be in no-echo mode. + */ +int +tt_setnoecho(ttp) + TTYSTRUCT *ttp; +{ +#if defined (TERMIOS_TTY_DRIVER) || defined (TERMIO_TTY_DRIVER) + ttp->c_lflag &= ~(ECHO|ECHOK|ECHONL); +#else + ttp->sg_flags &= ~ECHO; +#endif + + return 0; +} + +/* Set the tty associated with FD and TTP into no-echo mode */ +int +ttfd_noecho (fd, ttp) + int fd; + TTYSTRUCT *ttp; +{ + if (tt_setnoecho (ttp) < 0) + return -1; + return (ttsetattr (fd, ttp)); +} + +/* Set the terminal into no-echo mode */ +int +ttnoecho () +{ + TTYSTRUCT tt; + + if (ttsaved == 0) + return -1; + tt = ttin; + return (ttfd_noecho (0, &tt)); +} + +/* + * Change attributes in ttp so that when it is installed using + * ttsetattr, the terminal will be in eight-bit mode (pass8). + */ +int +tt_seteightbit (ttp) + TTYSTRUCT *ttp; +{ +#if defined (TERMIOS_TTY_DRIVER) || defined (TERMIO_TTY_DRIVER) + ttp->c_iflag &= ~ISTRIP; + ttp->c_cflag |= CS8; + ttp->c_cflag &= ~PARENB; +#else + ttp->sg_flags |= ANYP; +#endif + + return 0; +} + +/* Set the tty associated with FD and TTP into eight-bit mode */ +int +ttfd_eightbit (fd, ttp) + int fd; + TTYSTRUCT *ttp; +{ + if (tt_seteightbit (ttp) < 0) + return -1; + return (ttsetattr (fd, ttp)); +} + +/* Set the terminal into eight-bit mode */ +int +tteightbit () +{ + TTYSTRUCT tt; + + if (ttsaved == 0) + return -1; + tt = ttin; + return (ttfd_eightbit (0, &tt)); +} + +/* + * Change attributes in ttp so that when it is installed using + * ttsetattr, the terminal will be in non-canonical input mode. + */ +int +tt_setnocanon (ttp) + TTYSTRUCT *ttp; +{ +#if defined (TERMIOS_TTY_DRIVER) || defined (TERMIO_TTY_DRIVER) + ttp->c_lflag &= ~ICANON; +#endif + + return 0; +} + +/* Set the tty associated with FD and TTP into non-canonical mode */ +int +ttfd_nocanon (fd, ttp) + int fd; + TTYSTRUCT *ttp; +{ + if (tt_setnocanon (ttp) < 0) + return -1; + return (ttsetattr (fd, ttp)); +} + +/* Set the terminal into non-canonical mode */ +int +ttnocanon () +{ + TTYSTRUCT tt; + + if (ttsaved == 0) + return -1; + tt = ttin; + return (ttfd_nocanon (0, &tt)); +} + +/* + * Change attributes in ttp so that when it is installed using + * ttsetattr, the terminal will be in cbreak, no-echo mode. + */ +int +tt_setcbreak(ttp) + TTYSTRUCT *ttp; +{ + if (tt_setonechar (ttp) < 0) + return -1; + return (tt_setnoecho (ttp)); +} + +/* Set the tty associated with FD and TTP into cbreak (no-echo, + one-character-at-a-time) mode */ +int +ttfd_cbreak (fd, ttp) + int fd; + TTYSTRUCT *ttp; +{ + if (tt_setcbreak (ttp) < 0) + return -1; + return (ttsetattr (fd, ttp)); +} + +/* Set the terminal into cbreak (no-echo, one-character-at-a-time) mode */ +int +ttcbreak () +{ + TTYSTRUCT tt; + + if (ttsaved == 0) + return -1; + tt = ttin; + return (ttfd_cbreak (0, &tt)); +} diff --git a/third_party/bash/shtty.h b/third_party/bash/shtty.h new file mode 100644 index 000000000..fdf379b89 --- /dev/null +++ b/third_party/bash/shtty.h @@ -0,0 +1,112 @@ +/* Copyright (C) 1999-2020 Free Software Foundation, Inc. */ + +/* This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* + * shtty.h -- include the correct system-dependent files to manipulate the + * tty + */ + +#ifndef __SH_TTY_H_ +#define __SH_TTY_H_ + +#include "stdc.h" + +#if defined (_POSIX_VERSION) && defined (HAVE_TERMIOS_H) && defined (HAVE_TCGETATTR) && !defined (TERMIOS_MISSING) +# define TERMIOS_TTY_DRIVER +#else +# if defined (HAVE_TERMIO_H) +# define TERMIO_TTY_DRIVER +# else +# define NEW_TTY_DRIVER +# endif +#endif + +/* + * The _POSIX_SOURCE define is to avoid multiple symbol definitions + * between sys/ioctl.h and termios.h. Ditto for the test against SunOS4 + * and the undefining of several symbols. + */ + +#ifdef TERMIOS_TTY_DRIVER +# if (defined (SunOS4) || defined (SunOS5)) && !defined (_POSIX_SOURCE) +# define _POSIX_SOURCE +# endif +# if defined (SunOS4) +# undef ECHO +# undef NOFLSH +# undef TOSTOP +# endif /* SunOS4 */ +# include +# define TTYSTRUCT struct termios +#else +# ifdef TERMIO_TTY_DRIVER +# include +# define TTYSTRUCT struct termio +# else /* NEW_TTY_DRIVER */ +# include +# define TTYSTRUCT struct sgttyb +# endif +#endif + +/* Functions imported from lib/sh/shtty.c */ + +/* Get and set terminal attributes for the file descriptor passed as + an argument. */ +extern int ttgetattr PARAMS((int, TTYSTRUCT *)); +extern int ttsetattr PARAMS((int, TTYSTRUCT *)); + +/* Save and restore the terminal's attributes from static storage. */ +extern void ttsave PARAMS((void)); +extern void ttrestore PARAMS((void)); + +/* Return the attributes corresponding to the file descriptor (0 or 1) + passed as an argument. */ +extern TTYSTRUCT *ttattr PARAMS((int)); + +/* These functions only operate on the passed TTYSTRUCT; they don't + actually change anything with the kernel's current tty settings. */ +extern int tt_setonechar PARAMS((TTYSTRUCT *)); +extern int tt_setnoecho PARAMS((TTYSTRUCT *)); +extern int tt_seteightbit PARAMS((TTYSTRUCT *)); +extern int tt_setnocanon PARAMS((TTYSTRUCT *)); +extern int tt_setcbreak PARAMS((TTYSTRUCT *)); + +/* These functions are all generally mutually exclusive. If you call + more than one (bracketed with calls to ttsave and ttrestore, of + course), the right thing will happen, but more system calls will be + executed than absolutely necessary. You can do all of this yourself + with the other functions; these are only conveniences. */ + +/* These functions work with a given file descriptor and set terminal + attributes */ +extern int ttfd_onechar PARAMS((int, TTYSTRUCT *)); +extern int ttfd_noecho PARAMS((int, TTYSTRUCT *)); +extern int ttfd_eightbit PARAMS((int, TTYSTRUCT *)); +extern int ttfd_nocanon PARAMS((int, TTYSTRUCT *)); + +extern int ttfd_cbreak PARAMS((int, TTYSTRUCT *)); + +/* These functions work with fd 0 and the TTYSTRUCT saved with ttsave () */ +extern int ttonechar PARAMS((void)); +extern int ttnoecho PARAMS((void)); +extern int tteightbit PARAMS((void)); +extern int ttnocanon PARAMS((void)); + +extern int ttcbreak PARAMS((void)); + +#endif diff --git a/third_party/bash/sig.c b/third_party/bash/sig.c new file mode 100644 index 000000000..6c0615d73 --- /dev/null +++ b/third_party/bash/sig.c @@ -0,0 +1,817 @@ +/* sig.c - interface for shell signal handlers and signal initialization. */ + +/* Copyright (C) 1994-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include + +#include "bashintl.h" + +#include "shell.h" +#include "execute_cmd.h" +#if defined (JOB_CONTROL) +#include "jobs.h" +#endif /* JOB_CONTROL */ +#include "siglist.h" +#include "sig.h" +#include "trap.h" + +#include "common.h" +#include "builtext.h" + +#if defined (READLINE) +# include "bashline.h" +# include "third_party/readline/readline.h" +#endif + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +extern void initialize_siglist PARAMS((void)); +extern void set_original_signal PARAMS((int, SigHandler *)); + +#if !defined (JOB_CONTROL) +extern void initialize_job_signals PARAMS((void)); +#endif + +/* Non-zero after SIGINT. */ +volatile sig_atomic_t interrupt_state = 0; + +/* Non-zero after SIGWINCH */ +volatile sig_atomic_t sigwinch_received = 0; + +/* Non-zero after SIGTERM */ +volatile sig_atomic_t sigterm_received = 0; + +/* Set to the value of any terminating signal received. */ +volatile sig_atomic_t terminating_signal = 0; + +/* The environment at the top-level R-E loop. We use this in + the case of error return. */ +procenv_t top_level; + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) +/* The signal masks that this shell runs with. */ +sigset_t top_level_mask; +#endif /* JOB_CONTROL */ + +/* When non-zero, we throw_to_top_level (). */ +int interrupt_immediately = 0; + +/* When non-zero, we call the terminating signal handler immediately. */ +int terminate_immediately = 0; + +#if defined (SIGWINCH) +static SigHandler *old_winch = (SigHandler *)SIG_DFL; +#endif + +static void initialize_shell_signals PARAMS((void)); + +void +initialize_signals (reinit) + int reinit; +{ + initialize_shell_signals (); + initialize_job_signals (); +#if !defined (HAVE_SYS_SIGLIST) && !defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_STRSIGNAL) + if (reinit == 0) + initialize_siglist (); +#endif /* !HAVE_SYS_SIGLIST && !HAVE_UNDER_SYS_SIGLIST && !HAVE_STRSIGNAL */ +} + +/* A structure describing a signal that terminates the shell if not + caught. The orig_handler member is present so children can reset + these signals back to their original handlers. */ +struct termsig { + int signum; + SigHandler *orig_handler; + int orig_flags; + int core_dump; +}; + +#define NULL_HANDLER (SigHandler *)SIG_DFL + +/* The list of signals that would terminate the shell if not caught. + We catch them, but just so that we can write the history file, + and so forth. */ +static struct termsig terminating_signals[] = { +#ifdef SIGHUP +{ SIGHUP, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGINT +{ SIGINT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGILL +{ SIGILL, NULL_HANDLER, 0, 1}, +#endif + +#ifdef SIGTRAP +{ SIGTRAP, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGIOT +{ SIGIOT, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGDANGER +{ SIGDANGER, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGEMT +{ SIGEMT, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGFPE +{ SIGFPE, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGBUS +{ SIGBUS, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGSEGV +{ SIGSEGV, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGSYS +{ SIGSYS, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGPIPE +{ SIGPIPE, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGALRM +{ SIGALRM, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGTERM +{ SIGTERM, NULL_HANDLER, 0 }, +#endif + +/* These don't generate core dumps on anything but Linux, but we're doing + this just for Linux anyway. */ +#ifdef SIGXCPU +{ SIGXCPU, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGXFSZ +{ SIGXFSZ, NULL_HANDLER, 0, 1 }, +#endif + +#ifdef SIGVTALRM +{ SIGVTALRM, NULL_HANDLER, 0 }, +#endif + +#if 0 +#ifdef SIGPROF +{ SIGPROF, NULL_HANDLER, 0 }, +#endif +#endif + +#ifdef SIGLOST +{ SIGLOST, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGUSR1 +{ SIGUSR1, NULL_HANDLER, 0 }, +#endif + +#ifdef SIGUSR2 +{ SIGUSR2, NULL_HANDLER, 0 }, +#endif +}; + +#define TERMSIGS_LENGTH (sizeof (terminating_signals) / sizeof (struct termsig)) + +#define XSIG(x) (terminating_signals[x].signum) +#define XHANDLER(x) (terminating_signals[x].orig_handler) +#define XSAFLAGS(x) (terminating_signals[x].orig_flags) +#define XCOREDUMP(x) (terminating_signals[x].core_dump) + +static int termsigs_initialized = 0; + +/* Initialize signals that will terminate the shell to do some + unwind protection. For non-interactive shells, we only call + this when a trap is defined for EXIT (0) or when trap is run + to display signal dispositions. */ +void +initialize_terminating_signals () +{ + register int i; +#if defined (HAVE_POSIX_SIGNALS) + struct sigaction act, oact; +#endif + + if (termsigs_initialized) + return; + + /* The following code is to avoid an expensive call to + set_signal_handler () for each terminating_signals. Fortunately, + this is possible in Posix. Unfortunately, we have to call signal () + on non-Posix systems for each signal in terminating_signals. */ +#if defined (HAVE_POSIX_SIGNALS) + act.sa_handler = termsig_sighandler; + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + for (i = 0; i < TERMSIGS_LENGTH; i++) + sigaddset (&act.sa_mask, XSIG (i)); + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* If we've already trapped it, don't do anything. */ + if (signal_is_trapped (XSIG (i))) + continue; + + sigaction (XSIG (i), &act, &oact); + XHANDLER(i) = oact.sa_handler; + XSAFLAGS(i) = oact.sa_flags; + +#if 0 + set_original_signal (XSIG(i), XHANDLER(i)); /* optimization */ +#else + set_original_signal (XSIG(i), act.sa_handler); /* optimization */ +#endif + + /* Don't do anything with signals that are ignored at shell entry + if the shell is not interactive. */ + /* XXX - should we do this for interactive shells, too? */ + if (interactive_shell == 0 && XHANDLER (i) == SIG_IGN) + { + sigaction (XSIG (i), &oact, &act); + set_signal_hard_ignored (XSIG (i)); + } +#if defined (SIGPROF) && !defined (_MINIX) + if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) + sigaction (XSIG (i), &oact, (struct sigaction *)NULL); +#endif /* SIGPROF && !_MINIX */ + } +#else /* !HAVE_POSIX_SIGNALS */ + + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* If we've already trapped it, don't do anything. */ + if (signal_is_trapped (XSIG (i))) + continue; + + XHANDLER(i) = signal (XSIG (i), termsig_sighandler); + XSAFLAGS(i) = 0; + /* Don't do anything with signals that are ignored at shell entry + if the shell is not interactive. */ + /* XXX - should we do this for interactive shells, too? */ + if (interactive_shell == 0 && XHANDLER (i) == SIG_IGN) + { + signal (XSIG (i), SIG_IGN); + set_signal_hard_ignored (XSIG (i)); + } +#ifdef SIGPROF + if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) + signal (XSIG (i), XHANDLER (i)); +#endif + } + +#endif /* !HAVE_POSIX_SIGNALS */ + + termsigs_initialized = 1; +} + +static void +initialize_shell_signals () +{ + if (interactive) + initialize_terminating_signals (); + +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) + /* All shells use the signal mask they inherit, and pass it along + to child processes. Children will never block SIGCHLD, though. */ + sigemptyset (&top_level_mask); + sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &top_level_mask); +# if defined (SIGCHLD) + if (sigismember (&top_level_mask, SIGCHLD)) + { + sigdelset (&top_level_mask, SIGCHLD); + sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); + } +# endif +#endif /* JOB_CONTROL || HAVE_POSIX_SIGNALS */ + + /* And, some signals that are specifically ignored by the shell. */ + set_signal_handler (SIGQUIT, SIG_IGN); + + if (interactive) + { + set_signal_handler (SIGINT, sigint_sighandler); + get_original_signal (SIGTERM); + set_signal_handler (SIGTERM, SIG_IGN); + set_sigwinch_handler (); + } +} + +void +reset_terminating_signals () +{ + register int i; +#if defined (HAVE_POSIX_SIGNALS) + struct sigaction act; +#endif + + if (termsigs_initialized == 0) + return; + +#if defined (HAVE_POSIX_SIGNALS) + act.sa_flags = 0; + sigemptyset (&act.sa_mask); + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + /* Skip a signal if it's trapped or handled specially, because the + trap code will restore the correct value. */ + if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) + continue; + + act.sa_handler = XHANDLER (i); + act.sa_flags = XSAFLAGS (i); + sigaction (XSIG (i), &act, (struct sigaction *) NULL); + } +#else /* !HAVE_POSIX_SIGNALS */ + for (i = 0; i < TERMSIGS_LENGTH; i++) + { + if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) + continue; + + signal (XSIG (i), XHANDLER (i)); + } +#endif /* !HAVE_POSIX_SIGNALS */ + + termsigs_initialized = 0; +} +#undef XHANDLER + +/* Run some of the cleanups that should be performed when we run + jump_to_top_level from a builtin command context. XXX - might want to + also call reset_parser here. */ +void +top_level_cleanup () +{ + /* Clean up string parser environment. */ + while (parse_and_execute_level) + parse_and_execute_cleanup (-1); + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + run_unwind_protects (); + loop_level = continuing = breaking = funcnest = 0; + executing_list = comsub_ignore_return = return_catch_flag = wait_intr_flag = 0; +} + +/* What to do when we've been interrupted, and it is safe to handle it. */ +void +throw_to_top_level () +{ + int print_newline = 0; + + if (interrupt_state) + { + if (last_command_exit_value < 128) + last_command_exit_value = 128 + SIGINT; + set_pipestatus_from_exit (last_command_exit_value); + print_newline = 1; + DELINTERRUPT; + } + + if (interrupt_state) + return; + + last_command_exit_signal = (last_command_exit_value > 128) ? + (last_command_exit_value - 128) : 0; + last_command_exit_value |= 128; + set_pipestatus_from_exit (last_command_exit_value); + + /* Run any traps set on SIGINT, mostly for interactive shells */ + if (signal_is_trapped (SIGINT) && signal_is_pending (SIGINT)) + run_interrupt_trap (1); + + /* Clean up string parser environment. */ + while (parse_and_execute_level) + parse_and_execute_cleanup (-1); + + if (running_trap > 0) + { + run_trap_cleanup (running_trap - 1); + running_trap = 0; + } + +#if defined (JOB_CONTROL) + give_terminal_to (shell_pgrp, 0); +#endif /* JOB_CONTROL */ + + /* This needs to stay because jobs.c:make_child() uses it without resetting + the signal mask. */ + restore_sigmask (); + + reset_parser (); + +#if defined (READLINE) + if (interactive) + bashline_reset (); +#endif /* READLINE */ + +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + run_unwind_protects (); + loop_level = continuing = breaking = funcnest = 0; + executing_list = comsub_ignore_return = return_catch_flag = wait_intr_flag = 0; + + if (interactive && print_newline) + { + fflush (stdout); + fprintf (stderr, "\n"); + fflush (stderr); + } + + /* An interrupted `wait' command in a script does not exit the script. */ + if (interactive || (interactive_shell && !shell_initialized) || + (print_newline && signal_is_trapped (SIGINT))) + jump_to_top_level (DISCARD); + else + jump_to_top_level (EXITPROG); +} + +/* This is just here to isolate the longjmp calls. */ +void +jump_to_top_level (value) + int value; +{ + sh_longjmp (top_level, value); +} + +void +restore_sigmask () +{ +#if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) + sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); +#endif +} + +sighandler +termsig_sighandler (sig) + int sig; +{ + /* If we get called twice with the same signal before handling it, + terminate right away. */ + if ( +#ifdef SIGHUP + sig != SIGHUP && +#endif +#ifdef SIGINT + sig != SIGINT && +#endif +#ifdef SIGDANGER + sig != SIGDANGER && +#endif +#ifdef SIGPIPE + sig != SIGPIPE && +#endif +#ifdef SIGALRM + sig != SIGALRM && +#endif +#ifdef SIGTERM + sig != SIGTERM && +#endif +#ifdef SIGXCPU + sig != SIGXCPU && +#endif +#ifdef SIGXFSZ + sig != SIGXFSZ && +#endif +#ifdef SIGVTALRM + sig != SIGVTALRM && +#endif +#ifdef SIGLOST + sig != SIGLOST && +#endif +#ifdef SIGUSR1 + sig != SIGUSR1 && +#endif +#ifdef SIGUSR2 + sig != SIGUSR2 && +#endif + sig == terminating_signal) + terminate_immediately = 1; + + terminating_signal = sig; + + if (terminate_immediately) + { +#if defined (HISTORY) + /* XXX - will inhibit history file being written */ +# if defined (READLINE) + if (interactive_shell == 0 || interactive == 0 || (sig != SIGHUP && sig != SIGTERM) || no_line_editing || (RL_ISSTATE (RL_STATE_READCMD) == 0)) +# endif + history_lines_this_session = 0; +#endif + terminate_immediately = 0; + termsig_handler (sig); + } + +#if defined (READLINE) + /* Set the event hook so readline will call it after the signal handlers + finish executing, so if this interrupted character input we can get + quick response. If readline is active or has modified the terminal we + need to set this no matter what the signal is, though the check for + RL_STATE_TERMPREPPED is possibly redundant. */ + if (RL_ISSTATE (RL_STATE_SIGHANDLER) || RL_ISSTATE (RL_STATE_TERMPREPPED)) + bashline_set_event_hook (); +#endif + + SIGRETURN (0); +} + +void +termsig_handler (sig) + int sig; +{ + static int handling_termsig = 0; + int i, core; + sigset_t mask; + + /* Simple semaphore to keep this function from being executed multiple + times. Since we no longer are running as a signal handler, we don't + block multiple occurrences of the terminating signals while running. */ + if (handling_termsig) + return; + handling_termsig = 1; + terminating_signal = 0; /* keep macro from re-testing true. */ + + /* I don't believe this condition ever tests true. */ + if (sig == SIGINT && signal_is_trapped (SIGINT)) + run_interrupt_trap (0); + +#if defined (HISTORY) + /* If we don't do something like this, the history will not be saved when + an interactive shell is running in a terminal window that gets closed + with the `close' button. We can't test for RL_STATE_READCMD because + readline no longer handles SIGTERM synchronously. */ + if (interactive_shell && interactive && (sig == SIGHUP || sig == SIGTERM) && remember_on_history) + maybe_save_shell_history (); +#endif /* HISTORY */ + + if (this_shell_builtin == read_builtin) + read_tty_cleanup (); + +#if defined (JOB_CONTROL) + if (sig == SIGHUP && (interactive || (subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PROCSUB)))) + hangup_all_jobs (); + + if ((subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PROCSUB)) == 0) + end_job_control (); +#endif /* JOB_CONTROL */ + +#if defined (PROCESS_SUBSTITUTION) + unlink_all_fifos (); +# if defined (JOB_CONTROL) + procsub_clear (); +# endif +#endif /* PROCESS_SUBSTITUTION */ + + /* Reset execution context */ + loop_level = continuing = breaking = funcnest = 0; + executing_list = comsub_ignore_return = return_catch_flag = wait_intr_flag = 0; + + run_exit_trap (); /* XXX - run exit trap possibly in signal context? */ + + /* We don't change the set of blocked signals. If a user starts the shell + with a terminating signal blocked, we won't get here (and if by some + magic chance we do, we'll exit below). What we do is to restore the + top-level signal mask, in case this is called from a terminating signal + handler context, in which case the signal is blocked. */ + restore_sigmask (); + + set_signal_handler (sig, SIG_DFL); + + kill (getpid (), sig); + + if (dollar_dollar_pid != 1) + exit (128+sig); /* just in case the kill fails? */ + + /* We get here only under extraordinary circumstances. */ + + /* We are PID 1, and the kill above failed to kill the process. We assume + this means that we are running as an init process in a pid namespace + on Linux. In this case, we can't send ourselves a fatal signal, so we + determine whether or not we should have generated a core dump with the + kill call and attempt to trick the kernel into generating one if + necessary. */ + sigprocmask (SIG_SETMASK, (sigset_t *)NULL, &mask); + for (i = core = 0; i < TERMSIGS_LENGTH; i++) + { + set_signal_handler (XSIG (i), SIG_DFL); + sigdelset (&mask, XSIG (i)); + if (sig == XSIG (i)) + core = XCOREDUMP (i); + } + sigprocmask (SIG_SETMASK, &mask, (sigset_t *)NULL); + + if (core) + *((volatile unsigned long *) NULL) = 0xdead0000 + sig; /* SIGSEGV */ + + exit (128+sig); +} +#undef XSIG + +/* What we really do when SIGINT occurs. */ +sighandler +sigint_sighandler (sig) + int sig; +{ +#if defined (MUST_REINSTALL_SIGHANDLERS) + signal (sig, sigint_sighandler); +#endif + + /* interrupt_state needs to be set for the stack of interrupts to work + right. Should it be set unconditionally? */ + if (interrupt_state == 0) + ADDINTERRUPT; + + /* We will get here in interactive shells with job control active; allow + an interactive wait to be interrupted. wait_intr_flag is only set during + the execution of the wait builtin and when wait_intr_buf is valid. */ + if (wait_intr_flag) + { + last_command_exit_value = 128 + sig; + set_pipestatus_from_exit (last_command_exit_value); + wait_signal_received = sig; + SIGRETURN (0); + } + + /* In interactive shells, we will get here instead of trap_handler() so + note that we have a trap pending. */ + if (signal_is_trapped (sig)) + set_trap_state (sig); + + /* This is no longer used, but this code block remains as a reminder. */ + if (interrupt_immediately) + { + interrupt_immediately = 0; + set_exit_status (128 + sig); + throw_to_top_level (); + } +#if defined (READLINE) + /* Set the event hook so readline will call it after the signal handlers + finish executing, so if this interrupted character input we can get + quick response. */ + else if (RL_ISSTATE (RL_STATE_SIGHANDLER)) + bashline_set_event_hook (); +#endif + + SIGRETURN (0); +} + +#if defined (SIGWINCH) +sighandler +sigwinch_sighandler (sig) + int sig; +{ +#if defined (MUST_REINSTALL_SIGHANDLERS) + set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif /* MUST_REINSTALL_SIGHANDLERS */ + sigwinch_received = 1; + SIGRETURN (0); +} +#endif /* SIGWINCH */ + +void +set_sigwinch_handler () +{ +#if defined (SIGWINCH) + old_winch = set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif +} + +void +unset_sigwinch_handler () +{ +#if defined (SIGWINCH) + set_signal_handler (SIGWINCH, old_winch); +#endif +} + +sighandler +sigterm_sighandler (sig) + int sig; +{ + sigterm_received = 1; /* XXX - counter? */ + SIGRETURN (0); +} + +/* Signal functions used by the rest of the code. */ +#if !defined (HAVE_POSIX_SIGNALS) + +/* Perform OPERATION on NEWSET, perhaps leaving information in OLDSET. */ +sigprocmask (operation, newset, oldset) + int operation, *newset, *oldset; +{ + int old, new; + + if (newset) + new = *newset; + else + new = 0; + + switch (operation) + { + case SIG_BLOCK: + old = sigblock (new); + break; + + case SIG_SETMASK: + old = sigsetmask (new); + break; + + default: + internal_error (_("sigprocmask: %d: invalid operation"), operation); + } + + if (oldset) + *oldset = old; +} + +#else + +#if !defined (SA_INTERRUPT) +# define SA_INTERRUPT 0 +#endif + +#if !defined (SA_RESTART) +# define SA_RESTART 0 +#endif + +SigHandler * +set_signal_handler (sig, handler) + int sig; + SigHandler *handler; +{ + struct sigaction act, oact; + + act.sa_handler = handler; + act.sa_flags = 0; + + /* XXX - bash-4.2 */ + /* We don't want a child death to interrupt interruptible system calls, even + if we take the time to reap children */ +#if defined (SIGCHLD) + if (sig == SIGCHLD) + act.sa_flags |= SA_RESTART; /* XXX */ +#endif + /* Let's see if we can keep SIGWINCH from interrupting interruptible system + calls, like open(2)/read(2)/write(2) */ +#if defined (SIGWINCH) + if (sig == SIGWINCH) + act.sa_flags |= SA_RESTART; /* XXX */ +#endif + /* If we're installing a SIGTERM handler for interactive shells, we want + it to be as close to SIG_IGN as possible. */ + if (sig == SIGTERM && handler == sigterm_sighandler) + act.sa_flags |= SA_RESTART; /* XXX */ + + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + if (sigaction (sig, &act, &oact) == 0) + return (oact.sa_handler); + else + return (SIG_DFL); +} +#endif /* HAVE_POSIX_SIGNALS */ diff --git a/third_party/bash/sig.h b/third_party/bash/sig.h new file mode 100644 index 000000000..0217be53d --- /dev/null +++ b/third_party/bash/sig.h @@ -0,0 +1,136 @@ +/* sig.h -- header file for signal handler definitions. */ + +/* Copyright (C) 1994-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* Make sure that this is included *after* config.h! */ + +#if !defined (_SIG_H_) +# define _SIG_H_ + +#include "stdc.h" + +#include /* for sig_atomic_t */ + +#if !defined (SIGABRT) && defined (SIGIOT) +# define SIGABRT SIGIOT +#endif + +#define sighandler void +typedef void SigHandler PARAMS((int)); + +#define SIGRETURN(n) return + +/* Here is a definition for set_signal_handler () which simply expands to + a call to signal () for non-Posix systems. The code for set_signal_handler + in the Posix case resides in general.c. */ +#if !defined (HAVE_POSIX_SIGNALS) +# define set_signal_handler(sig, handler) (SigHandler *)signal (sig, handler) +#else +extern SigHandler *set_signal_handler PARAMS((int, SigHandler *)); /* in sig.c */ +#endif /* _POSIX_VERSION */ + +#if !defined (SIGCHLD) && defined (SIGCLD) +# define SIGCHLD SIGCLD +#endif + +#if !defined (HAVE_POSIX_SIGNALS) && !defined (sigmask) +# define sigmask(x) (1 << ((x)-1)) +#endif /* !HAVE_POSIX_SIGNALS && !sigmask */ + +#if !defined (HAVE_POSIX_SIGNALS) +# if !defined (SIG_BLOCK) +# define SIG_BLOCK 2 +# define SIG_SETMASK 3 +# endif /* SIG_BLOCK */ + +/* sigset_t defined in config.h */ + +/* Make sure there is nothing inside the signal set. */ +# define sigemptyset(set) (*(set) = 0) + +/* Initialize the signal set to hold all signals. */ +# define sigfillset(set) (*set) = sigmask (NSIG) - 1 + +/* Add SIG to the contents of SET. */ +# define sigaddset(set, sig) *(set) |= sigmask (sig) + +/* Delete SIG from signal set SET. */ +# define sigdelset(set, sig) *(set) &= ~sigmask (sig) + +/* Is SIG a member of the signal set SET? */ +# define sigismember(set, sig) ((*(set) & sigmask (sig)) != 0) + +/* Suspend the process until the reception of one of the signals + not present in SET. */ +# define sigsuspend(set) sigpause (*(set)) +#endif /* !HAVE_POSIX_SIGNALS */ + +/* These definitions are used both in POSIX and non-POSIX implementations. */ + +#define BLOCK_SIGNAL(sig, nvar, ovar) \ +do { \ + sigemptyset (&nvar); \ + sigaddset (&nvar, sig); \ + sigemptyset (&ovar); \ + sigprocmask (SIG_BLOCK, &nvar, &ovar); \ +} while (0) + +#define UNBLOCK_SIGNAL(ovar) sigprocmask (SIG_SETMASK, &ovar, (sigset_t *) NULL) + +#if defined (HAVE_POSIX_SIGNALS) +# define BLOCK_CHILD(nvar, ovar) BLOCK_SIGNAL (SIGCHLD, nvar, ovar) +# define UNBLOCK_CHILD(ovar) UNBLOCK_SIGNAL(ovar) +#else /* !HAVE_POSIX_SIGNALS */ +# define BLOCK_CHILD(nvar, ovar) ovar = sigblock (sigmask (SIGCHLD)) +# define UNBLOCK_CHILD(ovar) sigsetmask (ovar) +#endif /* !HAVE_POSIX_SIGNALS */ + +/* Extern variables */ +extern volatile sig_atomic_t sigwinch_received; +extern volatile sig_atomic_t sigterm_received; + +extern int interrupt_immediately; /* no longer used */ +extern int terminate_immediately; + +/* Functions from sig.c. */ +extern sighandler termsig_sighandler PARAMS((int)); +extern void termsig_handler PARAMS((int)); +extern sighandler sigint_sighandler PARAMS((int)); +extern void initialize_signals PARAMS((int)); +extern void initialize_terminating_signals PARAMS((void)); +extern void reset_terminating_signals PARAMS((void)); +extern void top_level_cleanup PARAMS((void)); +extern void throw_to_top_level PARAMS((void)); +extern void jump_to_top_level PARAMS((int)) __attribute__((__noreturn__)); +extern void restore_sigmask PARAMS((void)); + +extern sighandler sigwinch_sighandler PARAMS((int)); +extern void set_sigwinch_handler PARAMS((void)); +extern void unset_sigwinch_handler PARAMS((void)); + +extern sighandler sigterm_sighandler PARAMS((int)); + +/* Functions defined in trap.c. */ +extern SigHandler *set_sigint_handler PARAMS((void)); +extern SigHandler *trap_to_sighandler PARAMS((int)); +extern sighandler trap_handler PARAMS((int)); + +extern int block_trapped_signals PARAMS((sigset_t *, sigset_t *)); +extern int unblock_trapped_signals PARAMS((sigset_t *)); +#endif /* _SIG_H_ */ diff --git a/third_party/bash/siglist.h b/third_party/bash/siglist.h new file mode 100644 index 000000000..321c20c46 --- /dev/null +++ b/third_party/bash/siglist.h @@ -0,0 +1,44 @@ +/* siglist.h -- encapsulate various definitions for sys_siglist */ + +/* Copyright (C) 1993, 2001, 2005, 2008-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_SIGLIST_H_) +#define _SIGLIST_H_ + +#if !defined (SYS_SIGLIST_DECLARED) && !defined (HAVE_STRSIGNAL) + +#if defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_SYS_SIGLIST) && !defined (sys_siglist) +# define sys_siglist _sys_siglist +#endif /* HAVE_UNDER_SYS_SIGLIST && !HAVE_SYS_SIGLIST && !sys_siglist */ + +#if !defined (sys_siglist) +extern char *sys_siglist[]; +#endif /* !sys_siglist */ + +#endif /* !SYS_SIGLIST_DECLARED && !HAVE_STRSIGNAL */ + +#if !defined (strsignal) && !defined (HAVE_STRSIGNAL) +# define strsignal(sig) (char *)sys_siglist[sig] +#endif /* !strsignal && !HAVE_STRSIGNAL */ + +#if !defined (strsignal) && !HAVE_DECL_STRSIGNAL +extern char *strsignal PARAMS((int)); +#endif + +#endif /* _SIGLIST_H */ diff --git a/third_party/bash/signames.c b/third_party/bash/signames.c new file mode 100644 index 000000000..391482893 --- /dev/null +++ b/third_party/bash/signames.c @@ -0,0 +1,446 @@ +/* signames.c -- Create an array of signal names. */ + +/* Copyright (C) 2006-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include + +#include +#include + +#if defined (HAVE_STDLIB_H) +# include +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#if !defined (NSIG) +# define NSIG 64 +#endif + +/* + * Special traps: + * EXIT == 0 + * DEBUG == NSIG + * ERR == NSIG+1 + * RETURN == NSIG+2 + */ +#define LASTSIG NSIG+2 + +char *signal_names[2 * (LASTSIG)]; + +#define signal_names_size (sizeof(signal_names)/sizeof(signal_names[0])) + +/* AIX 4.3 defines SIGRTMIN and SIGRTMAX as 888 and 999 respectively. + I don't want to allocate so much unused space for the intervening signal + numbers, so we just punt if SIGRTMAX is past the bounds of the + signal_names array (handled in configure). */ +#if defined (SIGRTMAX) && defined (UNUSABLE_RT_SIGNALS) +# undef SIGRTMAX +# undef SIGRTMIN +#endif + +#if defined (SIGRTMAX) || defined (SIGRTMIN) +# define RTLEN 14 +# define RTLIM 256 +#endif + +#if defined (BUILDTOOL) +extern char *progname; +#endif + +void +initialize_signames () +{ + register int i; +#if defined (SIGRTMAX) || defined (SIGRTMIN) + int rtmin, rtmax, rtcnt; +#endif + + for (i = 1; i < signal_names_size; i++) + signal_names[i] = (char *)NULL; + + /* `signal' 0 is what we do on exit. */ + signal_names[0] = "EXIT"; + + /* Place signal names which can be aliases for more common signal + names first. This allows (for example) SIGABRT to overwrite SIGLOST. */ + + /* POSIX 1003.1b-1993 real time signals, but take care of incomplete + implementations. According to the standard, both SIGRTMIN and + SIGRTMAX must be defined, SIGRTMIN must be strictly less than + SIGRTMAX, and the difference must be at least 7; that is, there + must be at least eight distinct real time signals. */ + + /* The generated signal names are SIGRTMIN, SIGRTMIN+1, ..., + SIGRTMIN+x, SIGRTMAX-x, ..., SIGRTMAX-1, SIGRTMAX. If the number + of RT signals is odd, there is an extra SIGRTMIN+(x+1). + These names are the ones used by ksh and /usr/xpg4/bin/sh on SunOS5. */ + +#if defined (SIGRTMIN) + rtmin = SIGRTMIN; + signal_names[rtmin] = "SIGRTMIN"; +#endif + +#if defined (SIGRTMAX) + rtmax = SIGRTMAX; + signal_names[rtmax] = "SIGRTMAX"; +#endif + +#if defined (SIGRTMAX) && defined (SIGRTMIN) + if (rtmax > rtmin) + { + rtcnt = (rtmax - rtmin - 1) / 2; + /* croak if there are too many RT signals */ + if (rtcnt >= RTLIM/2) + { + rtcnt = RTLIM/2-1; +#ifdef BUILDTOOL + fprintf(stderr, "%s: error: more than %d real time signals, fix `%s'\n", + progname, RTLIM, progname); +#endif + } + + for (i = 1; i <= rtcnt; i++) + { + signal_names[rtmin+i] = (char *)malloc(RTLEN); + if (signal_names[rtmin+i]) + sprintf (signal_names[rtmin+i], "SIGRTMIN+%d", i); + signal_names[rtmax-i] = (char *)malloc(RTLEN); + if (signal_names[rtmax-i]) + sprintf (signal_names[rtmax-i], "SIGRTMAX-%d", i); + } + + if (rtcnt < RTLIM/2-1 && rtcnt != (rtmax-rtmin)/2) + { + /* Need an extra RTMIN signal */ + signal_names[rtmin+rtcnt+1] = (char *)malloc(RTLEN); + if (signal_names[rtmin+rtcnt+1]) + sprintf (signal_names[rtmin+rtcnt+1], "SIGRTMIN+%d", rtcnt+1); + } + } +#endif /* SIGRTMIN && SIGRTMAX */ + +#if defined (SIGLOST) /* resource lost (eg, record-lock lost) */ + signal_names[SIGLOST] = "SIGLOST"; +#endif + +/* AIX */ +#if defined (SIGMSG) /* HFT input data pending */ + signal_names[SIGMSG] = "SIGMSG"; +#endif + +#if defined (SIGDANGER) /* system crash imminent */ + signal_names[SIGDANGER] = "SIGDANGER"; +#endif + +#if defined (SIGMIGRATE) /* migrate process to another CPU */ + signal_names[SIGMIGRATE] = "SIGMIGRATE"; +#endif + +#if defined (SIGPRE) /* programming error */ + signal_names[SIGPRE] = "SIGPRE"; +#endif + +#if defined (SIGPHONE) /* Phone interrupt */ + signal_names[SIGPHONE] = "SIGPHONE"; +#endif + +#if defined (SIGVIRT) /* AIX virtual time alarm */ + signal_names[SIGVIRT] = "SIGVIRT"; +#endif + +#if defined (SIGTINT) /* Interrupt */ + signal_names[SIGTINT] = "SIGTINT"; +#endif + +#if defined (SIGALRM1) /* m:n condition variables */ + signal_names[SIGALRM1] = "SIGALRM1"; +#endif + +#if defined (SIGWAITING) /* m:n scheduling */ + signal_names[SIGWAITING] = "SIGWAITING"; +#endif + +#if defined (SIGGRANT) /* HFT monitor mode granted */ + signal_names[SIGGRANT] = "SIGGRANT"; +#endif + +#if defined (SIGKAP) /* keep alive poll from native keyboard */ + signal_names[SIGKAP] = "SIGKAP"; +#endif + +#if defined (SIGRETRACT) /* HFT monitor mode retracted */ + signal_names[SIGRETRACT] = "SIGRETRACT"; +#endif + +#if defined (SIGSOUND) /* HFT sound sequence has completed */ + signal_names[SIGSOUND] = "SIGSOUND"; +#endif + +#if defined (SIGSAK) /* Secure Attention Key */ + signal_names[SIGSAK] = "SIGSAK"; +#endif + +#if defined (SIGCPUFAIL) /* Predictive processor deconfiguration */ + signal_names[SIGCPUFAIL] = "SIGCPUFAIL"; +#endif + +#if defined (SIGAIO) /* Asynchronous I/O */ + signal_names[SIGAIO] = "SIGAIO"; +#endif + +#if defined (SIGLAB) /* Security label changed */ + signal_names[SIGLAB] = "SIGLAB"; +#endif + +/* SunOS5 */ +#if defined (SIGLWP) /* Solaris: special signal used by thread library */ + signal_names[SIGLWP] = "SIGLWP"; +#endif + +#if defined (SIGFREEZE) /* Solaris: special signal used by CPR */ + signal_names[SIGFREEZE] = "SIGFREEZE"; +#endif + +#if defined (SIGTHAW) /* Solaris: special signal used by CPR */ + signal_names[SIGTHAW] = "SIGTHAW"; +#endif + +#if defined (SIGCANCEL) /* Solaris: thread cancellation signal used by libthread */ + signal_names[SIGCANCEL] = "SIGCANCEL"; +#endif + +#if defined (SIGXRES) /* Solaris: resource control exceeded */ + signal_names[SIGXRES] = "SIGXRES"; +#endif + +#if defined (SIGJVM1) /* Solaris: Java Virtual Machine 1 */ + signal_names[SIGJVM1] = "SIGJVM1"; +#endif + +#if defined (SIGJVM2) /* Solaris: Java Virtual Machine 2 */ + signal_names[SIGJVM2] = "SIGJVM2"; +#endif + +#if defined (SIGDGTIMER1) + signal_names[SIGDGTIMER1] = "SIGDGTIMER1"; +#endif + +#if defined (SIGDGTIMER2) + signal_names[SIGDGTIMER2] = "SIGDGTIMER2"; +#endif + +#if defined (SIGDGTIMER3) + signal_names[SIGDGTIMER3] = "SIGDGTIMER3"; +#endif + +#if defined (SIGDGTIMER4) + signal_names[SIGDGTIMER4] = "SIGDGTIMER4"; +#endif + +#if defined (SIGDGNOTIFY) + signal_names[SIGDGNOTIFY] = "SIGDGNOTIFY"; +#endif + +/* Apollo */ +#if defined (SIGAPOLLO) + signal_names[SIGAPOLLO] = "SIGAPOLLO"; +#endif + +/* HP-UX */ +#if defined (SIGDIL) /* DIL signal (?) */ + signal_names[SIGDIL] = "SIGDIL"; +#endif + +/* System V */ +#if defined (SIGCLD) /* Like SIGCHLD. */ + signal_names[SIGCLD] = "SIGCLD"; +#endif + +#if defined (SIGPWR) /* power state indication */ + signal_names[SIGPWR] = "SIGPWR"; +#endif + +#if defined (SIGPOLL) /* Pollable event (for streams) */ + signal_names[SIGPOLL] = "SIGPOLL"; +#endif + +/* Unknown */ +#if defined (SIGWINDOW) + signal_names[SIGWINDOW] = "SIGWINDOW"; +#endif + +/* Linux */ +#if defined (SIGSTKFLT) + signal_names[SIGSTKFLT] = "SIGSTKFLT"; +#endif + +/* FreeBSD */ +#if defined (SIGTHR) /* thread interrupt */ + signal_names[SIGTHR] = "SIGTHR"; +#endif + +/* Common */ +#if defined (SIGHUP) /* hangup */ + signal_names[SIGHUP] = "SIGHUP"; +#endif + +#if defined (SIGINT) /* interrupt */ + signal_names[SIGINT] = "SIGINT"; +#endif + +#if defined (SIGQUIT) /* quit */ + signal_names[SIGQUIT] = "SIGQUIT"; +#endif + +#if defined (SIGILL) /* illegal instruction (not reset when caught) */ + signal_names[SIGILL] = "SIGILL"; +#endif + +#if defined (SIGTRAP) /* trace trap (not reset when caught) */ + signal_names[SIGTRAP] = "SIGTRAP"; +#endif + +#if defined (SIGIOT) /* IOT instruction */ + signal_names[SIGIOT] = "SIGIOT"; +#endif + +#if defined (SIGABRT) /* Cause current process to dump core. */ + signal_names[SIGABRT] = "SIGABRT"; +#endif + +#if defined (SIGEMT) /* EMT instruction */ + signal_names[SIGEMT] = "SIGEMT"; +#endif + +#if defined (SIGFPE) /* floating point exception */ + signal_names[SIGFPE] = "SIGFPE"; +#endif + +#if defined (SIGKILL) /* kill (cannot be caught or ignored) */ + signal_names[SIGKILL] = "SIGKILL"; +#endif + +#if defined (SIGBUS) /* bus error */ + signal_names[SIGBUS] = "SIGBUS"; +#endif + +#if defined (SIGSEGV) /* segmentation violation */ + signal_names[SIGSEGV] = "SIGSEGV"; +#endif + +#if defined (SIGSYS) /* bad argument to system call */ + signal_names[SIGSYS] = "SIGSYS"; +#endif + +#if defined (SIGPIPE) /* write on a pipe with no one to read it */ + signal_names[SIGPIPE] = "SIGPIPE"; +#endif + +#if defined (SIGALRM) /* alarm clock */ + signal_names[SIGALRM] = "SIGALRM"; +#endif + +#if defined (SIGTERM) /* software termination signal from kill */ + signal_names[SIGTERM] = "SIGTERM"; +#endif + +#if defined (SIGURG) /* urgent condition on IO channel */ + signal_names[SIGURG] = "SIGURG"; +#endif + +#if defined (SIGSTOP) /* sendable stop signal not from tty */ + signal_names[SIGSTOP] = "SIGSTOP"; +#endif + +#if defined (SIGTSTP) /* stop signal from tty */ + signal_names[SIGTSTP] = "SIGTSTP"; +#endif + +#if defined (SIGCONT) /* continue a stopped process */ + signal_names[SIGCONT] = "SIGCONT"; +#endif + +#if defined (SIGCHLD) /* to parent on child stop or exit */ + signal_names[SIGCHLD] = "SIGCHLD"; +#endif + +#if defined (SIGTTIN) /* to readers pgrp upon background tty read */ + signal_names[SIGTTIN] = "SIGTTIN"; +#endif + +#if defined (SIGTTOU) /* like TTIN for output if (tp->t_local<OSTOP) */ + signal_names[SIGTTOU] = "SIGTTOU"; +#endif + +#if defined (SIGIO) /* input/output possible signal */ + signal_names[SIGIO] = "SIGIO"; +#endif + +#if defined (SIGXCPU) /* exceeded CPU time limit */ + signal_names[SIGXCPU] = "SIGXCPU"; +#endif + +#if defined (SIGXFSZ) /* exceeded file size limit */ + signal_names[SIGXFSZ] = "SIGXFSZ"; +#endif + +#if defined (SIGVTALRM) /* virtual time alarm */ + signal_names[SIGVTALRM] = "SIGVTALRM"; +#endif + +#if defined (SIGPROF) /* profiling time alarm */ + signal_names[SIGPROF] = "SIGPROF"; +#endif + +#if defined (SIGWINCH) /* window changed */ + signal_names[SIGWINCH] = "SIGWINCH"; +#endif + +/* 4.4 BSD */ +#if defined (SIGINFO) && !defined (_SEQUENT_) /* information request */ + signal_names[SIGINFO] = "SIGINFO"; +#endif + +#if defined (SIGUSR1) /* user defined signal 1 */ + signal_names[SIGUSR1] = "SIGUSR1"; +#endif + +#if defined (SIGUSR2) /* user defined signal 2 */ + signal_names[SIGUSR2] = "SIGUSR2"; +#endif + +#if defined (SIGKILLTHR) /* BeOS: Kill Thread */ + signal_names[SIGKILLTHR] = "SIGKILLTHR"; +#endif + + for (i = 0; i < NSIG; i++) + if (signal_names[i] == (char *)NULL) + { + signal_names[i] = (char *)malloc (18); + if (signal_names[i]) + sprintf (signal_names[i], "SIGJUNK(%d)", i); + } + + signal_names[NSIG] = "DEBUG"; + signal_names[NSIG+1] = "ERR"; + signal_names[NSIG+2] = "RETURN"; +} diff --git a/third_party/bash/signames.h b/third_party/bash/signames.h new file mode 100644 index 000000000..29efbf1ed --- /dev/null +++ b/third_party/bash/signames.h @@ -0,0 +1,2 @@ +extern char *signal_names[NSIG + 4]; +void initialize_signames (); diff --git a/third_party/bash/sm_loop.inc b/third_party/bash/sm_loop.inc new file mode 100644 index 000000000..247ba28aa --- /dev/null +++ b/third_party/bash/sm_loop.inc @@ -0,0 +1,981 @@ +/* Copyright (C) 1991-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +extern int interrupt_state, terminating_signal; + +struct STRUCT +{ + CHAR *pattern; + CHAR *string; +}; + +int FCT PARAMS((CHAR *, CHAR *, int)); + +static int GMATCH PARAMS((CHAR *, CHAR *, CHAR *, CHAR *, struct STRUCT *, int)); +static CHAR *PARSE_COLLSYM PARAMS((CHAR *, INT *)); +static CHAR *BRACKMATCH PARAMS((CHAR *, U_CHAR, int)); +static int EXTMATCH PARAMS((INT, CHAR *, CHAR *, CHAR *, CHAR *, int)); + +extern void DEQUOTE_PATHNAME PARAMS((CHAR *)); + +/*static*/ CHAR *PATSCAN PARAMS((CHAR *, CHAR *, INT)); + +int +FCT (pattern, string, flags) + CHAR *pattern; + CHAR *string; + int flags; +{ + CHAR *se, *pe; + + if (string == 0 || pattern == 0) + return FNM_NOMATCH; + + se = string + STRLEN ((XCHAR *)string); + pe = pattern + STRLEN ((XCHAR *)pattern); + + return (GMATCH (string, se, pattern, pe, (struct STRUCT *)NULL, flags)); +} + +/* Match STRING against the filename pattern PATTERN, returning zero if + it matches, FNM_NOMATCH if not. */ +static int +GMATCH (string, se, pattern, pe, ends, flags) + CHAR *string, *se; + CHAR *pattern, *pe; + struct STRUCT *ends; + int flags; +{ + CHAR *p, *n; /* pattern, string */ + INT c; /* current pattern character - XXX U_CHAR? */ + INT sc; /* current string character - XXX U_CHAR? */ + + p = pattern; + n = string; + + if (string == 0 || pattern == 0) + return FNM_NOMATCH; + +#if DEBUG_MATCHING +fprintf(stderr, "gmatch: string = %s; se = %s\n", string, se); +fprintf(stderr, "gmatch: pattern = %s; pe = %s\n", pattern, pe); +#endif + + while (p < pe) + { + c = *p++; + c = FOLD (c); + + sc = n < se ? *n : '\0'; + + if (interrupt_state || terminating_signal) + return FNM_NOMATCH; + +#ifdef EXTENDED_GLOB + /* EXTMATCH () will handle recursively calling GMATCH, so we can + just return what EXTMATCH() returns. */ + if ((flags & FNM_EXTMATCH) && *p == L('(') && + (c == L('+') || c == L('*') || c == L('?') || c == L('@') || c == L('!'))) /* ) */ + { + int lflags; + /* If we're not matching the start of the string, we're not + concerned about the special cases for matching `.' */ + lflags = (n == string) ? flags : (flags & ~(FNM_PERIOD|FNM_DOTDOT)); + return (EXTMATCH (c, n, se, p, pe, lflags)); + } +#endif /* EXTENDED_GLOB */ + + switch (c) + { + case L('?'): /* Match single character */ + if (sc == '\0') + return FNM_NOMATCH; + else if ((flags & FNM_PATHNAME) && sc == L('/')) + /* If we are matching a pathname, `?' can never match a `/'. */ + return FNM_NOMATCH; + else if ((flags & FNM_PERIOD) && sc == L('.') && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == L('/')))) + /* `?' cannot match a `.' if it is the first character of the + string or if it is the first character following a slash and + we are matching a pathname. */ + return FNM_NOMATCH; + + /* `?' cannot match `.' or `..' if it is the first character of the + string or if it is the first character following a slash and + we are matching a pathname. */ + if ((flags & FNM_DOTDOT) && + ((n == string && SDOT_OR_DOTDOT(n)) || + ((flags & FNM_PATHNAME) && n[-1] == L('/') && PDOT_OR_DOTDOT(n)))) + return FNM_NOMATCH; + + break; + + case L('\\'): /* backslash escape removes special meaning */ + if (p == pe && sc == '\\' && (n+1 == se)) + break; + + if (p == pe) + return FNM_NOMATCH; + + if ((flags & FNM_NOESCAPE) == 0) + { + c = *p++; + /* A trailing `\' cannot match. */ + if (p > pe) + return FNM_NOMATCH; + c = FOLD (c); + } + if (FOLD (sc) != (U_CHAR)c) + return FNM_NOMATCH; + break; + + case L('*'): /* Match zero or more characters */ + /* See below for the reason for using this. It avoids backtracking + back to a previous `*'. Picked up from glibc. */ + if (ends != NULL) + { + ends->pattern = p - 1; + ends->string = n; + return (0); + } + + if ((flags & FNM_PERIOD) && sc == L('.') && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == L('/')))) + /* `*' cannot match a `.' if it is the first character of the + string or if it is the first character following a slash and + we are matching a pathname. */ + return FNM_NOMATCH; + + /* `*' cannot match `.' or `..' if it is the first character of the + string or if it is the first character following a slash and + we are matching a pathname. */ + if ((flags & FNM_DOTDOT) && + ((n == string && SDOT_OR_DOTDOT(n)) || + ((flags & FNM_PATHNAME) && n[-1] == L('/') && PDOT_OR_DOTDOT(n)))) + return FNM_NOMATCH; + + if (p == pe) + return 0; + + /* Collapse multiple consecutive `*' and `?', but make sure that + one character of the string is consumed for each `?'. */ + for (c = *p++; (c == L('?') || c == L('*')); c = *p++) + { + if ((flags & FNM_PATHNAME) && sc == L('/')) + /* A slash does not match a wildcard under FNM_PATHNAME. */ + return FNM_NOMATCH; +#ifdef EXTENDED_GLOB + else if ((flags & FNM_EXTMATCH) && c == L('?') && *p == L('(')) /* ) */ + { + CHAR *newn; + + /* We can match 0 or 1 times. If we match, return success */ + if (EXTMATCH (c, n, se, p, pe, flags) == 0) + return (0); + + /* We didn't match the extended glob pattern, but + that's OK, since we can match 0 or 1 occurrences. + We need to skip the glob pattern and see if we + match the rest of the string. */ + newn = PATSCAN (p + 1, pe, 0); + /* If NEWN is 0, we have an ill-formed pattern. */ + p = newn ? newn : pe; + } +#endif + else if (c == L('?')) + { + if (sc == L('\0')) + return FNM_NOMATCH; + /* One character of the string is consumed in matching + this ? wildcard, so *??? won't match if there are + fewer than three characters. */ + n++; + sc = n < se ? *n : '\0'; + } + +#ifdef EXTENDED_GLOB + /* Handle ******(patlist) */ + if ((flags & FNM_EXTMATCH) && c == L('*') && *p == L('(')) /*)*/ + { + CHAR *newn; + /* We need to check whether or not the extended glob + pattern matches the remainder of the string. + If it does, we match the entire pattern. */ + for (newn = n; newn < se; ++newn) + { + if (EXTMATCH (c, newn, se, p, pe, flags) == 0) + return (0); + } + /* We didn't match the extended glob pattern, but + that's OK, since we can match 0 or more occurrences. + We need to skip the glob pattern and see if we + match the rest of the string. */ + newn = PATSCAN (p + 1, pe, 0); + /* If NEWN is 0, we have an ill-formed pattern. */ + p = newn ? newn : pe; + } +#endif + if (p == pe) + break; + } + + /* The wildcards are the last element of the pattern. The name + cannot match completely if we are looking for a pathname and + it contains another slash, unless FNM_LEADING_DIR is set. */ + if (c == L('\0')) + { + int r = (flags & FNM_PATHNAME) == 0 ? 0 : FNM_NOMATCH; + if (flags & FNM_PATHNAME) + { + if (flags & FNM_LEADING_DIR) + r = 0; + else if (MEMCHR (n, L('/'), se - n) == NULL) + r = 0; + } + return r; + } + + /* If we've hit the end of the pattern and the last character of + the pattern was handled by the loop above, we've succeeded. + Otherwise, we need to match that last character. */ + if (p == pe && (c == L('?') || c == L('*'))) + return (0); + + /* If we've hit the end of the string and the rest of the pattern + is something that matches the empty string, we can succeed. */ +#if defined (EXTENDED_GLOB) + if (n == se && ((flags & FNM_EXTMATCH) && (c == L('!') || c == L('?')) && *p == L('('))) + { + --p; + if (EXTMATCH (c, n, se, p, pe, flags) == 0) + return (c == L('!') ? FNM_NOMATCH : 0); + return (c == L('!') ? 0 : FNM_NOMATCH); + } +#endif + + /* If we stop at a slash in the pattern and we are looking for a + pathname ([star]/foo), then consume enough of the string to stop + at any slash and then try to match the rest of the pattern. If + the string doesn't contain a slash, fail */ + if (c == L('/') && (flags & FNM_PATHNAME)) + { + while (n < se && *n != L('/')) + ++n; + if (n < se && *n == L('/') && (GMATCH (n+1, se, p, pe, NULL, flags) == 0)) + return 0; + return FNM_NOMATCH; /* XXX */ + } + + /* General case, use recursion. */ + { + U_CHAR c1; + const CHAR *endp; + struct STRUCT end; + + end.pattern = NULL; + endp = MEMCHR (n, (flags & FNM_PATHNAME) ? L('/') : L('\0'), se - n); + if (endp == 0) + endp = se; + + c1 = ((flags & FNM_NOESCAPE) == 0 && c == L('\\')) ? *p : c; + c1 = FOLD (c1); + for (--p; n < endp; ++n) + { + /* Only call strmatch if the first character indicates a + possible match. We can check the first character if + we're not doing an extended glob match. */ + if ((flags & FNM_EXTMATCH) == 0 && c != L('[') && FOLD (*n) != c1) /*]*/ + continue; + + /* If we're doing an extended glob match and the pattern is not + one of the extended glob patterns, we can check the first + character. */ + if ((flags & FNM_EXTMATCH) && p[1] != L('(') && /*)*/ + STRCHR (L("?*+@!"), *p) == 0 && c != L('[') && FOLD (*n) != c1) /*]*/ + continue; + + /* Otherwise, we just recurse. */ + if (GMATCH (n, se, p, pe, &end, flags & ~(FNM_PERIOD|FNM_DOTDOT)) == 0) + { + if (end.pattern == NULL) + return (0); + break; + } + } + /* This is a clever idea from glibc, used to avoid backtracking + to a `*' that appears earlier in the pattern. We get away + without saving se and pe because they are always the same, + even in the recursive calls to gmatch */ + if (end.pattern != NULL) + { + p = end.pattern; + n = end.string; + continue; + } + + return FNM_NOMATCH; + } + + case L('['): + { + if (sc == L('\0') || n == se) + return FNM_NOMATCH; + + /* A character class cannot match a `.' if it is the first + character of the string or if it is the first character + following a slash and we are matching a pathname. */ + if ((flags & FNM_PERIOD) && sc == L('.') && + (n == string || ((flags & FNM_PATHNAME) && n[-1] == L('/')))) + return (FNM_NOMATCH); + + /* `?' cannot match `.' or `..' if it is the first character of the + string or if it is the first character following a slash and + we are matching a pathname. */ + if ((flags & FNM_DOTDOT) && + ((n == string && SDOT_OR_DOTDOT(n)) || + ((flags & FNM_PATHNAME) && n[-1] == L('/') && PDOT_OR_DOTDOT(n)))) + return FNM_NOMATCH; + + p = BRACKMATCH (p, sc, flags); + if (p == 0) + return FNM_NOMATCH; + } + break; + + default: + if ((U_CHAR)c != FOLD (sc)) + return (FNM_NOMATCH); + } + + ++n; + } + + if (n == se) + return (0); + + if ((flags & FNM_LEADING_DIR) && *n == L('/')) + /* The FNM_LEADING_DIR flag says that "foo*" matches "foobar/frobozz". */ + return 0; + + return (FNM_NOMATCH); +} + +/* Parse a bracket expression collating symbol ([.sym.]) starting at P, find + the value of the symbol, and move P past the collating symbol expression. + The value is returned in *VP, if VP is not null. */ +static CHAR * +PARSE_COLLSYM (p, vp) + CHAR *p; + INT *vp; +{ + register int pc; + INT val; + + p++; /* move past the `.' */ + + for (pc = 0; p[pc]; pc++) + if (p[pc] == L('.') && p[pc+1] == L(']')) + break; + if (p[pc] == 0) + { + if (vp) + *vp = INVALID; + return (p + pc); + } + val = COLLSYM (p, pc); + if (vp) + *vp = val; + return (p + pc + 2); +} + +/* Use prototype definition here because of type promotion. */ +static CHAR * +#if defined (PROTOTYPES) +BRACKMATCH (CHAR *p, U_CHAR test, int flags) +#else +BRACKMATCH (p, test, flags) + CHAR *p; + U_CHAR test; + int flags; +#endif +{ + register CHAR cstart, cend, c; + register int not; /* Nonzero if the sense of the character class is inverted. */ + int brcnt, forcecoll, isrange; + INT pc; + CHAR *savep; + CHAR *brchrp; + U_CHAR orig_test; + + orig_test = test; + test = FOLD (orig_test); + + savep = p; + + /* POSIX.2 3.13.1 says that an exclamation mark (`!') shall replace the + circumflex (`^') in its role in a `nonmatching list'. A bracket + expression starting with an unquoted circumflex character produces + unspecified results. This implementation treats the two identically. */ + if (not = (*p == L('!') || *p == L('^'))) + ++p; + + c = *p++; + for (;;) + { + /* Initialize cstart and cend in case `-' is the last + character of the pattern. */ + cstart = cend = c; + forcecoll = 0; + + /* POSIX.2 equivalence class: [=c=]. See POSIX.2 2.8.3.2. Find + the end of the equivalence class, move the pattern pointer past + it, and check for equivalence. XXX - this handles only + single-character equivalence classes, which is wrong, or at + least incomplete. */ + if (c == L('[') && *p == L('=') && p[2] == L('=') && p[3] == L(']')) + { + pc = FOLD (p[1]); + p += 4; + if (COLLEQUIV (test, pc)) + { +/*[*/ /* Move past the closing `]', since the first thing we do at + the `matched:' label is back p up one. */ + p++; + goto matched; + } + else + { + c = *p++; + if (c == L('\0')) + return ((test == L('[')) ? savep : (CHAR *)0); /*]*/ + c = FOLD (c); + continue; + } + } + + /* POSIX.2 character class expression. See POSIX.2 2.8.3.2. */ + if (c == L('[') && *p == L(':')) + { + CHAR *close, *ccname; + + pc = 0; /* make sure invalid char classes don't match. */ + /* Find end of character class name */ + for (close = p + 1; *close != '\0'; close++) + if (*close == L(':') && *(close+1) == L(']')) + break; + + if (*close != L('\0')) + { + ccname = (CHAR *)malloc ((close - p) * sizeof (CHAR)); + if (ccname == 0) + pc = 0; + else + { + bcopy (p + 1, ccname, (close - p - 1) * sizeof (CHAR)); + *(ccname + (close - p - 1)) = L('\0'); + /* As a result of a POSIX discussion, char class names are + allowed to be quoted (?) */ + DEQUOTE_PATHNAME (ccname); + pc = IS_CCLASS (orig_test, (XCHAR *)ccname); + } + if (pc == -1) + { + /* CCNAME is not a valid character class in the current + locale. In addition to noting no match (pc = 0), we have + a choice about what to do with the invalid charclass. + Posix leaves the behavior unspecified, but we're going + to skip over the charclass and keep going instead of + testing ORIG_TEST against each character in the class + string. If we don't want to do that, take out the update + of P. */ + pc = 0; + p = close + 2; + } + else + p = close + 2; /* move past the closing `]' */ + + free (ccname); + } + + if (pc) + { +/*[*/ /* Move past the closing `]', since the first thing we do at + the `matched:' label is back p up one. */ + p++; + goto matched; + } + else + { + /* continue the loop here, since this expression can't be + the first part of a range expression. */ + c = *p++; + if (c == L('\0')) + return ((test == L('[')) ? savep : (CHAR *)0); + else if (c == L(']')) + break; + c = FOLD (c); + continue; + } + } + + /* POSIX.2 collating symbols. See POSIX.2 2.8.3.2. Find the end of + the symbol name, make sure it is terminated by `.]', translate + the name to a character using the external table, and do the + comparison. */ + if (c == L('[') && *p == L('.')) + { + p = PARSE_COLLSYM (p, &pc); + /* An invalid collating symbol cannot be the first point of a + range. If it is, we set cstart to one greater than `test', + so any comparisons later will fail. */ + cstart = (pc == INVALID) ? test + 1 : pc; + forcecoll = 1; + } + + if (!(flags & FNM_NOESCAPE) && c == L('\\')) + { + if (*p == '\0') + return (CHAR *)0; + cstart = cend = *p++; + } + + cstart = cend = FOLD (cstart); + isrange = 0; + + /* POSIX.2 2.8.3.1.2 says: `An expression containing a `[' that + is not preceded by a backslash and is not part of a bracket + expression produces undefined results.' This implementation + treats the `[' as just a character to be matched if there is + not a closing `]'. */ + if (c == L('\0')) + return ((test == L('[')) ? savep : (CHAR *)0); + + c = *p++; + c = FOLD (c); + + if (c == L('\0')) + return ((test == L('[')) ? savep : (CHAR *)0); + + if ((flags & FNM_PATHNAME) && c == L('/')) + /* [/] can never match when matching a pathname. */ + return (CHAR *)0; + + /* This introduces a range, unless the `-' is the last + character of the class. Find the end of the range + and move past it. */ + if (c == L('-') && *p != L(']')) + { + cend = *p++; + if (!(flags & FNM_NOESCAPE) && cend == L('\\')) + cend = *p++; + if (cend == L('\0')) + return (CHAR *)0; + if (cend == L('[') && *p == L('.')) + { + p = PARSE_COLLSYM (p, &pc); + /* An invalid collating symbol cannot be the second part of a + range expression. If we get one, we set cend to one fewer + than the test character to make sure the range test fails. */ + cend = (pc == INVALID) ? test - 1 : pc; + forcecoll = 1; + } + cend = FOLD (cend); + + c = *p++; + + /* POSIX.2 2.8.3.2: ``The ending range point shall collate + equal to or higher than the starting range point; otherwise + the expression shall be treated as invalid.'' Note that this + applies to only the range expression; the rest of the bracket + expression is still checked for matches. */ + if (RANGECMP (cstart, cend, forcecoll) > 0) + { + if (c == L(']')) + break; + c = FOLD (c); + continue; + } + isrange = 1; + } + + if (isrange == 0 && test == cstart) + goto matched; + if (isrange && RANGECMP (test, cstart, forcecoll) >= 0 && RANGECMP (test, cend, forcecoll) <= 0) + goto matched; + + if (c == L(']')) + break; + } + /* No match. */ + return (!not ? (CHAR *)0 : p); + +matched: + /* Skip the rest of the [...] that already matched. */ + c = *--p; + brcnt = 1; + brchrp = 0; + while (brcnt > 0) + { + int oc; + + /* A `[' without a matching `]' is just another character to match. */ + if (c == L('\0')) + return ((test == L('[')) ? savep : (CHAR *)0); + + oc = c; + c = *p++; + if (c == L('[') && (*p == L('=') || *p == L(':') || *p == L('.'))) + { + brcnt++; + brchrp = p++; /* skip over the char after the left bracket */ + if ((c = *p) == L('\0')) + return ((test == L('[')) ? savep : (CHAR *)0); + /* If *brchrp == ':' we should check that the rest of the characters + form a valid character class name. We don't do that yet, but we + keep BRCHRP in case we want to. */ + } + /* We only want to check brchrp if we set it above. */ + else if (c == L(']') && brcnt > 1 && brchrp != 0 && oc == *brchrp) + { + brcnt--; + brchrp = 0; /* just in case */ + } + /* Left bracket loses its special meaning inside a bracket expression. + It is only valid when followed by a `.', `=', or `:', which we check + for above. Technically the right bracket can appear in a collating + symbol, so we check for that here. Otherwise, it terminates the + bracket expression. */ + else if (c == L(']') && (brchrp == 0 || *brchrp != L('.')) && brcnt >= 1) + brcnt = 0; + else if (!(flags & FNM_NOESCAPE) && c == L('\\')) + { + if (*p == '\0') + return (CHAR *)0; + /* XXX 1003.2d11 is unclear if this is right. */ + ++p; + } + } + return (not ? (CHAR *)0 : p); +} + +#if defined (EXTENDED_GLOB) +/* ksh-like extended pattern matching: + + [?*+@!](pat-list) + + where pat-list is a list of one or patterns separated by `|'. Operation + is as follows: + + ?(patlist) match zero or one of the given patterns + *(patlist) match zero or more of the given patterns + +(patlist) match one or more of the given patterns + @(patlist) match exactly one of the given patterns + !(patlist) match anything except one of the given patterns +*/ + +/* Scan a pattern starting at STRING and ending at END, keeping track of + embedded () and []. If DELIM is 0, we scan until a matching `)' + because we're scanning a `patlist'. Otherwise, we scan until we see + DELIM. In all cases, we never scan past END. The return value is the + first character after the matching DELIM or NULL if the pattern is + empty or invalid. */ +/*static*/ CHAR * +PATSCAN (string, end, delim) + CHAR *string, *end; + INT delim; +{ + int pnest, bnest, skip; + INT cchar; + CHAR *s, c, *bfirst; + + pnest = bnest = skip = 0; + cchar = 0; + bfirst = NULL; + + if (string == end) + return (NULL); + + for (s = string; c = *s; s++) + { + if (s >= end) + return (s); + if (skip) + { + skip = 0; + continue; + } + switch (c) + { + case L('\\'): + skip = 1; + break; + + case L('\0'): + return ((CHAR *)NULL); + + /* `[' is not special inside a bracket expression, but it may + introduce one of the special POSIX bracket expressions + ([.SYM.], [=c=], [: ... :]) that needs special handling. */ + case L('['): + if (bnest == 0) + { + bfirst = s + 1; + if (*bfirst == L('!') || *bfirst == L('^')) + bfirst++; + bnest++; + } + else if (s[1] == L(':') || s[1] == L('.') || s[1] == L('=')) + cchar = s[1]; + break; + + /* `]' is not special if it's the first char (after a leading `!' + or `^') in a bracket expression or if it's part of one of the + special POSIX bracket expressions ([.SYM.], [=c=], [: ... :]) */ + case L(']'): + if (bnest) + { + if (cchar && s[-1] == cchar) + cchar = 0; + else if (s != bfirst) + { + bnest--; + bfirst = 0; + } + } + break; + + case L('('): + if (bnest == 0) + pnest++; + break; + + case L(')'): + if (bnest == 0 && pnest-- <= 0) + return ++s; + break; + + case L('|'): + if (bnest == 0 && pnest == 0 && delim == L('|')) + return ++s; + break; + } + } + + return (NULL); +} + +/* Return 0 if dequoted pattern matches S in the current locale. */ +static int +STRCOMPARE (p, pe, s, se) + CHAR *p, *pe, *s, *se; +{ + int ret; + CHAR c1, c2; + int l1, l2; + + l1 = pe - p; + l2 = se - s; + + if (l1 != l2) + return (FNM_NOMATCH); /* unequal lengths, can't be identical */ + + c1 = *pe; + c2 = *se; + + if (c1 != 0) + *pe = '\0'; + if (c2 != 0) + *se = '\0'; + +#if HAVE_MULTIBYTE || defined (HAVE_STRCOLL) + ret = STRCOLL ((XCHAR *)p, (XCHAR *)s); +#else + ret = STRCMP ((XCHAR *)p, (XCHAR *)s); +#endif + + if (c1 != 0) + *pe = c1; + if (c2 != 0) + *se = c2; + + return (ret == 0 ? ret : FNM_NOMATCH); +} + +/* Match a ksh extended pattern specifier. Return FNM_NOMATCH on failure or + 0 on success. This is handed the entire rest of the pattern and string + the first time an extended pattern specifier is encountered, so it calls + gmatch recursively. */ +static int +EXTMATCH (xc, s, se, p, pe, flags) + INT xc; /* select which operation */ + CHAR *s, *se; + CHAR *p, *pe; + int flags; +{ + CHAR *prest; /* pointer to rest of pattern */ + CHAR *psub; /* pointer to sub-pattern */ + CHAR *pnext; /* pointer to next sub-pattern */ + CHAR *srest; /* pointer to rest of string */ + int m1, m2, xflags; /* xflags = flags passed to recursive matches */ + +#if DEBUG_MATCHING +fprintf(stderr, "extmatch: xc = %c\n", xc); +fprintf(stderr, "extmatch: s = %s; se = %s\n", s, se); +fprintf(stderr, "extmatch: p = %s; pe = %s\n", p, pe); +fprintf(stderr, "extmatch: flags = %d\n", flags); +#endif + + prest = PATSCAN (p + (*p == L('(')), pe, 0); /* ) */ + if (prest == 0) + /* If PREST is 0, we failed to scan a valid pattern. In this + case, we just want to compare the two as strings. */ + return (STRCOMPARE (p - 1, pe, s, se)); + + switch (xc) + { + case L('+'): /* match one or more occurrences */ + case L('*'): /* match zero or more occurrences */ + /* If we can get away with no matches, don't even bother. Just + call GMATCH on the rest of the pattern and return success if + it succeeds. */ + if (xc == L('*') && (GMATCH (s, se, prest, pe, NULL, flags) == 0)) + return 0; + + /* OK, we have to do this the hard way. First, we make sure one of + the subpatterns matches, then we try to match the rest of the + string. */ + for (psub = p + 1; ; psub = pnext) + { + pnext = PATSCAN (psub, pe, L('|')); + for (srest = s; srest <= se; srest++) + { + /* Match this substring (S -> SREST) against this + subpattern (psub -> pnext - 1) */ + m1 = GMATCH (s, srest, psub, pnext - 1, NULL, flags) == 0; + /* OK, we matched a subpattern, so make sure the rest of the + string matches the rest of the pattern. Also handle + multiple matches of the pattern. */ + if (m1) + { + /* if srest > s, we are not at start of string */ + xflags = (srest > s) ? (flags & ~(FNM_PERIOD|FNM_DOTDOT)) : flags; + m2 = (GMATCH (srest, se, prest, pe, NULL, xflags) == 0) || + (s != srest && GMATCH (srest, se, p - 1, pe, NULL, xflags) == 0); + } + if (m1 && m2) + return (0); + } + if (pnext == prest) + break; + } + return (FNM_NOMATCH); + + case L('?'): /* match zero or one of the patterns */ + case L('@'): /* match one (or more) of the patterns */ + /* If we can get away with no matches, don't even bother. Just + call gmatch on the rest of the pattern and return success if + it succeeds. */ + if (xc == L('?') && (GMATCH (s, se, prest, pe, NULL, flags) == 0)) + return 0; + + /* OK, we have to do this the hard way. First, we see if one of + the subpatterns matches, then, if it does, we try to match the + rest of the string. */ + for (psub = p + 1; ; psub = pnext) + { + pnext = PATSCAN (psub, pe, L('|')); + srest = (prest == pe) ? se : s; + for ( ; srest <= se; srest++) + { + /* if srest > s, we are not at start of string */ + xflags = (srest > s) ? (flags & ~(FNM_PERIOD|FNM_DOTDOT)) : flags; + if (GMATCH (s, srest, psub, pnext - 1, NULL, flags) == 0 && + GMATCH (srest, se, prest, pe, NULL, xflags) == 0) + return (0); + } + if (pnext == prest) + break; + } + return (FNM_NOMATCH); + + case '!': /* match anything *except* one of the patterns */ + for (srest = s; srest <= se; srest++) + { + m1 = 0; + for (psub = p + 1; ; psub = pnext) + { + pnext = PATSCAN (psub, pe, L('|')); + /* If one of the patterns matches, just bail immediately. */ + if (m1 = (GMATCH (s, srest, psub, pnext - 1, NULL, flags) == 0)) + break; + if (pnext == prest) + break; + } + + /* If nothing matched, but the string starts with a period and we + need to match periods explicitly, don't return this as a match, + even for negation. */ + if (m1 == 0 && (flags & FNM_PERIOD) && *s == '.') + return (FNM_NOMATCH); + + if (m1 == 0 && (flags & FNM_DOTDOT) && + (SDOT_OR_DOTDOT (s) || + ((flags & FNM_PATHNAME) && s[-1] == L('/') && PDOT_OR_DOTDOT(s)))) + return (FNM_NOMATCH); + + /* if srest > s, we are not at start of string */ + xflags = (srest > s) ? (flags & ~(FNM_PERIOD|FNM_DOTDOT)) : flags; + if (m1 == 0 && GMATCH (srest, se, prest, pe, NULL, xflags) == 0) + return (0); + } + return (FNM_NOMATCH); + } + + return (FNM_NOMATCH); +} +#endif /* EXTENDED_GLOB */ + +#undef IS_CCLASS +#undef FOLD +#undef CHAR +#undef U_CHAR +#undef XCHAR +#undef INT +#undef INVALID +#undef FCT +#undef GMATCH +#undef COLLSYM +#undef PARSE_COLLSYM +#undef PATSCAN +#undef STRCOMPARE +#undef EXTMATCH +#undef DEQUOTE_PATHNAME +#undef STRUCT +#undef BRACKMATCH +#undef STRCHR +#undef STRCOLL +#undef STRLEN +#undef STRCMP +#undef MEMCHR +#undef COLLEQUIV +#undef RANGECMP +#undef ISDIRSEP +#undef PATHSEP +#undef PDOT_OR_DOTDOT +#undef SDOT_OR_DOTDOT +#undef L diff --git a/third_party/bash/smatch.c b/third_party/bash/smatch.c new file mode 100644 index 000000000..e265c9571 --- /dev/null +++ b/third_party/bash/smatch.c @@ -0,0 +1,638 @@ +/* strmatch.c -- ksh-like extended pattern matching for the shell and filename + globbing. */ + +/* Copyright (C) 1991-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include /* for debugging */ + +#include "strmatch.h" +#include "chartypes.h" + +#include "bashansi.h" +#include "shmbutil.h" +#include "xmalloc.h" + +#include + +#if !defined (errno) +extern int errno; +#endif + +#if FNMATCH_EQUIV_FALLBACK +/* We don't include in order to avoid namespace collisions; the + internal strmatch still uses the FNM_ constants. */ +extern int fnmatch (const char *, const char *, int); +#endif + +/* First, compile `sm_loop.c' for single-byte characters. */ +#define CHAR unsigned char +#define U_CHAR unsigned char +#define XCHAR char +#define INT int +#define L(CS) CS +#define INVALID -1 + +#undef STREQ +#undef STREQN +#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0) +#define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, n) == 0) + +#ifndef GLOBASCII_DEFAULT +# define GLOBASCII_DEFAULT 0 +#endif + +int glob_asciirange = GLOBASCII_DEFAULT; + +#if FNMATCH_EQUIV_FALLBACK +/* Construct a string w1 = "c1" and a pattern w2 = "[[=c2=]]" and pass them + to fnmatch to see if wide characters c1 and c2 collate as members of the + same equivalence class. We can't really do this portably any other way */ +static int +_fnmatch_fallback (s, p) + int s, p; /* string char, patchar */ +{ + char s1[2]; /* string */ + char s2[8]; /* constructed pattern */ + + s1[0] = (unsigned char)s; + s1[1] = '\0'; + + /* reconstruct the pattern */ + s2[0] = s2[1] = '['; + s2[2] = '='; + s2[3] = (unsigned char)p; + s2[4] = '='; + s2[5] = s2[6] = ']'; + s2[7] = '\0'; + + return (fnmatch ((const char *)s2, (const char *)s1, 0)); +} +#endif + +/* We use strcoll(3) for range comparisons in bracket expressions, + even though it can have unwanted side effects in locales + other than POSIX or US. For instance, in the de locale, [A-Z] matches + all characters. If GLOB_ASCIIRANGE is non-zero, and we're not forcing + the use of strcoll (e.g., for explicit collating symbols), we use + straight ordering as if in the C locale. */ + +#if defined (HAVE_STRCOLL) +/* Helper functions for collating symbol equivalence. */ + +/* Return 0 if C1 == C2 or collates equally if FORCECOLL is non-zero. */ +static int +charcmp (c1, c2, forcecoll) + int c1, c2; + int forcecoll; +{ + static char s1[2] = { ' ', '\0' }; + static char s2[2] = { ' ', '\0' }; + int ret; + + /* Eight bits only. Period. */ + c1 &= 0xFF; + c2 &= 0xFF; + + if (c1 == c2) + return (0); + + if (forcecoll == 0 && glob_asciirange) + return (c1 - c2); + + s1[0] = c1; + s2[0] = c2; + + return (strcoll (s1, s2)); +} + +static int +rangecmp (c1, c2, forcecoll) + int c1, c2; + int forcecoll; +{ + int r; + + r = charcmp (c1, c2, forcecoll); + + /* We impose a total ordering here by returning c1-c2 if charcmp returns 0 */ + if (r != 0) + return r; + return (c1 - c2); /* impose total ordering */ +} +#else /* !HAVE_STRCOLL */ +# define rangecmp(c1, c2, f) ((int)(c1) - (int)(c2)) +#endif /* !HAVE_STRCOLL */ + +#if defined (HAVE_STRCOLL) +/* Returns 1 if chars C and EQUIV collate equally in the current locale. */ +static int +collequiv (c, equiv) + int c, equiv; +{ + if (charcmp (c, equiv, 1) == 0) + return 1; + +#if FNMATCH_EQUIV_FALLBACK + return (_fnmatch_fallback (c, equiv) == 0); +#else + return 0; +#endif + +} +#else +# define collequiv(c, equiv) ((c) == (equiv)) +#endif + +#define _COLLSYM _collsym +#define __COLLSYM __collsym +#define POSIXCOLL posix_collsyms +#include "collsyms.h" + +static int +collsym (s, len) + CHAR *s; + int len; +{ + register struct _collsym *csp; + char *x; + + x = (char *)s; + for (csp = posix_collsyms; csp->name; csp++) + { + if (STREQN(csp->name, x, len) && csp->name[len] == '\0') + return (csp->code); + } + if (len == 1) + return s[0]; + return INVALID; +} + +/* unibyte character classification */ +#if !defined (isascii) && !defined (HAVE_ISASCII) +# define isascii(c) ((unsigned int)(c) <= 0177) +#endif + +enum char_class + { + CC_NO_CLASS = 0, + CC_ASCII, CC_ALNUM, CC_ALPHA, CC_BLANK, CC_CNTRL, CC_DIGIT, CC_GRAPH, + CC_LOWER, CC_PRINT, CC_PUNCT, CC_SPACE, CC_UPPER, CC_WORD, CC_XDIGIT + }; + +static char const *const cclass_name[] = + { + "", + "ascii", "alnum", "alpha", "blank", "cntrl", "digit", "graph", + "lower", "print", "punct", "space", "upper", "word", "xdigit" + }; + +#define N_CHAR_CLASS (sizeof(cclass_name) / sizeof (cclass_name[0])) + +static enum char_class +is_valid_cclass (name) + const char *name; +{ + enum char_class ret; + int i; + + ret = CC_NO_CLASS; + + for (i = 1; i < N_CHAR_CLASS; i++) + { + if (STREQ (name, cclass_name[i])) + { + ret = (enum char_class)i; + break; + } + } + + return ret; +} + +static int +cclass_test (c, char_class) + int c; + enum char_class char_class; +{ + int result; + + switch (char_class) + { + case CC_ASCII: + result = isascii (c); + break; + case CC_ALNUM: + result = ISALNUM (c); + break; + case CC_ALPHA: + result = ISALPHA (c); + break; + case CC_BLANK: + result = ISBLANK (c); + break; + case CC_CNTRL: + result = ISCNTRL (c); + break; + case CC_DIGIT: + result = ISDIGIT (c); + break; + case CC_GRAPH: + result = ISGRAPH (c); + break; + case CC_LOWER: + result = ISLOWER (c); + break; + case CC_PRINT: + result = ISPRINT (c); + break; + case CC_PUNCT: + result = ISPUNCT (c); + break; + case CC_SPACE: + result = ISSPACE (c); + break; + case CC_UPPER: + result = ISUPPER (c); + break; + case CC_WORD: + result = (ISALNUM (c) || c == '_'); + break; + case CC_XDIGIT: + result = ISXDIGIT (c); + break; + default: + result = -1; + break; + } + + return result; +} + +static int +is_cclass (c, name) + int c; + const char *name; +{ + enum char_class char_class; + int result; + + char_class = is_valid_cclass (name); + if (char_class == CC_NO_CLASS) + return -1; + + result = cclass_test (c, char_class); + return (result); +} + +/* Now include `sm_loop.c' for single-byte characters. */ +/* The result of FOLD is an `unsigned char' */ +# define FOLD(c) ((flags & FNM_CASEFOLD) \ + ? TOLOWER ((unsigned char)c) \ + : ((unsigned char)c)) + +#if !defined (__CYGWIN__) +# define ISDIRSEP(c) ((c) == '/') +#else +# define ISDIRSEP(c) ((c) == '/' || (c) == '\\') +#endif /* __CYGWIN__ */ +#define PATHSEP(c) (ISDIRSEP(c) || (c) == 0) + +# define PDOT_OR_DOTDOT(s) (s[0] == '.' && (PATHSEP (s[1]) || (s[1] == '.' && PATHSEP (s[2])))) +# define SDOT_OR_DOTDOT(s) (s[0] == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0))) + +#define FCT internal_strmatch +#define GMATCH gmatch +#define COLLSYM collsym +#define PARSE_COLLSYM parse_collsym +#define BRACKMATCH brackmatch +#define PATSCAN glob_patscan +#define STRCOMPARE strcompare +#define EXTMATCH extmatch +#define DEQUOTE_PATHNAME udequote_pathname +#define STRUCT smat_struct +#define STRCHR(S, C) strchr((S), (C)) +#define MEMCHR(S, C, N) memchr((S), (C), (N)) +#define STRCOLL(S1, S2) strcoll((S1), (S2)) +#define STRLEN(S) strlen(S) +#define STRCMP(S1, S2) strcmp((S1), (S2)) +#define RANGECMP(C1, C2, F) rangecmp((C1), (C2), (F)) +#define COLLEQUIV(C1, C2) collequiv((C1), (C2)) +#define CTYPE_T enum char_class +#define IS_CCLASS(C, S) is_cclass((C), (S)) +#include "sm_loop.inc" + +#if HANDLE_MULTIBYTE + +# define CHAR wchar_t +# define U_CHAR wint_t +# define XCHAR wchar_t +# define INT wint_t +# define L(CS) L##CS +# define INVALID WEOF + +# undef STREQ +# undef STREQN +# define STREQ(s1, s2) ((wcscmp (s1, s2) == 0)) +# define STREQN(a, b, n) ((a)[0] == (b)[0] && wcsncmp(a, b, n) == 0) + +extern char *mbsmbchar PARAMS((const char *)); + +#if FNMATCH_EQUIV_FALLBACK +/* Construct a string w1 = "c1" and a pattern w2 = "[[=c2=]]" and pass them + to fnmatch to see if wide characters c1 and c2 collate as members of the + same equivalence class. We can't really do this portably any other way */ +static int +_fnmatch_fallback_wc (c1, c2) + wchar_t c1, c2; /* string char, patchar */ +{ + char w1[MB_LEN_MAX+1]; /* string */ + char w2[MB_LEN_MAX+8]; /* constructed pattern */ + int l1, l2; + + l1 = wctomb (w1, c1); + if (l1 == -1) + return (2); + w1[l1] = '\0'; + + /* reconstruct the pattern */ + w2[0] = w2[1] = '['; + w2[2] = '='; + l2 = wctomb (w2+3, c2); + if (l2 == -1) + return (2); + w2[l2+3] = '='; + w2[l2+4] = w2[l2+5] = ']'; + w2[l2+6] = '\0'; + + return (fnmatch ((const char *)w2, (const char *)w1, 0)); +} +#endif + +static int +charcmp_wc (c1, c2, forcecoll) + wint_t c1, c2; + int forcecoll; +{ + static wchar_t s1[2] = { L' ', L'\0' }; + static wchar_t s2[2] = { L' ', L'\0' }; + int r; + + if (c1 == c2) + return 0; + + if (forcecoll == 0 && glob_asciirange && c1 <= UCHAR_MAX && c2 <= UCHAR_MAX) + return ((int)(c1 - c2)); + + s1[0] = c1; + s2[0] = c2; + + return (wcscoll (s1, s2)); +} + +static int +rangecmp_wc (c1, c2, forcecoll) + wint_t c1, c2; + int forcecoll; +{ + int r; + + r = charcmp_wc (c1, c2, forcecoll); + + /* We impose a total ordering here by returning c1-c2 if charcmp returns 0, + as we do above in the single-byte case. */ + if (r != 0 || forcecoll) + return r; + return ((int)(c1 - c2)); /* impose total ordering */ +} + +/* Returns 1 if wide chars C and EQUIV collate equally in the current locale. */ +static int +collequiv_wc (c, equiv) + wint_t c, equiv; +{ + wchar_t s, p; + + if (charcmp_wc (c, equiv, 1) == 0) + return 1; + +#if FNMATCH_EQUIV_FALLBACK +/* We check explicitly for success (fnmatch returns 0) to avoid problems if + our local definition of FNM_NOMATCH (strmatch.h) doesn't match the + system's (fnmatch.h). We don't care about error return values here. */ + + s = c; + p = equiv; + return (_fnmatch_fallback_wc (s, p) == 0); +#else + return 0; +#endif +} + +/* Helper function for collating symbol. */ +# define _COLLSYM _collwcsym +# define __COLLSYM __collwcsym +# define POSIXCOLL posix_collwcsyms +# include "collsyms.h" + +static wint_t +collwcsym (s, len) + wchar_t *s; + int len; +{ + register struct _collwcsym *csp; + + for (csp = posix_collwcsyms; csp->name; csp++) + { + if (STREQN(csp->name, s, len) && csp->name[len] == L'\0') + return (csp->code); + } + if (len == 1) + return s[0]; + return INVALID; +} + +static int +is_wcclass (wc, name) + wint_t wc; + wchar_t *name; +{ + char *mbs; + mbstate_t state; + size_t mbslength; + wctype_t desc; + int want_word; + + if ((wctype ("ascii") == (wctype_t)0) && (wcscmp (name, L"ascii") == 0)) + { + int c; + + if ((c = wctob (wc)) == EOF) + return 0; + else + return (c <= 0x7F); + } + + want_word = (wcscmp (name, L"word") == 0); + if (want_word) + name = L"alnum"; + + memset (&state, '\0', sizeof (mbstate_t)); + mbs = (char *) malloc (wcslen(name) * MB_CUR_MAX + 1); + if (mbs == 0) + return -1; + mbslength = wcsrtombs (mbs, (const wchar_t **)&name, (wcslen(name) * MB_CUR_MAX + 1), &state); + + if (mbslength == (size_t)-1 || mbslength == (size_t)-2) + { + free (mbs); + return -1; + } + desc = wctype (mbs); + free (mbs); + + if (desc == (wctype_t)0) + return -1; + + if (want_word) + return (iswctype (wc, desc) || wc == L'_'); + else + return (iswctype (wc, desc)); +} + +/* Return 1 if there are no char class [:class:] expressions (degenerate case) + or only posix-specified (C locale supported) char class expressions in + PATTERN. These are the ones where it's safe to punt to the single-byte + code, since wide character support allows locale-defined char classes. + This only uses single-byte code, but is only needed to support multibyte + locales. */ +static int +posix_cclass_only (pattern) + char *pattern; +{ + char *p, *p1; + char cc[16]; /* sufficient for all valid posix char class names */ + enum char_class valid; + + p = pattern; + while (p = strchr (p, '[')) + { + if (p[1] != ':') + { + p++; + continue; + } + p += 2; /* skip past "[:" */ + /* Find end of char class expression */ + for (p1 = p; *p1; p1++) + if (*p1 == ':' && p1[1] == ']') + break; + if (*p1 == 0) /* no char class expression found */ + break; + /* Find char class name and validate it against posix char classes */ + if ((p1 - p) >= sizeof (cc)) + return 0; + bcopy (p, cc, p1 - p); + cc[p1 - p] = '\0'; + valid = is_valid_cclass (cc); + if (valid == CC_NO_CLASS) + return 0; /* found unrecognized char class name */ + + p = p1 + 2; /* found posix char class name */ + } + + return 1; /* no char class names or only posix */ +} + +/* Now include `sm_loop.c' for multibyte characters. */ +#define FOLD(c) ((flags & FNM_CASEFOLD) && iswupper (c) ? towlower (c) : (c)) + +# if !defined (__CYGWIN__) +# define ISDIRSEP(c) ((c) == L'/') +# else +# define ISDIRSEP(c) ((c) == L'/' || (c) == L'\\') +# endif /* __CYGWIN__ */ +# define PATHSEP(c) (ISDIRSEP(c) || (c) == L'\0') + +# define PDOT_OR_DOTDOT(w) (w[0] == L'.' && (PATHSEP(w[1]) || (w[1] == L'.' && PATHSEP(w[2])))) +# define SDOT_OR_DOTDOT(w) (w[0] == L'.' && (w[1] == L'\0' || (w[1] == L'.' && w[2] == L'\0'))) + +#define FCT internal_wstrmatch +#define GMATCH gmatch_wc +#define COLLSYM collwcsym +#define PARSE_COLLSYM parse_collwcsym +#define BRACKMATCH brackmatch_wc +#define PATSCAN glob_patscan_wc +#define STRCOMPARE wscompare +#define EXTMATCH extmatch_wc +#define DEQUOTE_PATHNAME wcdequote_pathname +#define STRUCT wcsmat_struct +#define STRCHR(S, C) wcschr((S), (C)) +#define MEMCHR(S, C, N) wmemchr((S), (C), (N)) +#define STRCOLL(S1, S2) wcscoll((S1), (S2)) +#define STRLEN(S) wcslen(S) +#define STRCMP(S1, S2) wcscmp((S1), (S2)) +#define RANGECMP(C1, C2, F) rangecmp_wc((C1), (C2), (F)) +#define COLLEQUIV(C1, C2) collequiv_wc((C1), (C2)) +#define CTYPE_T enum char_class +#define IS_CCLASS(C, S) is_wcclass((C), (S)) +#include "sm_loop.inc" + +#endif /* HAVE_MULTIBYTE */ + +int +xstrmatch (pattern, string, flags) + char *pattern; + char *string; + int flags; +{ +#if HANDLE_MULTIBYTE + int ret; + size_t n; + wchar_t *wpattern, *wstring; + size_t plen, slen, mplen, mslen; + + if (MB_CUR_MAX == 1) + return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags)); + + if (mbsmbchar (string) == 0 && mbsmbchar (pattern) == 0 && posix_cclass_only (pattern)) + return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags)); + + n = xdupmbstowcs (&wpattern, NULL, pattern); + if (n == (size_t)-1 || n == (size_t)-2) + return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags)); + + n = xdupmbstowcs (&wstring, NULL, string); + if (n == (size_t)-1 || n == (size_t)-2) + { + free (wpattern); + return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags)); + } + + ret = internal_wstrmatch (wpattern, wstring, flags); + + free (wpattern); + free (wstring); + + return ret; +#else + return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags)); +#endif /* !HANDLE_MULTIBYTE */ +} diff --git a/third_party/bash/spell.c b/third_party/bash/spell.c new file mode 100644 index 000000000..2ad89836b --- /dev/null +++ b/third_party/bash/spell.c @@ -0,0 +1,212 @@ +/* spell.c -- spelling correction for pathnames. */ + +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include "bashtypes.h" +#include "posixdir.h" +#include "posixstat.h" +#if defined (HAVE_SYS_PARAM_H) +#include +#endif + +#include + +#include "bashansi.h" +#include "maxpath.h" +#include "stdc.h" + +static int mindist PARAMS((char *, char *, char *)); +static int spdist PARAMS((char *, char *)); + +/* + * `spname' and its helpers are inspired by the code in "The UNIX + * Programming Environment", Kernighan & Pike, Prentice-Hall 1984, + * pages 209 - 213. + */ + +/* + * `spname' -- return a correctly spelled filename + * + * int spname(char * oldname, char * newname) + * Returns: -1 if no reasonable match found + * 0 if exact match found + * 1 if corrected + * Stores corrected name in `newname'. + */ +int +spname(oldname, newname) + char *oldname; + char *newname; +{ + char *op, *np, *p; + char guess[PATH_MAX + 1], best[PATH_MAX + 1]; + + op = oldname; + np = newname; + for (;;) + { + while (*op == '/') /* Skip slashes */ + *np++ = *op++; + *np = '\0'; + + if (*op == '\0') /* Exact or corrected */ + { + /* `.' is rarely the right thing. */ + if (oldname[1] == '\0' && newname[1] == '\0' && + oldname[0] != '.' && newname[0] == '.') + return -1; + return strcmp(oldname, newname) != 0; + } + + /* Copy next component into guess */ + for (p = guess; *op != '/' && *op != '\0'; op++) + if (p < guess + PATH_MAX) + *p++ = *op; + *p = '\0'; + + if (mindist(newname, guess, best) >= 3) + return -1; /* Hopeless */ + + /* + * Add to end of newname + */ + for (p = best; *np = *p++; np++) + ; + } +} + +/* + * Search directory for a guess + */ +static int +mindist(dir, guess, best) + char *dir; + char *guess; + char *best; +{ + DIR *fd; + struct dirent *dp; + int dist, x; + + dist = 3; /* Worst distance */ + if (*dir == '\0') + dir = "."; + + if ((fd = opendir(dir)) == NULL) + return dist; + + while ((dp = readdir(fd)) != NULL) + { + /* + * Look for a better guess. If the new guess is as + * good as the current one, we take it. This way, + * any single character match will be a better match + * than ".". + */ + x = spdist(dp->d_name, guess); + if (x <= dist && x != 3) + { + strcpy(best, dp->d_name); + dist = x; + if (dist == 0) /* Exact match */ + break; + } + } + (void)closedir(fd); + + /* Don't return `.' */ + if (best[0] == '.' && best[1] == '\0') + dist = 3; + return dist; +} + +/* + * `spdist' -- return the "distance" between two names. + * + * int spname(char * oldname, char * newname) + * Returns: 0 if strings are identical + * 1 if two characters are transposed + * 2 if one character is wrong, added or deleted + * 3 otherwise + */ +static int +spdist(cur, new) + char *cur, *new; +{ + while (*cur == *new) + { + if (*cur == '\0') + return 0; /* Exact match */ + cur++; + new++; + } + + if (*cur) + { + if (*new) + { + if (cur[1] && new[1] && cur[0] == new[1] && cur[1] == new[0] && strcmp (cur + 2, new + 2) == 0) + return 1; /* Transposition */ + + if (strcmp (cur + 1, new + 1) == 0) + return 2; /* One character mismatch */ + } + + if (strcmp(&cur[1], &new[0]) == 0) + return 2; /* Extra character */ + } + + if (*new && strcmp(cur, new + 1) == 0) + return 2; /* Missing character */ + + return 3; +} + +char * +dirspell (dirname) + char *dirname; +{ + int n; + char *guess; + + n = (strlen (dirname) * 3 + 1) / 2 + 1; + guess = (char *)malloc (n); + if (guess == 0) + return 0; + + switch (spname (dirname, guess)) + { + case -1: + default: + free (guess); + return (char *)NULL; + case 0: + case 1: + return guess; + } +} diff --git a/third_party/bash/stat-time.h b/third_party/bash/stat-time.h new file mode 100644 index 000000000..e04cc619e --- /dev/null +++ b/third_party/bash/stat-time.h @@ -0,0 +1,214 @@ +/* stat-related time functions. + + Copyright (C) 2005, 2007, 2009-2012 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Paul Eggert. */ + +#ifndef STAT_TIME_H +#define STAT_TIME_H 1 + +#include + +#if defined (TIME_H_DEFINES_STRUCT_TIMESPEC) +# include +#elif defined (SYS_TIME_H_DEFINES_STRUCT_TIMESPEC) +# include +#elif defined (PTHREAD_H_DEFINES_STRUCT_TIMESPEC) +# include +#endif + +#ifndef HAVE_STRUCT_TIMESPEC +struct timespec +{ + time_t tv_sec; + long int tv_nsec; +}; +#endif + +/* STAT_TIMESPEC (ST, ST_XTIM) is the ST_XTIM member for *ST of type + struct timespec, if available. If not, then STAT_TIMESPEC_NS (ST, + ST_XTIM) is the nanosecond component of the ST_XTIM member for *ST, + if available. ST_XTIM can be st_atim, st_ctim, st_mtim, or st_birthtim + for access, status change, data modification, or birth (creation) + time respectively. + + These macros are private to stat-time.h. */ +#if defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC +# ifdef TYPEOF_STRUCT_STAT_ST_ATIM_IS_STRUCT_TIMESPEC +# define STAT_TIMESPEC(st, st_xtim) ((st)->st_xtim) +# else +# define STAT_TIMESPEC_NS(st, st_xtim) ((st)->st_xtim.tv_nsec) +# endif +#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC_TV_NSEC +# define STAT_TIMESPEC(st, st_xtim) ((st)->st_xtim##espec) +#elif defined HAVE_STRUCT_STAT_ST_ATIMENSEC +# define STAT_TIMESPEC_NS(st, st_xtim) ((st)->st_xtim##ensec) +#elif defined HAVE_STRUCT_STAT_ST_ATIM_ST__TIM_TV_NSEC +# define STAT_TIMESPEC_NS(st, st_xtim) ((st)->st_xtim.st__tim.tv_nsec) +#endif + +/* Return the nanosecond component of *ST's access time. */ +static inline long int +get_stat_atime_ns (struct stat const *st) +{ +# if defined STAT_TIMESPEC + return STAT_TIMESPEC (st, st_atim).tv_nsec; +# elif defined STAT_TIMESPEC_NS + return STAT_TIMESPEC_NS (st, st_atim); +# else + return 0; +# endif +} + +/* Return the nanosecond component of *ST's status change time. */ +static inline long int +get_stat_ctime_ns (struct stat const *st) +{ +# if defined STAT_TIMESPEC + return STAT_TIMESPEC (st, st_ctim).tv_nsec; +# elif defined STAT_TIMESPEC_NS + return STAT_TIMESPEC_NS (st, st_ctim); +# else + return 0; +# endif +} + +/* Return the nanosecond component of *ST's data modification time. */ +static inline long int +get_stat_mtime_ns (struct stat const *st) +{ +# if defined STAT_TIMESPEC + return STAT_TIMESPEC (st, st_mtim).tv_nsec; +# elif defined STAT_TIMESPEC_NS + return STAT_TIMESPEC_NS (st, st_mtim); +# else + return 0; +# endif +} + +/* Return the nanosecond component of *ST's birth time. */ +static inline long int +get_stat_birthtime_ns (struct stat const *st) +{ +# if defined HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC + return STAT_TIMESPEC (st, st_birthtim).tv_nsec; +# elif defined HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC + return STAT_TIMESPEC_NS (st, st_birthtim); +# else + /* Avoid a "parameter unused" warning. */ + (void) st; + return 0; +# endif +} + +/* Return *ST's access time. */ +static inline struct timespec +get_stat_atime (struct stat const *st) +{ +#ifdef STAT_TIMESPEC + return STAT_TIMESPEC (st, st_atim); +#else + struct timespec t; + t.tv_sec = st->st_atime; + t.tv_nsec = get_stat_atime_ns (st); + return t; +#endif +} + +/* Return *ST's status change time. */ +static inline struct timespec +get_stat_ctime (struct stat const *st) +{ +#ifdef STAT_TIMESPEC + return STAT_TIMESPEC (st, st_ctim); +#else + struct timespec t; + t.tv_sec = st->st_ctime; + t.tv_nsec = get_stat_ctime_ns (st); + return t; +#endif +} + +/* Return *ST's data modification time. */ +static inline struct timespec +get_stat_mtime (struct stat const *st) +{ +#ifdef STAT_TIMESPEC + return STAT_TIMESPEC (st, st_mtim); +#else + struct timespec t; + t.tv_sec = st->st_mtime; + t.tv_nsec = get_stat_mtime_ns (st); + return t; +#endif +} + +static inline int +timespec_cmp (struct timespec a, struct timespec b) +{ + return (a.tv_sec < b.tv_sec + ? -1 + : (a.tv_sec > b.tv_sec + ? 1 + : (int) (a.tv_nsec - b.tv_nsec))); +} + +/* Return *ST's birth time, if available; otherwise return a value + with tv_sec and tv_nsec both equal to -1. */ +static inline struct timespec +get_stat_birthtime (struct stat const *st) +{ + struct timespec t; + +#if (defined HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC \ + || defined HAVE_STRUCT_STAT_ST_BIRTHTIM_TV_NSEC) + t = STAT_TIMESPEC (st, st_birthtim); +#elif defined HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC + t.tv_sec = st->st_birthtime; + t.tv_nsec = st->st_birthtimensec; +#elif (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ + /* Native Windows platforms (but not Cygwin) put the "file creation + time" in st_ctime (!). See + . */ + t.tv_sec = st->st_ctime; + t.tv_nsec = 0; +#else + /* Birth time is not supported. */ + t.tv_sec = -1; + t.tv_nsec = -1; + /* Avoid a "parameter unused" warning. */ + (void) st; +#endif + +#if (defined HAVE_STRUCT_STAT_ST_BIRTHTIMESPEC_TV_NSEC \ + || defined HAVE_STRUCT_STAT_ST_BIRTHTIM_TV_NSEC \ + || defined HAVE_STRUCT_STAT_ST_BIRTHTIMENSEC) + /* FreeBSD and NetBSD sometimes signal the absence of knowledge by + using zero. Attempt to work around this problem. Alas, this can + report failure even for valid time stamps. Also, NetBSD + sometimes returns junk in the birth time fields; work around this + bug if it is detected. */ + if (! (t.tv_sec && 0 <= t.tv_nsec && t.tv_nsec < 1000000000)) + { + t.tv_sec = -1; + t.tv_nsec = -1; + } +#endif + + return t; +} + +#endif diff --git a/third_party/bash/stdc.h b/third_party/bash/stdc.h new file mode 100644 index 000000000..38516ae5d --- /dev/null +++ b/third_party/bash/stdc.h @@ -0,0 +1,89 @@ +/* stdc.h -- macros to make source compile on both ANSI C and K&R C + compilers. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_STDC_H_) +#define _STDC_H_ + +/* Adapted from BSD /usr/include/sys/cdefs.h. */ + +/* A function can be defined using prototypes and compile on both ANSI C + and traditional C compilers with something like this: + extern char *func PARAMS((char *, char *, int)); */ + +#if !defined (PARAMS) +# if defined (__STDC__) || defined (__GNUC__) || defined (__cplusplus) || defined (PROTOTYPES) +# define PARAMS(protos) protos +# else +# define PARAMS(protos) () +# endif +#endif + +/* Fortify, at least, has trouble with this definition */ +#if defined (HAVE_STRINGIZE) +# define CPP_STRING(x) #x +#else +# define CPP_STRING(x) "x" +#endif + +#if !defined (__STDC__) + +#if defined (__GNUC__) /* gcc with -traditional */ +# if !defined (signed) +# define signed __signed +# endif +# if !defined (volatile) +# define volatile __volatile +# endif +#else /* !__GNUC__ */ +# if !defined (inline) +# define inline +# endif +# if !defined (signed) +# define signed +# endif +# if !defined (volatile) +# define volatile +# endif +#endif /* !__GNUC__ */ + +#endif /* !__STDC__ */ + +#ifndef __attribute__ +# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) +# define __attribute__(x) +# endif +#endif + +/* For those situations when gcc handles inlining a particular function but + other compilers complain. */ +#ifdef __GNUC__ +# define INLINE inline +#else +# define INLINE +#endif + +#if defined (PREFER_STDARG) +# define SH_VA_START(va, arg) va_start(va, arg) +#else +# define SH_VA_START(va, arg) va_start(va) +#endif + +#endif /* !_STDC_H_ */ diff --git a/third_party/bash/stringlib.c b/third_party/bash/stringlib.c new file mode 100644 index 000000000..5f25fffd6 --- /dev/null +++ b/third_party/bash/stringlib.c @@ -0,0 +1,295 @@ +/* stringlib.c - Miscellaneous string functions. */ + +/* Copyright (C) 1996-2009 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include +#include "chartypes.h" + +#include "shell.h" +#include "pathexp.h" + +#include "glob.h" + +#if defined (EXTENDED_GLOB) +# include "strmatch.h" +#endif + +/* **************************************************************** */ +/* */ +/* Functions to manage arrays of strings */ +/* */ +/* **************************************************************** */ + +/* Find STRING in ALIST, a list of string key/int value pairs. If FLAGS + is 1, STRING is treated as a pattern and matched using strmatch. */ +int +find_string_in_alist (string, alist, flags) + char *string; + STRING_INT_ALIST *alist; + int flags; +{ + register int i; + int r; + + for (i = r = 0; alist[i].word; i++) + { +#if defined (EXTENDED_GLOB) + if (flags) + r = strmatch (alist[i].word, string, FNM_EXTMATCH) != FNM_NOMATCH; + else +#endif + r = STREQ (string, alist[i].word); + + if (r) + return (alist[i].token); + } + return -1; +} + +/* Find TOKEN in ALIST, a list of string/int value pairs. Return the + corresponding string. Allocates memory for the returned + string. FLAGS is currently ignored, but reserved. */ +char * +find_token_in_alist (token, alist, flags) + int token; + STRING_INT_ALIST *alist; + int flags; +{ + register int i; + + for (i = 0; alist[i].word; i++) + { + if (alist[i].token == token) + return (savestring (alist[i].word)); + } + return ((char *)NULL); +} + +int +find_index_in_alist (string, alist, flags) + char *string; + STRING_INT_ALIST *alist; + int flags; +{ + register int i; + int r; + + for (i = r = 0; alist[i].word; i++) + { +#if defined (EXTENDED_GLOB) + if (flags) + r = strmatch (alist[i].word, string, FNM_EXTMATCH) != FNM_NOMATCH; + else +#endif + r = STREQ (string, alist[i].word); + + if (r) + return (i); + } + + return -1; +} + +/* **************************************************************** */ +/* */ +/* String Management Functions */ +/* */ +/* **************************************************************** */ + +/* Cons a new string from STRING starting at START and ending at END, + not including END. */ +char * +substring (string, start, end) + const char *string; + int start, end; +{ + register int len; + register char *result; + + len = end - start; + result = (char *)xmalloc (len + 1); + memcpy (result, string + start, len); + result[len] = '\0'; + return (result); +} + +/* Replace occurrences of PAT with REP in STRING. If GLOBAL is non-zero, + replace all occurrences, otherwise replace only the first. + This returns a new string; the caller should free it. */ +char * +strsub (string, pat, rep, global) + char *string, *pat, *rep; + int global; +{ + size_t patlen, replen, templen, tempsize, i; + int repl; + char *temp, *r; + + patlen = strlen (pat); + replen = strlen (rep); + for (temp = (char *)NULL, i = templen = tempsize = 0, repl = 1; string[i]; ) + { + if (repl && STREQN (string + i, pat, patlen)) + { + if (replen) + RESIZE_MALLOCED_BUFFER (temp, templen, replen, tempsize, (replen * 2)); + + for (r = rep; *r; ) /* can rep == "" */ + temp[templen++] = *r++; + + i += patlen ? patlen : 1; /* avoid infinite recursion */ + repl = global != 0; + } + else + { + RESIZE_MALLOCED_BUFFER (temp, templen, 1, tempsize, 16); + temp[templen++] = string[i++]; + } + } + if (temp) + temp[templen] = 0; + else + temp = savestring (string); + return (temp); +} + +/* Replace all instances of C in STRING with TEXT. TEXT may be empty or + NULL. If (FLAGS & 1) is non-zero, we quote the replacement text for + globbing. Backslash may be used to quote C. If (FLAGS & 2) we allow + backslash to escape backslash as well. */ +char * +strcreplace (string, c, text, flags) + char *string; + int c; + const char *text; + int flags; +{ + char *ret, *p, *r, *t; + size_t len, rlen, ind, tlen; + int do_glob, escape_backslash; + + do_glob = flags & 1; + escape_backslash = flags & 2; + + len = STRLEN (text); + rlen = len + strlen (string) + 2; + ret = (char *)xmalloc (rlen); + + for (p = string, r = ret; p && *p; ) + { + if (*p == c) + { + if (len) + { + ind = r - ret; + if (do_glob && (glob_pattern_p (text) || strchr (text, '\\'))) + { + t = quote_globbing_chars (text); + tlen = strlen (t); + RESIZE_MALLOCED_BUFFER (ret, ind, tlen, rlen, rlen); + r = ret + ind; /* in case reallocated */ + strcpy (r, t); + r += tlen; + free (t); + } + else + { + RESIZE_MALLOCED_BUFFER (ret, ind, len, rlen, rlen); + r = ret + ind; /* in case reallocated */ + strcpy (r, text); + r += len; + } + } + p++; + continue; + } + + if (*p == '\\' && p[1] == c) + p++; + else if (escape_backslash && *p == '\\' && p[1] == '\\') + p++; + + ind = r - ret; + RESIZE_MALLOCED_BUFFER (ret, ind, 2, rlen, rlen); + r = ret + ind; /* in case reallocated */ + *r++ = *p++; + } + *r = '\0'; + + return ret; +} + +#ifdef INCLUDE_UNUSED +/* Remove all leading whitespace from STRING. This includes + newlines. STRING should be terminated with a zero. */ +void +strip_leading (string) + char *string; +{ + char *start = string; + + while (*string && (whitespace (*string) || *string == '\n')) + string++; + + if (string != start) + { + int len = strlen (string); + FASTCOPY (string, start, len); + start[len] = '\0'; + } +} +#endif + +/* Remove all trailing whitespace from STRING. This includes + newlines. If NEWLINES_ONLY is non-zero, only trailing newlines + are removed. STRING should be terminated with a zero. */ +void +strip_trailing (string, len, newlines_only) + char *string; + int len; + int newlines_only; +{ + while (len >= 0) + { + if ((newlines_only && string[len] == '\n') || + (!newlines_only && whitespace (string[len]))) + len--; + else + break; + } + string[len + 1] = '\0'; +} + +/* A wrapper for bcopy that can be prototyped in general.h */ +void +xbcopy (s, d, n) + char *s, *d; + int n; +{ + FASTCOPY (s, d, n); +} diff --git a/third_party/bash/stringlist.c b/third_party/bash/stringlist.c new file mode 100644 index 000000000..0ad6177b9 --- /dev/null +++ b/third_party/bash/stringlist.c @@ -0,0 +1,297 @@ +/* stringlist.c - functions to handle a generic `list of strings' structure */ + +/* Copyright (C) 2000-2019 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include "bashansi.h" + +#include "shell.h" + +#ifdef STRDUP +# undef STRDUP +#endif +#define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) + +/* Allocate a new STRINGLIST, with room for N strings. */ + +STRINGLIST * +strlist_create (n) + int n; +{ + STRINGLIST *ret; + register int i; + + ret = (STRINGLIST *)xmalloc (sizeof (STRINGLIST)); + if (n) + { + ret->list = strvec_create (n+1); + ret->list_size = n; + for (i = 0; i < n; i++) + ret->list[i] = (char *)NULL; + } + else + { + ret->list = (char **)NULL; + ret->list_size = 0; + } + ret->list_len = 0; + return ret; +} + +STRINGLIST * +strlist_resize (sl, n) + STRINGLIST *sl; + int n; +{ + register int i; + + if (sl == 0) + return (sl = strlist_create (n)); + + if (n > sl->list_size) + { + sl->list = strvec_resize (sl->list, n + 1); + for (i = sl->list_size; i <= n; i++) + sl->list[i] = (char *)NULL; + sl->list_size = n; + } + return sl; +} + +void +strlist_flush (sl) + STRINGLIST *sl; +{ + if (sl == 0 || sl->list == 0) + return; + strvec_flush (sl->list); + sl->list_len = 0; +} + +void +strlist_dispose (sl) + STRINGLIST *sl; +{ + if (sl == 0) + return; + if (sl->list) + strvec_dispose (sl->list); + free (sl); +} + +int +strlist_remove (sl, s) + STRINGLIST *sl; + char *s; +{ + int r; + + if (sl == 0 || sl->list == 0 || sl->list_len == 0) + return 0; + + r = strvec_remove (sl->list, s); + if (r) + sl->list_len--; + return r; +} + +STRINGLIST * +strlist_copy (sl) + STRINGLIST *sl; +{ + STRINGLIST *new; + register int i; + + if (sl == 0) + return ((STRINGLIST *)0); + new = strlist_create (sl->list_size); + /* I'd like to use strvec_copy, but that doesn't copy everything. */ + if (sl->list) + { + for (i = 0; i < sl->list_size; i++) + new->list[i] = STRDUP (sl->list[i]); + } + new->list_size = sl->list_size; + new->list_len = sl->list_len; + /* just being careful */ + if (new->list) + new->list[new->list_len] = (char *)NULL; + return new; +} + +/* Return a new STRINGLIST with everything from M1 and M2. */ + +STRINGLIST * +strlist_merge (m1, m2) + STRINGLIST *m1, *m2; +{ + STRINGLIST *sl; + int i, n, l1, l2; + + l1 = m1 ? m1->list_len : 0; + l2 = m2 ? m2->list_len : 0; + + sl = strlist_create (l1 + l2 + 1); + for (i = n = 0; i < l1; i++, n++) + sl->list[n] = STRDUP (m1->list[i]); + for (i = 0; i < l2; i++, n++) + sl->list[n] = STRDUP (m2->list[i]); + sl->list_len = n; + sl->list[n] = (char *)NULL; + return (sl); +} + +/* Make STRINGLIST M1 contain everything in M1 and M2. */ +STRINGLIST * +strlist_append (m1, m2) + STRINGLIST *m1, *m2; +{ + register int i, n, len1, len2; + + if (m1 == 0) + return (m2 ? strlist_copy (m2) : (STRINGLIST *)0); + + len1 = m1->list_len; + len2 = m2 ? m2->list_len : 0; + + if (len2) + { + m1 = strlist_resize (m1, len1 + len2 + 1); + for (i = 0, n = len1; i < len2; i++, n++) + m1->list[n] = STRDUP (m2->list[i]); + m1->list[n] = (char *)NULL; + m1->list_len = n; + } + + return m1; +} + +STRINGLIST * +strlist_prefix_suffix (sl, prefix, suffix) + STRINGLIST *sl; + char *prefix, *suffix; +{ + int plen, slen, tlen, llen, i; + char *t; + + if (sl == 0 || sl->list == 0 || sl->list_len == 0) + return sl; + + plen = STRLEN (prefix); + slen = STRLEN (suffix); + + if (plen == 0 && slen == 0) + return (sl); + + for (i = 0; i < sl->list_len; i++) + { + llen = STRLEN (sl->list[i]); + tlen = plen + llen + slen + 1; + t = (char *)xmalloc (tlen + 1); + if (plen) + strcpy (t, prefix); + strcpy (t + plen, sl->list[i]); + if (slen) + strcpy (t + plen + llen, suffix); + free (sl->list[i]); + sl->list[i] = t; + } + + return (sl); +} + +void +strlist_print (sl, prefix) + STRINGLIST *sl; + char *prefix; +{ + register int i; + + if (sl == 0) + return; + for (i = 0; i < sl->list_len; i++) + printf ("%s%s\n", prefix ? prefix : "", sl->list[i]); +} + +void +strlist_walk (sl, func) + STRINGLIST *sl; + sh_strlist_map_func_t *func; +{ + register int i; + + if (sl == 0) + return; + for (i = 0; i < sl->list_len; i++) + if ((*func)(sl->list[i]) < 0) + break; +} + +void +strlist_sort (sl) + STRINGLIST *sl; +{ + if (sl == 0 || sl->list_len == 0 || sl->list == 0) + return; + strvec_sort (sl->list, 0); +} + +STRINGLIST * +strlist_from_word_list (list, alloc, starting_index, ip) + WORD_LIST *list; + int alloc, starting_index, *ip; +{ + STRINGLIST *ret; + int slen, len; + + if (list == 0) + { + if (ip) + *ip = 0; + return ((STRINGLIST *)0); + } + slen = list_length (list); + ret = (STRINGLIST *)xmalloc (sizeof (STRINGLIST)); + ret->list = strvec_from_word_list (list, alloc, starting_index, &len); + ret->list_size = slen + starting_index; + ret->list_len = len; + if (ip) + *ip = len; + return ret; +} + +WORD_LIST * +strlist_to_word_list (sl, alloc, starting_index) + STRINGLIST *sl; + int alloc, starting_index; +{ + WORD_LIST *list; + + if (sl == 0 || sl->list == 0) + return ((WORD_LIST *)NULL); + + list = strvec_to_word_list (sl->list, alloc, starting_index); + return list; +} diff --git a/third_party/bash/stringvec.c b/third_party/bash/stringvec.c new file mode 100644 index 000000000..52693c6e3 --- /dev/null +++ b/third_party/bash/stringvec.c @@ -0,0 +1,272 @@ +/* stringvec.c - functions for managing arrays of strings. */ + +/* Copyright (C) 2000-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include +#include "chartypes.h" + +#include "shell.h" + +/* Allocate an array of strings with room for N members. */ +char ** +strvec_create (n) + int n; +{ + return ((char **)xmalloc ((n) * sizeof (char *))); +} + +/* Allocate an array of strings with room for N members. */ +char ** +strvec_mcreate (n) + int n; +{ + return ((char **)malloc ((n) * sizeof (char *))); +} + +char ** +strvec_resize (array, nsize) + char **array; + int nsize; +{ + return ((char **)xrealloc (array, nsize * sizeof (char *))); +} + +char ** +strvec_mresize (array, nsize) + char **array; + int nsize; +{ + return ((char **)realloc (array, nsize * sizeof (char *))); +} + +/* Return the length of ARRAY, a NULL terminated array of char *. */ +int +strvec_len (array) + char **array; +{ + register int i; + + for (i = 0; array[i]; i++); + return (i); +} + +/* Free the contents of ARRAY, a NULL terminated array of char *. */ +void +strvec_flush (array) + char **array; +{ + register int i; + + if (array == 0) + return; + + for (i = 0; array[i]; i++) + free (array[i]); +} + +void +strvec_dispose (array) + char **array; +{ + if (array == 0) + return; + + strvec_flush (array); + free (array); +} + +int +strvec_remove (array, name) + char **array, *name; +{ + register int i, j; + char *x; + + if (array == 0) + return 0; + + for (i = 0; array[i]; i++) + if (STREQ (name, array[i])) + { + x = array[i]; + for (j = i; array[j]; j++) + array[j] = array[j + 1]; + free (x); + return 1; + } + return 0; +} + +/* Find NAME in ARRAY. Return the index of NAME, or -1 if not present. + ARRAY should be NULL terminated. */ +int +strvec_search (array, name) + char **array, *name; +{ + int i; + + for (i = 0; array[i]; i++) + if (STREQ (name, array[i])) + return (i); + + return (-1); +} + +/* Allocate and return a new copy of ARRAY and its contents. */ +char ** +strvec_copy (array) + char **array; +{ + register int i; + int len; + char **ret; + + len = strvec_len (array); + + ret = (char **)xmalloc ((len + 1) * sizeof (char *)); + for (i = 0; array[i]; i++) + ret[i] = savestring (array[i]); + ret[i] = (char *)NULL; + + return (ret); +} + +/* Comparison routine for use by qsort that conforms to the new Posix + requirements (http://austingroupbugs.net/view.php?id=1070). + + Perform a bytewise comparison if *S1 and *S2 collate equally. */ +int +strvec_posixcmp (s1, s2) + register char **s1, **s2; +{ + int result; + +#if defined (HAVE_STRCOLL) + result = strcoll (*s1, *s2); + if (result != 0) + return result; +#endif + + if ((result = **s1 - **s2) == 0) + result = strcmp (*s1, *s2); + + return (result); +} + +/* Comparison routine for use with qsort() on arrays of strings. Uses + strcoll(3) if available, otherwise it uses strcmp(3). */ +int +strvec_strcmp (s1, s2) + register char **s1, **s2; +{ +#if defined (HAVE_STRCOLL) + return (strcoll (*s1, *s2)); +#else /* !HAVE_STRCOLL */ + int result; + + if ((result = **s1 - **s2) == 0) + result = strcmp (*s1, *s2); + + return (result); +#endif /* !HAVE_STRCOLL */ +} + +/* Sort ARRAY, a null terminated array of pointers to strings. */ +void +strvec_sort (array, posix) + char **array; + int posix; +{ + if (posix) + qsort (array, strvec_len (array), sizeof (char *), (QSFUNC *)strvec_posixcmp); + else + qsort (array, strvec_len (array), sizeof (char *), (QSFUNC *)strvec_strcmp); +} + +/* Cons up a new array of words. The words are taken from LIST, + which is a WORD_LIST *. If ALLOC is true, everything is malloc'ed, + so you should free everything in this array when you are done. + The array is NULL terminated. If IP is non-null, it gets the + number of words in the returned array. STARTING_INDEX says where + to start filling in the returned array; it can be used to reserve + space at the beginning of the array. */ + +char ** +strvec_from_word_list (list, alloc, starting_index, ip) + WORD_LIST *list; + int alloc, starting_index, *ip; +{ + int count; + char **array; + + count = list_length (list); + array = (char **)xmalloc ((1 + count + starting_index) * sizeof (char *)); + + for (count = 0; count < starting_index; count++) + array[count] = (char *)NULL; + for (count = starting_index; list; count++, list = list->next) + array[count] = alloc ? savestring (list->word->word) : list->word->word; + array[count] = (char *)NULL; + + if (ip) + *ip = count; + return (array); +} + +/* Convert an array of strings into the form used internally by the shell. + ALLOC means to allocate new storage for each WORD_DESC in the returned + list rather than copy the values in ARRAY. STARTING_INDEX says where + in ARRAY to begin. */ + +WORD_LIST * +strvec_to_word_list (array, alloc, starting_index) + char **array; + int alloc, starting_index; +{ + WORD_LIST *list; + WORD_DESC *w; + int i, count; + + if (array == 0 || array[0] == 0) + return (WORD_LIST *)NULL; + + for (count = 0; array[count]; count++) + ; + + for (i = starting_index, list = (WORD_LIST *)NULL; i < count; i++) + { + w = make_bare_word (alloc ? array[i] : ""); + if (alloc == 0) + { + free (w->word); + w->word = array[i]; + } + list = make_word_list (w, list); + } + return (REVERSE_LIST (list, WORD_LIST *)); +} diff --git a/third_party/bash/strmatch.c b/third_party/bash/strmatch.c new file mode 100644 index 000000000..0de45eb41 --- /dev/null +++ b/third_party/bash/strmatch.c @@ -0,0 +1,79 @@ +/* strmatch.c -- ksh-like extended pattern matching for the shell and filename + globbing. */ + +/* Copyright (C) 1991-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "stdc.h" +#include "strmatch.h" + +extern int xstrmatch PARAMS((char *, char *, int)); +#if defined (HANDLE_MULTIBYTE) +extern int internal_wstrmatch PARAMS((wchar_t *, wchar_t *, int)); +#endif + +int +strmatch (pattern, string, flags) + char *pattern; + char *string; + int flags; +{ + if (string == 0 || pattern == 0) + return FNM_NOMATCH; + + return (xstrmatch (pattern, string, flags)); +} + +#if defined (HANDLE_MULTIBYTE) +int +wcsmatch (wpattern, wstring, flags) + wchar_t *wpattern; + wchar_t *wstring; + int flags; +{ + if (wstring == 0 || wpattern == 0) + return (FNM_NOMATCH); + + return (internal_wstrmatch (wpattern, wstring, flags)); +} +#endif + +#ifdef TEST +main (c, v) + int c; + char **v; +{ + char *string, *pat; + + string = v[1]; + pat = v[2]; + + if (strmatch (pat, string, 0) == 0) + { + printf ("%s matches %s\n", string, pat); + exit (0); + } + else + { + printf ("%s does not match %s\n", string, pat); + exit (1); + } +} +#endif diff --git a/third_party/bash/strmatch.h b/third_party/bash/strmatch.h new file mode 100644 index 000000000..b1efad907 --- /dev/null +++ b/third_party/bash/strmatch.h @@ -0,0 +1,65 @@ +/* Copyright (C) 1991-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne-Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _STRMATCH_H +#define _STRMATCH_H 1 + +#include "config.h" + +#include "stdc.h" + +/* We #undef these before defining them because some losing systems + (HP-UX A.08.07 for example) define these in . */ +#undef FNM_PATHNAME +#undef FNM_NOESCAPE +#undef FNM_PERIOD + +/* Bits set in the FLAGS argument to `strmatch'. */ + +/* standard flags are like fnmatch(3). */ +#define FNM_PATHNAME (1 << 0) /* No wildcard can ever match `/'. */ +#define FNM_NOESCAPE (1 << 1) /* Backslashes don't quote special chars. */ +#define FNM_PERIOD (1 << 2) /* Leading `.' is matched only explicitly. */ + +/* extended flags not available in most libc fnmatch versions, but we undef + them to avoid any possible warnings. */ +#undef FNM_LEADING_DIR +#undef FNM_CASEFOLD +#undef FNM_EXTMATCH + +#define FNM_LEADING_DIR (1 << 3) /* Ignore `/...' after a match. */ +#define FNM_CASEFOLD (1 << 4) /* Compare without regard to case. */ +#define FNM_EXTMATCH (1 << 5) /* Use ksh-like extended matching. */ + +#define FNM_FIRSTCHAR (1 << 6) /* Match only the first character */ +#define FNM_DOTDOT (1 << 7) /* force `.' and `..' to match explicitly even if FNM_PERIOD not supplied. */ + +/* Value returned by `strmatch' if STRING does not match PATTERN. */ +#undef FNM_NOMATCH + +#define FNM_NOMATCH 1 + +/* Match STRING against the filename pattern PATTERN, + returning zero if it matches, FNM_NOMATCH if not. */ +extern int strmatch PARAMS((char *, char *, int)); + +#if HANDLE_MULTIBYTE +extern int wcsmatch PARAMS((wchar_t *, wchar_t *, int)); +#endif + +#endif /* _STRMATCH_H */ diff --git a/third_party/bash/strtrans.c b/third_party/bash/strtrans.c new file mode 100644 index 000000000..aae07a82b --- /dev/null +++ b/third_party/bash/strtrans.c @@ -0,0 +1,400 @@ +/* strtrans.c - Translate and untranslate strings with ANSI-C escape sequences. */ + +/* Copyright (C) 2000-2015 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" +#include +#include "chartypes.h" + +#include "shell.h" + +#include "shmbchar.h" +#include "shmbutil.h" + +#ifdef ESC +#undef ESC +#endif +#define ESC '\033' /* ASCII */ + +/* Convert STRING by expanding the escape sequences specified by the + ANSI C standard. If SAWC is non-null, recognize `\c' and use that + as a string terminator. If we see \c, set *SAWC to 1 before + returning. LEN is the length of STRING. If (FLAGS&1) is non-zero, + that we're translating a string for `echo -e', and therefore should not + treat a single quote as a character that may be escaped with a backslash. + If (FLAGS&2) is non-zero, we're expanding for the parser and want to + quote CTLESC and CTLNUL with CTLESC. If (flags&4) is non-zero, we want + to remove the backslash before any unrecognized escape sequence. */ +char * +ansicstr (string, len, flags, sawc, rlen) + char *string; + int len, flags, *sawc, *rlen; +{ + int c, temp; + char *ret, *r, *s; + unsigned long v; + size_t clen; + int b, mb_cur_max; +#if defined (HANDLE_MULTIBYTE) + wchar_t wc; +#endif + + if (string == 0 || *string == '\0') + return ((char *)NULL); + + mb_cur_max = MB_CUR_MAX; +#if defined (HANDLE_MULTIBYTE) + temp = 4*len + 4; + if (temp < 12) + temp = 12; /* ensure enough for eventual u32cesc */ + ret = (char *)xmalloc (temp); +#else + ret = (char *)xmalloc (2*len + 1); /* 2*len for possible CTLESC */ +#endif + for (r = ret, s = string; s && *s; ) + { + c = *s++; + if (c != '\\' || *s == '\0') + { + clen = 1; +#if defined (HANDLE_MULTIBYTE) + if ((locale_utf8locale && (c & 0x80)) || + (locale_utf8locale == 0 && mb_cur_max > 0 && is_basic (c) == 0)) + { + clen = mbrtowc (&wc, s - 1, mb_cur_max, 0); + if (MB_INVALIDCH (clen)) + clen = 1; + } +#endif + *r++ = c; + for (--clen; clen > 0; clen--) + *r++ = *s++; + } + else + { + switch (c = *s++) + { +#if defined (__STDC__) + case 'a': c = '\a'; break; + case 'v': c = '\v'; break; +#else + case 'a': c = (int) 0x07; break; + case 'v': c = (int) 0x0B; break; +#endif + case 'b': c = '\b'; break; + case 'e': case 'E': /* ESC -- non-ANSI */ + c = ESC; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': +#if 1 + if (flags & 1) + { + *r++ = '\\'; + break; + } + /*FALLTHROUGH*/ +#endif + case '0': + /* If (FLAGS & 1), we're translating a string for echo -e (or + the equivalent xpg_echo option), so we obey the SUSv3/ + POSIX-2001 requirement and accept 0-3 octal digits after + a leading `0'. */ + temp = 2 + ((flags & 1) && (c == '0')); + for (c -= '0'; ISOCTAL (*s) && temp--; s++) + c = (c * 8) + OCTVALUE (*s); + c &= 0xFF; + break; + case 'x': /* Hex digit -- non-ANSI */ + if ((flags & 2) && *s == '{') + { + flags |= 16; /* internal flag value */ + s++; + } + /* Consume at least two hex characters */ + for (temp = 2, c = 0; ISXDIGIT ((unsigned char)*s) && temp--; s++) + c = (c * 16) + HEXVALUE (*s); + /* DGK says that after a `\x{' ksh93 consumes ISXDIGIT chars + until a non-xdigit or `}', so potentially more than two + chars are consumed. */ + if (flags & 16) + { + for ( ; ISXDIGIT ((unsigned char)*s); s++) + c = (c * 16) + HEXVALUE (*s); + flags &= ~16; + if (*s == '}') + s++; + } + /* \x followed by non-hex digits is passed through unchanged */ + else if (temp == 2) + { + *r++ = '\\'; + c = 'x'; + } + c &= 0xFF; + break; +#if defined (HANDLE_MULTIBYTE) + case 'u': + case 'U': + temp = (c == 'u') ? 4 : 8; /* \uNNNN \UNNNNNNNN */ + for (v = 0; ISXDIGIT ((unsigned char)*s) && temp--; s++) + v = (v * 16) + HEXVALUE (*s); + if (temp == ((c == 'u') ? 4 : 8)) + { + *r++ = '\\'; /* c remains unchanged */ + break; + } + else if (v <= 0x7f) /* <= 0x7f translates directly */ + { + c = v; + break; + } + else + { + temp = u32cconv (v, r); + r += temp; + continue; + } +#endif + case '\\': + break; + case '\'': case '"': case '?': + if (flags & 1) + *r++ = '\\'; + break; + case 'c': + if (sawc) + { + *sawc = 1; + *r = '\0'; + if (rlen) + *rlen = r - ret; + return ret; + } + else if ((flags & 1) == 0 && *s == 0) + ; /* pass \c through */ + else if ((flags & 1) == 0 && (c = *s)) + { + s++; + if ((flags & 2) && c == '\\' && c == *s) + s++; /* Posix requires $'\c\\' do backslash escaping */ + c = TOCTRL(c); + break; + } + /*FALLTHROUGH*/ + default: + if ((flags & 4) == 0) + *r++ = '\\'; + break; + } + if ((flags & 2) && (c == CTLESC || c == CTLNUL)) + *r++ = CTLESC; + *r++ = c; + } + } + *r = '\0'; + if (rlen) + *rlen = r - ret; + return ret; +} + +/* Take a string STR, possibly containing non-printing characters, and turn it + into a $'...' ANSI-C style quoted string. Returns a new string. */ +char * +ansic_quote (str, flags, rlen) + char *str; + int flags, *rlen; +{ + char *r, *ret, *s; + int l, rsize; + unsigned char c; + size_t clen; + int b; +#if defined (HANDLE_MULTIBYTE) + wchar_t wc; +#endif + + if (str == 0 || *str == 0) + return ((char *)0); + + l = strlen (str); + rsize = 4 * l + 4; + r = ret = (char *)xmalloc (rsize); + + *r++ = '$'; + *r++ = '\''; + + for (s = str; c = *s; s++) + { + b = l = 1; /* 1 == add backslash; 0 == no backslash */ + clen = 1; + + switch (c) + { + case ESC: c = 'E'; break; +#ifdef __STDC__ + case '\a': c = 'a'; break; + case '\v': c = 'v'; break; +#else + case 0x07: c = 'a'; break; + case 0x0b: c = 'v'; break; +#endif + + case '\b': c = 'b'; break; + case '\f': c = 'f'; break; + case '\n': c = 'n'; break; + case '\r': c = 'r'; break; + case '\t': c = 't'; break; + case '\\': + case '\'': + break; + default: +#if defined (HANDLE_MULTIBYTE) + b = is_basic (c); + /* XXX - clen comparison to 0 is dicey */ + if ((b == 0 && ((clen = mbrtowc (&wc, s, MB_CUR_MAX, 0)) < 0 || MB_INVALIDCH (clen) || iswprint (wc) == 0)) || + (b == 1 && ISPRINT (c) == 0)) +#else + if (ISPRINT (c) == 0) +#endif + { + *r++ = '\\'; + *r++ = TOCHAR ((c >> 6) & 07); + *r++ = TOCHAR ((c >> 3) & 07); + *r++ = TOCHAR (c & 07); + continue; + } + l = 0; + break; + } + if (b == 0 && clen == 0) + break; + + if (l) + *r++ = '\\'; + + if (clen == 1) + *r++ = c; + else + { + for (b = 0; b < (int)clen; b++) + *r++ = (unsigned char)s[b]; + s += clen - 1; /* -1 because of the increment above */ + } + } + + *r++ = '\''; + *r = '\0'; + if (rlen) + *rlen = r - ret; + return ret; +} + +#if defined (HANDLE_MULTIBYTE) +int +ansic_wshouldquote (string) + const char *string; +{ + const wchar_t *wcs; + wchar_t wcc; + wchar_t *wcstr = NULL; + size_t slen; + + slen = mbstowcs (wcstr, string, 0); + + if (slen == (size_t)-1) + return 1; + + wcstr = (wchar_t *)xmalloc (sizeof (wchar_t) * (slen + 1)); + mbstowcs (wcstr, string, slen + 1); + + for (wcs = wcstr; wcc = *wcs; wcs++) + if (iswprint(wcc) == 0) + { + free (wcstr); + return 1; + } + + free (wcstr); + return 0; +} +#endif + +/* return 1 if we need to quote with $'...' because of non-printing chars. */ +int +ansic_shouldquote (string) + const char *string; +{ + const char *s; + unsigned char c; + + if (string == 0) + return 0; + + for (s = string; c = *s; s++) + { +#if defined (HANDLE_MULTIBYTE) + if (is_basic (c) == 0) + return (ansic_wshouldquote (s)); +#endif + if (ISPRINT (c) == 0) + return 1; + } + + return 0; +} + +/* $'...' ANSI-C expand the portion of STRING between START and END and + return the result. The result cannot be longer than the input string. */ +char * +ansiexpand (string, start, end, lenp) + char *string; + int start, end, *lenp; +{ + char *temp, *t; + int len, tlen; + + temp = (char *)xmalloc (end - start + 1); + for (tlen = 0, len = start; len < end; ) + temp[tlen++] = string[len++]; + temp[tlen] = '\0'; + + if (*temp) + { + t = ansicstr (temp, tlen, 2, (int *)NULL, lenp); + free (temp); + return (t); + } + else + { + if (lenp) + *lenp = 0; + return (temp); + } +} diff --git a/third_party/bash/strvis.c b/third_party/bash/strvis.c new file mode 100644 index 000000000..6d8a5f32f --- /dev/null +++ b/third_party/bash/strvis.c @@ -0,0 +1,154 @@ +/* strvis.c - make unsafe graphical characters in a string visible. */ + +/* Copyright (C) 2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* This is a stripped-down version suitable for the shell's use. */ +#include "config.h" + +#include + +#include "bashansi.h" +#include + +#include "chartypes.h" +#include "bashintl.h" +#include "shmbutil.h" + +#define SAFECHAR(c) ((c) == ' ' || (c) == '\t') + +#ifndef RUBOUT +#define RUBOUT 0x7f +#endif + +#ifndef CTRL_CHAR +#define CTRL_CHAR(c) ((c) < 0x20) +#endif + +#ifndef META_CHAR +#define META_CHAR(c) ((c) > 0x7f && (c) <= UCHAR_MAX) +#endif + +#ifndef UNCTRL +#define UNCTRL(c) (TOUPPER ((c) | 0x40)) +#endif + +#ifndef UNMETA +#define UNMETA(c) ((c) & 0x7f) +#endif + +int +sh_charvis (s, sindp, slen, ret, rindp) + const char *s; + size_t *sindp; + size_t slen; + char *ret; + size_t *rindp; +{ + unsigned char c; + size_t si, ri; + const char *send; + DECLARE_MBSTATE; + + si = *sindp; + ri = *rindp; + c = s[*sindp]; + +#if defined (HANDLE_MULTIBYTE) + send = (locale_mb_cur_max > 1) ? s + slen : 0; +#else + send = 0; +#endif + + if (SAFECHAR (c)) + { + ret[ri++] = c; + si++; + } + else if (c == RUBOUT) + { + ret[ri++] = '^'; + ret[ri++] = '?'; + si++; + } + else if (CTRL_CHAR (c)) + { + ret[ri++] = '^'; + ret[ri++] = UNCTRL (c); + si++; + } +#if defined (HANDLE_MULTIBYTE) + else if (locale_utf8locale && (c & 0x80)) + COPY_CHAR_I (ret, ri, s, send, si); + else if (locale_mb_cur_max > 1 && is_basic (c) == 0) + COPY_CHAR_I (ret, ri, s, send, si); +#endif + else if (META_CHAR (c)) + { + ret[ri++] = 'M'; + ret[ri++] = '-'; + ret[ri++] = UNMETA (c); + si++; + } + else + ret[ri++] = s[si++]; + + *sindp = si; + *rindp = ri; + + return si; +} + +/* Return a new string with `unsafe' non-graphical characters in S rendered + in a visible way. */ +char * +sh_strvis (string) + const char *string; +{ + size_t slen, sind; + char *ret; + size_t retind, retsize; + unsigned char c; + DECLARE_MBSTATE; + + if (string == 0) + return 0; + if (*string == '\0') + { + if ((ret = (char *)malloc (1)) == 0) + return 0; + ret[0] = '\0'; + return ret; + } + + slen = strlen (string); + retsize = 3 * slen + 1; + + ret = (char *)malloc (retsize); + if (ret == 0) + return 0; + + retind = 0; + sind = 0; + + while (string[sind]) + sind = sh_charvis (string, &sind, slen, ret, &retind); + + ret[retind] = '\0'; + return ret; +} diff --git a/third_party/bash/subst.c b/third_party/bash/subst.c new file mode 100644 index 000000000..b682a84ca --- /dev/null +++ b/third_party/bash/subst.c @@ -0,0 +1,13006 @@ +/* subst.c -- The part of the shell that does parameter, command, arithmetic, + and globbing substitutions. */ + +/* ``Have a little faith, there's magic in the night. You ain't a + beauty, but, hey, you're alright.'' */ + +/* Copyright (C) 1987-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include +#include "chartypes.h" +#if defined (HAVE_PWD_H) +# include +#endif +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#define NEED_FPURGE_DECL + +#include "bashansi.h" +#include "posixstat.h" +#include "bashintl.h" + +#include "shell.h" +#include "parser.h" +#include "redir.h" +#include "flags.h" +#include "jobs.h" +#include "execute_cmd.h" +#include "filecntl.h" +#include "trap.h" +#include "pathexp.h" +#include "mailcheck.h" + +#include "shmbutil.h" +#if defined (HAVE_MBSTR_H) && defined (HAVE_MBSCHR) +# include /* mbschr */ +#endif +#include "typemax.h" + +#include "getopt.h" +#include "common.h" + +#include "builtext.h" + +#include "tilde.h" +#include "strmatch.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +/* The size that strings change by. */ +#define DEFAULT_INITIAL_ARRAY_SIZE 112 +#define DEFAULT_ARRAY_SIZE 128 + +/* Variable types. */ +#define VT_VARIABLE 0 +#define VT_POSPARMS 1 +#define VT_ARRAYVAR 2 +#define VT_ARRAYMEMBER 3 +#define VT_ASSOCVAR 4 + +#define VT_STARSUB 128 /* $* or ${array[*]} -- used to split */ + +/* Flags for quoted_strchr */ +#define ST_BACKSL 0x01 +#define ST_CTLESC 0x02 +#define ST_SQUOTE 0x04 /* unused yet */ +#define ST_DQUOTE 0x08 /* unused yet */ + +/* These defs make it easier to use the editor. */ +#define LBRACE '{' +#define RBRACE '}' +#define LPAREN '(' +#define RPAREN ')' +#define LBRACK '[' +#define RBRACK ']' + +#if defined (HANDLE_MULTIBYTE) +#define WLPAREN L'(' +#define WRPAREN L')' +#endif + +#define DOLLAR_AT_STAR(c) ((c) == '@' || (c) == '*') +#define STR_DOLLAR_AT_STAR(s) (DOLLAR_AT_STAR ((s)[0]) && (s)[1] == '\0') + +/* Evaluates to 1 if C is one of the shell's special parameters whose length + can be taken, but is also one of the special expansion characters. */ +#define VALID_SPECIAL_LENGTH_PARAM(c) \ + ((c) == '-' || (c) == '?' || (c) == '#' || (c) == '@') + +/* Evaluates to 1 if C is one of the shell's special parameters for which an + indirect variable reference may be made. */ +#define VALID_INDIR_PARAM(c) \ + ((posixly_correct == 0 && (c) == '#') || (posixly_correct == 0 && (c) == '?') || (c) == '@' || (c) == '*') + +/* Evaluates to 1 if C is one of the OP characters that follows the parameter + in ${parameter[:]OPword}. */ +#define VALID_PARAM_EXPAND_CHAR(c) (sh_syntaxtab[(unsigned char)c] & CSUBSTOP) + +/* Evaluates to 1 if this is one of the shell's special variables. */ +#define SPECIAL_VAR(name, wi) \ + (*name && ((DIGIT (*name) && all_digits (name)) || \ + (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \ + (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1])))) + +/* This can be used by all of the *_extract_* functions that have a similar + structure. It can't just be wrapped in a do...while(0) loop because of + the embedded `break'. The dangling else accommodates a trailing semicolon; + we could also put in a do ; while (0) */ + +#define CHECK_STRING_OVERRUN(oind, ind, len, ch) \ + if (ind >= len) \ + { \ + oind = len; \ + ch = 0; \ + break; \ + } \ + else \ + +/* An expansion function that takes a string and a quoted flag and returns + a WORD_LIST *. Used as the type of the third argument to + expand_string_if_necessary(). */ +typedef WORD_LIST *EXPFUNC PARAMS((char *, int)); + +/* Process ID of the last command executed within command substitution. */ +pid_t last_command_subst_pid = NO_PID; +pid_t current_command_subst_pid = NO_PID; + +/* Variables used to keep track of the characters in IFS. */ +SHELL_VAR *ifs_var; +char *ifs_value; +unsigned char ifs_cmap[UCHAR_MAX + 1]; +int ifs_is_set, ifs_is_null; + +#if defined (HANDLE_MULTIBYTE) +unsigned char ifs_firstc[MB_LEN_MAX]; +size_t ifs_firstc_len; +#else +unsigned char ifs_firstc; +#endif + +/* If non-zero, command substitution inherits the value of errexit option */ +int inherit_errexit = 0; + +/* Sentinel to tell when we are performing variable assignments preceding a + command name and putting them into the environment. Used to make sure + we use the temporary environment when looking up variable values. */ +int assigning_in_environment; + +/* Used to hold a list of variable assignments preceding a command. Global + so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a + SIGCHLD trap and so it can be saved and restored by the trap handlers. */ +WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL; + +/* Tell the expansion functions to not longjmp back to top_level on fatal + errors. Enabled when doing completion and prompt string expansion. */ +int no_longjmp_on_fatal_error = 0; + +/* Non-zero means to allow unmatched globbed filenames to expand to + a null file. */ +int allow_null_glob_expansion; + +/* Non-zero means to throw an error when globbing fails to match anything. */ +int fail_glob_expansion; + +/* If non-zero, perform `&' substitution on the replacement string in the + pattern substitution word expansion. */ +int patsub_replacement = 1; + +/* Extern functions and variables from different files. */ +extern struct fd_bitmap *current_fds_to_close; +extern int wordexp_only; +extern int singlequote_translations; +extern int extended_quote; + +#if defined (JOB_CONTROL) && defined (PROCESS_SUBSTITUTION) +extern PROCESS *last_procsub_child; +#endif + +#if !defined (HAVE_WCSDUP) && defined (HANDLE_MULTIBYTE) +extern wchar_t *wcsdup PARAMS((const wchar_t *)); +#endif + +#if 0 +/* Variables to keep track of which words in an expanded word list (the + output of expand_word_list_internal) are the result of globbing + expansions. GLOB_ARGV_FLAGS is used by execute_cmd.c. + (CURRENTLY UNUSED). */ +char *glob_argv_flags; +static int glob_argv_flags_size; +#endif + +static WORD_LIST *cached_quoted_dollar_at = 0; + +/* Distinguished error values to return from expansion functions */ +static WORD_LIST expand_word_error, expand_word_fatal; +static WORD_DESC expand_wdesc_error, expand_wdesc_fatal; +static char expand_param_error, expand_param_fatal, expand_param_unset; +static char extract_string_error, extract_string_fatal; + +/* Set by expand_word_unsplit and several of the expand_string_XXX functions; + used to inhibit splitting and re-joining $* on $IFS, primarily when doing + assignment statements. The idea is that if we're in a context where this + is set, we're not going to be performing word splitting, so we use the same + rules to expand $* as we would if it appeared within double quotes. */ +static int expand_no_split_dollar_star = 0; + +/* A WORD_LIST of words to be expanded by expand_word_list_internal, + without any leading variable assignments. */ +static WORD_LIST *garglist = (WORD_LIST *)NULL; + +static char *quoted_substring PARAMS((char *, int, int)); +static int quoted_strlen PARAMS((char *)); +static char *quoted_strchr PARAMS((char *, int, int)); + +static char *expand_string_if_necessary PARAMS((char *, int, EXPFUNC *)); +static inline char *expand_string_to_string_internal PARAMS((char *, int, EXPFUNC *)); +static WORD_LIST *call_expand_word_internal PARAMS((WORD_DESC *, int, int, int *, int *)); +static WORD_LIST *expand_string_internal PARAMS((char *, int)); +static WORD_LIST *expand_string_leave_quoted PARAMS((char *, int)); +static WORD_LIST *expand_string_for_rhs PARAMS((char *, int, int, int, int *, int *)); +static WORD_LIST *expand_string_for_pat PARAMS((char *, int, int *, int *)); + +static char *quote_escapes_internal PARAMS((const char *, int)); + +static WORD_LIST *list_quote_escapes PARAMS((WORD_LIST *)); +static WORD_LIST *list_dequote_escapes PARAMS((WORD_LIST *)); + +static char *make_quoted_char PARAMS((int)); +static WORD_LIST *quote_list PARAMS((WORD_LIST *)); + +static int unquoted_substring PARAMS((char *, char *)); +static int unquoted_member PARAMS((int, char *)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *do_compound_assignment PARAMS((char *, char *, int)); +#endif +static int do_assignment_internal PARAMS((const WORD_DESC *, int)); + +static char *string_extract_verbatim PARAMS((char *, size_t, int *, char *, int)); +static char *string_extract PARAMS((char *, int *, char *, int)); +static char *string_extract_double_quoted PARAMS((char *, int *, int)); +static inline char *string_extract_single_quoted PARAMS((char *, int *, int)); +static inline int skip_single_quoted PARAMS((const char *, size_t, int, int)); +static int skip_double_quoted PARAMS((char *, size_t, int, int)); +static char *extract_delimited_string PARAMS((char *, int *, char *, char *, char *, int)); +static char *extract_heredoc_dolbrace_string PARAMS((char *, int *, int, int)); +static char *extract_dollar_brace_string PARAMS((char *, int *, int, int)); +static int skip_matched_pair PARAMS((const char *, int, int, int, int)); + +static char *pos_params PARAMS((char *, int, int, int, int)); + +static unsigned char *mb_getcharlens PARAMS((char *, int)); + +static char *remove_upattern PARAMS((char *, char *, int)); +#if defined (HANDLE_MULTIBYTE) +static wchar_t *remove_wpattern PARAMS((wchar_t *, size_t, wchar_t *, int)); +#endif +static char *remove_pattern PARAMS((char *, char *, int)); + +static int match_upattern PARAMS((char *, char *, int, char **, char **)); +#if defined (HANDLE_MULTIBYTE) +static int match_wpattern PARAMS((wchar_t *, char **, size_t, wchar_t *, int, char **, char **)); +#endif +static int match_pattern PARAMS((char *, char *, int, char **, char **)); +static int getpatspec PARAMS((int, char *)); +static char *getpattern PARAMS((char *, int, int)); +static char *variable_remove_pattern PARAMS((char *, char *, int, int)); +static char *list_remove_pattern PARAMS((WORD_LIST *, char *, int, int, int)); +static char *parameter_list_remove_pattern PARAMS((int, char *, int, int)); +#ifdef ARRAY_VARS +static char *array_remove_pattern PARAMS((SHELL_VAR *, char *, int, int, int)); +#endif +static char *parameter_brace_remove_pattern PARAMS((char *, char *, array_eltstate_t *, char *, int, int, int)); + +static char *string_var_assignment PARAMS((SHELL_VAR *, char *)); +#if defined (ARRAY_VARS) +static char *array_var_assignment PARAMS((SHELL_VAR *, int, int, int)); +#endif +static char *pos_params_assignment PARAMS((WORD_LIST *, int, int)); +static char *string_transform PARAMS((int, SHELL_VAR *, char *)); +static char *list_transform PARAMS((int, SHELL_VAR *, WORD_LIST *, int, int)); +static char *parameter_list_transform PARAMS((int, int, int)); +#if defined ARRAY_VARS +static char *array_transform PARAMS((int, SHELL_VAR *, int, int)); +#endif +static char *parameter_brace_transform PARAMS((char *, char *, array_eltstate_t *, char *, int, int, int, int)); +static int valid_parameter_transform PARAMS((char *)); + +static char *process_substitute PARAMS((char *, int)); + +static char *optimize_cat_file PARAMS((REDIRECT *, int, int, int *)); +static char *read_comsub PARAMS((int, int, int, int *)); + +#ifdef ARRAY_VARS +static arrayind_t array_length_reference PARAMS((char *)); +#endif + +static int valid_brace_expansion_word PARAMS((char *, int)); +static int chk_atstar PARAMS((char *, int, int, int *, int *)); +static int chk_arithsub PARAMS((const char *, int)); + +static WORD_DESC *parameter_brace_expand_word PARAMS((char *, int, int, int, array_eltstate_t *)); +static char *parameter_brace_find_indir PARAMS((char *, int, int, int)); +static WORD_DESC *parameter_brace_expand_indir PARAMS((char *, int, int, int, int *, int *)); +static WORD_DESC *parameter_brace_expand_rhs PARAMS((char *, char *, int, int, int, int *, int *)); +static void parameter_brace_expand_error PARAMS((char *, char *, int)); + +static int valid_length_expression PARAMS((char *)); +static intmax_t parameter_brace_expand_length PARAMS((char *)); + +static char *skiparith PARAMS((char *, int)); +static int verify_substring_values PARAMS((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *)); +static int get_var_and_type PARAMS((char *, char *, array_eltstate_t *, int, int, SHELL_VAR **, char **)); +static char *mb_substring PARAMS((char *, int, int)); +static char *parameter_brace_substring PARAMS((char *, char *, array_eltstate_t *, char *, int, int, int)); + +static int shouldexp_replacement PARAMS((char *)); + +static char *pos_params_pat_subst PARAMS((char *, char *, char *, int)); + +static char *expand_string_for_patsub PARAMS((char *, int)); +static char *parameter_brace_patsub PARAMS((char *, char *, array_eltstate_t *, char *, int, int, int)); + +static char *pos_params_casemod PARAMS((char *, char *, int, int)); +static char *parameter_brace_casemod PARAMS((char *, char *, array_eltstate_t *, int, char *, int, int, int)); + +static WORD_DESC *parameter_brace_expand PARAMS((char *, int *, int, int, int *, int *)); +static WORD_DESC *param_expand PARAMS((char *, int *, int, int *, int *, int *, int *, int)); + +static WORD_LIST *expand_word_internal PARAMS((WORD_DESC *, int, int, int *, int *)); + +static WORD_LIST *word_list_split PARAMS((WORD_LIST *)); + +static void exp_jump_to_top_level PARAMS((int)); + +static WORD_LIST *separate_out_assignments PARAMS((WORD_LIST *)); +static WORD_LIST *glob_expand_word_list PARAMS((WORD_LIST *, int)); +#ifdef BRACE_EXPANSION +static WORD_LIST *brace_expand_word_list PARAMS((WORD_LIST *, int)); +#endif +#if defined (ARRAY_VARS) +static int make_internal_declare PARAMS((char *, char *, char *)); +static void expand_compound_assignment_word PARAMS((WORD_LIST *, int)); +static WORD_LIST *expand_declaration_argument PARAMS((WORD_LIST *, WORD_LIST *)); +#endif +static WORD_LIST *shell_expand_word_list PARAMS((WORD_LIST *, int)); +static WORD_LIST *expand_word_list_internal PARAMS((WORD_LIST *, int)); + +static int do_assignment_statements PARAMS((WORD_LIST *, char *, int)); + +/* **************************************************************** */ +/* */ +/* Utility Functions */ +/* */ +/* **************************************************************** */ + +#if defined (DEBUG) +void +dump_word_flags (flags) + int flags; +{ + int f; + + f = flags; + fprintf (stderr, "%d -> ", f); + if (f & W_ARRAYIND) + { + f &= ~W_ARRAYIND; + fprintf (stderr, "W_ARRAYIND%s", f ? "|" : ""); + } + if (f & W_ASSIGNASSOC) + { + f &= ~W_ASSIGNASSOC; + fprintf (stderr, "W_ASSIGNASSOC%s", f ? "|" : ""); + } + if (f & W_ASSIGNARRAY) + { + f &= ~W_ASSIGNARRAY; + fprintf (stderr, "W_ASSIGNARRAY%s", f ? "|" : ""); + } + if (f & W_SAWQUOTEDNULL) + { + f &= ~W_SAWQUOTEDNULL; + fprintf (stderr, "W_SAWQUOTEDNULL%s", f ? "|" : ""); + } + if (f & W_NOPROCSUB) + { + f &= ~W_NOPROCSUB; + fprintf (stderr, "W_NOPROCSUB%s", f ? "|" : ""); + } + if (f & W_DQUOTE) + { + f &= ~W_DQUOTE; + fprintf (stderr, "W_DQUOTE%s", f ? "|" : ""); + } + if (f & W_HASQUOTEDNULL) + { + f &= ~W_HASQUOTEDNULL; + fprintf (stderr, "W_HASQUOTEDNULL%s", f ? "|" : ""); + } + if (f & W_ASSIGNARG) + { + f &= ~W_ASSIGNARG; + fprintf (stderr, "W_ASSIGNARG%s", f ? "|" : ""); + } + if (f & W_ASSNBLTIN) + { + f &= ~W_ASSNBLTIN; + fprintf (stderr, "W_ASSNBLTIN%s", f ? "|" : ""); + } + if (f & W_ASSNGLOBAL) + { + f &= ~W_ASSNGLOBAL; + fprintf (stderr, "W_ASSNGLOBAL%s", f ? "|" : ""); + } + if (f & W_COMPASSIGN) + { + f &= ~W_COMPASSIGN; + fprintf (stderr, "W_COMPASSIGN%s", f ? "|" : ""); + } + if (f & W_EXPANDRHS) + { + f &= ~W_EXPANDRHS; + fprintf (stderr, "W_EXPANDRHS%s", f ? "|" : ""); + } + if (f & W_NOTILDE) + { + f &= ~W_NOTILDE; + fprintf (stderr, "W_NOTILDE%s", f ? "|" : ""); + } + if (f & W_ASSIGNRHS) + { + f &= ~W_ASSIGNRHS; + fprintf (stderr, "W_ASSIGNRHS%s", f ? "|" : ""); + } + if (f & W_NOASSNTILDE) + { + f &= ~W_NOASSNTILDE; + fprintf (stderr, "W_NOASSNTILDE%s", f ? "|" : ""); + } + if (f & W_NOCOMSUB) + { + f &= ~W_NOCOMSUB; + fprintf (stderr, "W_NOCOMSUB%s", f ? "|" : ""); + } + if (f & W_ARRAYREF) + { + f &= ~W_ARRAYREF; + fprintf (stderr, "W_ARRAYREF%s", f ? "|" : ""); + } + if (f & W_DOLLARAT) + { + f &= ~W_DOLLARAT; + fprintf (stderr, "W_DOLLARAT%s", f ? "|" : ""); + } + if (f & W_TILDEEXP) + { + f &= ~W_TILDEEXP; + fprintf (stderr, "W_TILDEEXP%s", f ? "|" : ""); + } + if (f & W_NOSPLIT2) + { + f &= ~W_NOSPLIT2; + fprintf (stderr, "W_NOSPLIT2%s", f ? "|" : ""); + } + if (f & W_NOSPLIT) + { + f &= ~W_NOSPLIT; + fprintf (stderr, "W_NOSPLIT%s", f ? "|" : ""); + } + if (f & W_NOBRACE) + { + f &= ~W_NOBRACE; + fprintf (stderr, "W_NOBRACE%s", f ? "|" : ""); + } + if (f & W_NOGLOB) + { + f &= ~W_NOGLOB; + fprintf (stderr, "W_NOGLOB%s", f ? "|" : ""); + } + if (f & W_SPLITSPACE) + { + f &= ~W_SPLITSPACE; + fprintf (stderr, "W_SPLITSPACE%s", f ? "|" : ""); + } + if (f & W_ASSIGNMENT) + { + f &= ~W_ASSIGNMENT; + fprintf (stderr, "W_ASSIGNMENT%s", f ? "|" : ""); + } + if (f & W_QUOTED) + { + f &= ~W_QUOTED; + fprintf (stderr, "W_QUOTED%s", f ? "|" : ""); + } + if (f & W_HASDOLLAR) + { + f &= ~W_HASDOLLAR; + fprintf (stderr, "W_HASDOLLAR%s", f ? "|" : ""); + } + if (f & W_COMPLETE) + { + f &= ~W_COMPLETE; + fprintf (stderr, "W_COMPLETE%s", f ? "|" : ""); + } + if (f & W_CHKLOCAL) + { + f &= ~W_CHKLOCAL; + fprintf (stderr, "W_CHKLOCAL%s", f ? "|" : ""); + } + if (f & W_FORCELOCAL) + { + f &= ~W_FORCELOCAL; + fprintf (stderr, "W_FORCELOCAL%s", f ? "|" : ""); + } + + fprintf (stderr, "\n"); + fflush (stderr); +} +#endif + +#ifdef INCLUDE_UNUSED +static char * +quoted_substring (string, start, end) + char *string; + int start, end; +{ + register int len, l; + register char *result, *s, *r; + + len = end - start; + + /* Move to string[start], skipping quoted characters. */ + for (s = string, l = 0; *s && l < start; ) + { + if (*s == CTLESC) + { + s++; + continue; + } + l++; + if (*s == 0) + break; + } + + r = result = (char *)xmalloc (2*len + 1); /* save room for quotes */ + + /* Copy LEN characters, including quote characters. */ + s = string + l; + for (l = 0; l < len; s++) + { + if (*s == CTLESC) + *r++ = *s++; + *r++ = *s; + l++; + if (*s == 0) + break; + } + *r = '\0'; + return result; +} +#endif + +#ifdef INCLUDE_UNUSED +/* Return the length of S, skipping over quoted characters */ +static int +quoted_strlen (s) + char *s; +{ + register char *p; + int i; + + i = 0; + for (p = s; *p; p++) + { + if (*p == CTLESC) + { + p++; + if (*p == 0) + return (i + 1); + } + i++; + } + + return i; +} +#endif + +#ifdef INCLUDE_UNUSED +/* Find the first occurrence of character C in string S, obeying shell + quoting rules. If (FLAGS & ST_BACKSL) is non-zero, backslash-escaped + characters are skipped. If (FLAGS & ST_CTLESC) is non-zero, characters + escaped with CTLESC are skipped. */ +static char * +quoted_strchr (s, c, flags) + char *s; + int c, flags; +{ + register char *p; + + for (p = s; *p; p++) + { + if (((flags & ST_BACKSL) && *p == '\\') + || ((flags & ST_CTLESC) && *p == CTLESC)) + { + p++; + if (*p == '\0') + return ((char *)NULL); + continue; + } + else if (*p == c) + return p; + } + return ((char *)NULL); +} + +/* Return 1 if CHARACTER appears in an unquoted portion of + STRING. Return 0 otherwise. CHARACTER must be a single-byte character. */ +static int +unquoted_member (character, string) + int character; + char *string; +{ + size_t slen; + int sindex, c; + DECLARE_MBSTATE; + + slen = strlen (string); + sindex = 0; + while (c = string[sindex]) + { + if (c == character) + return (1); + + switch (c) + { + default: + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\\': + sindex++; + if (string[sindex]) + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\'': + sindex = skip_single_quoted (string, slen, ++sindex, 0); + break; + + case '"': + sindex = skip_double_quoted (string, slen, ++sindex, 0); + break; + } + } + return (0); +} + +/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */ +static int +unquoted_substring (substr, string) + char *substr, *string; +{ + size_t slen; + int sindex, c, sublen; + DECLARE_MBSTATE; + + if (substr == 0 || *substr == '\0') + return (0); + + slen = strlen (string); + sublen = strlen (substr); + for (sindex = 0; c = string[sindex]; ) + { + if (STREQN (string + sindex, substr, sublen)) + return (1); + + switch (c) + { + case '\\': + sindex++; + if (string[sindex]) + ADVANCE_CHAR (string, slen, sindex); + break; + + case '\'': + sindex = skip_single_quoted (string, slen, ++sindex, 0); + break; + + case '"': + sindex = skip_double_quoted (string, slen, ++sindex, 0); + break; + + default: + ADVANCE_CHAR (string, slen, sindex); + break; + } + } + return (0); +} +#endif + +/* Most of the substitutions must be done in parallel. In order + to avoid using tons of unclear goto's, I have some functions + for manipulating malloc'ed strings. They all take INDX, a + pointer to an integer which is the offset into the string + where manipulation is taking place. They also take SIZE, a + pointer to an integer which is the current length of the + character array for this string. */ + +/* Append SOURCE to TARGET at INDEX. SIZE is the current amount + of space allocated to TARGET. SOURCE can be NULL, in which + case nothing happens. Gets rid of SOURCE by freeing it. + Returns TARGET in case the location has changed. */ +INLINE char * +sub_append_string (source, target, indx, size) + char *source, *target; + size_t *indx; + size_t *size; +{ + if (source) + { + size_t n, srclen; + + srclen = STRLEN (source); + if (srclen >= (*size - *indx)) + { + n = srclen + *indx; + n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE); + target = (char *)xrealloc (target, (*size = n)); + } + + FASTCOPY (source, target + *indx, srclen); + *indx += srclen; + target[*indx] = '\0'; + + free (source); + } + return (target); +} + +#if 0 +/* UNUSED */ +/* Append the textual representation of NUMBER to TARGET. + INDX and SIZE are as in SUB_APPEND_STRING. */ +char * +sub_append_number (number, target, indx, size) + intmax_t number; + char *target; + size_t *indx; + size_t *size; +{ + char *temp; + + temp = itos (number); + return (sub_append_string (temp, target, indx, size)); +} +#endif + +/* Extract a substring from STRING, starting at SINDEX and ending with + one of the characters in CHARLIST. Don't make the ending character + part of the string. Leave SINDEX pointing at the ending character. + Understand about backslashes in the string. If (flags & SX_VARNAME) + is non-zero, and array variables have been compiled into the shell, + everything between a `[' and a corresponding `]' is skipped over. + If (flags & SX_NOALLOC) is non-zero, don't return the substring, just + update SINDEX. If (flags & SX_REQMATCH) is non-zero, the string must + contain a closing character from CHARLIST. */ +static char * +string_extract (string, sindex, charlist, flags) + char *string; + int *sindex; + char *charlist; + int flags; +{ + register int c, i; + int found; + size_t slen; + char *temp; + DECLARE_MBSTATE; + + slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0; + i = *sindex; + found = 0; + while (c = string[i]) + { + if (c == '\\') + { + if (string[i + 1]) + i++; + else + break; + } +#if defined (ARRAY_VARS) + else if ((flags & SX_VARNAME) && c == LBRACK) + { + int ni; + /* If this is an array subscript, skip over it and continue. */ + ni = skipsubscript (string, i, 0); + if (string[ni] == RBRACK) + i = ni; + } +#endif + else if (MEMBER (c, charlist)) + { + found = 1; + break; + } + + ADVANCE_CHAR (string, slen, i); + } + + /* If we had to have a matching delimiter and didn't find one, return an + error and let the caller deal with it. */ + if ((flags & SX_REQMATCH) && found == 0) + { + *sindex = i; + return (&extract_string_error); + } + + temp = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); + *sindex = i; + + return (temp); +} + +/* Extract the contents of STRING as if it is enclosed in double quotes. + SINDEX, when passed in, is the offset of the character immediately + following the opening double quote; on exit, SINDEX is left pointing after + the closing double quote. If STRIPDQ is non-zero, unquoted double + quotes are stripped and the string is terminated by a null byte. + Backslashes between the embedded double quotes are processed. If STRIPDQ + is zero, an unquoted `"' terminates the string. */ +static char * +string_extract_double_quoted (string, sindex, flags) + char *string; + int *sindex, flags; +{ + size_t slen; + char *send; + int j, i, t; + unsigned char c; + char *temp, *ret; /* The new string we return. */ + int pass_next, backquote, si; /* State variables for the machine. */ + int dquote; + int stripdq; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + send = string + slen; + + stripdq = (flags & SX_STRIPDQ); + + pass_next = backquote = dquote = 0; + temp = (char *)xmalloc (1 + slen - *sindex); + + j = 0; + i = *sindex; + while (c = string[i]) + { + /* Process a character that was quoted by a backslash. */ + if (pass_next) + { + /* XXX - take another look at this in light of Interp 221 */ + /* Posix.2 sez: + + ``The backslash shall retain its special meaning as an escape + character only when followed by one of the characters: + $ ` " \ ''. + + If STRIPDQ is zero, we handle the double quotes here and let + expand_word_internal handle the rest. If STRIPDQ is non-zero, + we have already been through one round of backslash stripping, + and want to strip these backslashes only if DQUOTE is non-zero, + indicating that we are inside an embedded double-quoted string. */ + + /* If we are in an embedded quoted string, then don't strip + backslashes before characters for which the backslash + retains its special meaning, but remove backslashes in + front of other characters. If we are not in an + embedded quoted string, don't strip backslashes at all. + This mess is necessary because the string was already + surrounded by double quotes (and sh has some really weird + quoting rules). + The returned string will be run through expansion as if + it were double-quoted. */ + if ((stripdq == 0 && c != '"') || + (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0))) + temp[j++] = '\\'; + pass_next = 0; + +add_one_character: + COPY_CHAR_I (temp, j, string, send, i); + continue; + } + + /* A backslash protects the next character. The code just above + handles preserving the backslash in front of any character but + a double quote. */ + if (c == '\\') + { + pass_next++; + i++; + continue; + } + + /* Inside backquotes, ``the portion of the quoted string from the + initial backquote and the characters up to the next backquote + that is not preceded by a backslash, having escape characters + removed, defines that command''. */ + if (backquote) + { + if (c == '`') + backquote = 0; + temp[j++] = c; /* COPY_CHAR_I? */ + i++; + continue; + } + + if (c == '`') + { + temp[j++] = c; + backquote++; + i++; + continue; + } + + /* Pass everything between `$(' and the matching `)' or a quoted + ${ ... } pair through according to the Posix.2 specification. */ + if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) + { + int free_ret = 1; + + si = i + 2; + if (string[i + 1] == LPAREN) + ret = extract_command_subst (string, &si, (flags & SX_COMPLETE)); + else + ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0); + + temp[j++] = '$'; + temp[j++] = string[i + 1]; + + /* Just paranoia; ret will not be 0 unless no_longjmp_on_fatal_error + is set. */ + if (ret == 0 && no_longjmp_on_fatal_error) + { + free_ret = 0; + ret = string + i + 2; + } + + /* XXX - CHECK_STRING_OVERRUN here? */ + for (t = 0; ret[t]; t++, j++) + temp[j] = ret[t]; + temp[j] = string[si]; + + if (si < i + 2) /* we went back? */ + i += 2; + else if (string[si]) + { + j++; + i = si + 1; + } + else + i = si; + + if (free_ret) + free (ret); + continue; + } + + /* Add any character but a double quote to the quoted string we're + accumulating. */ + if (c != '"') + goto add_one_character; + + /* c == '"' */ + if (stripdq) + { + dquote ^= 1; + i++; + continue; + } + + break; + } + temp[j] = '\0'; + + /* Point to after the closing quote. */ + if (c) + i++; + *sindex = i; + + return (temp); +} + +/* This should really be another option to string_extract_double_quoted. */ +static int +skip_double_quoted (string, slen, sind, flags) + char *string; + size_t slen; + int sind; + int flags; +{ + int c, i; + char *ret; + int pass_next, backquote, si; + DECLARE_MBSTATE; + + pass_next = backquote = 0; + i = sind; + while (c = string[i]) + { + if (pass_next) + { + pass_next = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next++; + i++; + continue; + } + else if (backquote) + { + if (c == '`') + backquote = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '`') + { + backquote++; + i++; + continue; + } + else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) + { + si = i + 2; + if (string[i + 1] == LPAREN) + ret = extract_command_subst (string, &si, SX_NOALLOC|(flags&SX_COMPLETE)); + else + ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC); + + /* These can consume the entire string if they are unterminated */ + CHECK_STRING_OVERRUN (i, si, slen, c); + + i = si + 1; + continue; + } + else if (c != '"') + { + ADVANCE_CHAR (string, slen, i); + continue; + } + else + break; + } + + if (c) + i++; + + return (i); +} + +/* Extract the contents of STRING as if it is enclosed in single quotes. + SINDEX, when passed in, is the offset of the character immediately + following the opening single quote; on exit, SINDEX is left pointing after + the closing single quote. ALLOWESC allows the single quote to be quoted by + a backslash; it's not used yet. */ +static inline char * +string_extract_single_quoted (string, sindex, allowesc) + char *string; + int *sindex; + int allowesc; +{ + register int i; + size_t slen; + char *t; + int pass_next; + DECLARE_MBSTATE; + + /* Don't need slen for ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0; + i = *sindex; + pass_next = 0; + while (string[i]) + { + if (pass_next) + { + pass_next = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + if (allowesc && string[i] == '\\') + pass_next++; + else if (string[i] == '\'') + break; + ADVANCE_CHAR (string, slen, i); + } + + t = substring (string, *sindex, i); + + if (string[i]) + i++; + *sindex = i; + + return (t); +} + +/* Skip over a single-quoted string. We overload the SX_COMPLETE flag to mean + that we are splitting out words for completion and have encountered a $'...' + string, which allows backslash-escaped single quotes. */ +static inline int +skip_single_quoted (string, slen, sind, flags) + const char *string; + size_t slen; + int sind; + int flags; +{ + register int c; + DECLARE_MBSTATE; + + c = sind; + while (string[c] && string[c] != '\'') + { + if ((flags & SX_COMPLETE) && string[c] == '\\' && string[c+1] == '\'' && string[c+2]) + ADVANCE_CHAR (string, slen, c); + ADVANCE_CHAR (string, slen, c); + } + + if (string[c]) + c++; + return c; +} + +/* Just like string_extract, but doesn't hack backslashes or any of + that other stuff. Obeys CTLESC quoting. Used to do splitting on $IFS. */ +static char * +string_extract_verbatim (string, slen, sindex, charlist, flags) + char *string; + size_t slen; + int *sindex; + char *charlist; + int flags; +{ + register int i; +#if defined (HANDLE_MULTIBYTE) + wchar_t *wcharlist; +#endif + int c; + char *temp; + DECLARE_MBSTATE; + + if ((flags & SX_NOCTLESC) && charlist[0] == '\'' && charlist[1] == '\0') + { + temp = string_extract_single_quoted (string, sindex, 0); + --*sindex; /* leave *sindex at separator character */ + return temp; + } + + /* This can never be called with charlist == NULL. If *charlist == NULL, + we can skip the loop and just return a copy of the string, updating + *sindex */ + if (*charlist == 0) + { + temp = string + *sindex; + c = (*sindex == 0) ? slen : STRLEN (temp); + temp = savestring (temp); + *sindex += c; + return temp; + } + + i = *sindex; +#if defined (HANDLE_MULTIBYTE) + wcharlist = 0; +#endif + while (c = string[i]) + { +#if defined (HANDLE_MULTIBYTE) + size_t mblength; +#endif + if ((flags & SX_NOCTLESC) == 0 && c == CTLESC) + { + i += 2; + CHECK_STRING_OVERRUN (i, i, slen, c); + continue; + } + /* Even if flags contains SX_NOCTLESC, we let CTLESC quoting CTLNUL + through, to protect the CTLNULs from later calls to + remove_quoted_nulls. */ + else if ((flags & SX_NOESCCTLNUL) == 0 && c == CTLESC && string[i+1] == CTLNUL) + { + i += 2; + CHECK_STRING_OVERRUN (i, i, slen, c); + continue; + } + +#if defined (HANDLE_MULTIBYTE) + if (locale_utf8locale && slen > i && UTF8_SINGLEBYTE (string[i])) + mblength = (string[i] != 0) ? 1 : 0; + else + mblength = MBLEN (string + i, slen - i); + if (mblength > 1) + { + wchar_t wc; + mblength = mbtowc (&wc, string + i, slen - i); + if (MB_INVALIDCH (mblength)) + { + if (MEMBER (c, charlist)) + break; + } + else + { + if (wcharlist == 0) + { + size_t len; + len = mbstowcs (wcharlist, charlist, 0); + if (len == -1) + len = 0; + wcharlist = (wchar_t *)xmalloc (sizeof (wchar_t) * (len + 1)); + mbstowcs (wcharlist, charlist, len + 1); + } + + if (wcschr (wcharlist, wc)) + break; + } + } + else +#endif + if (MEMBER (c, charlist)) + break; + + ADVANCE_CHAR (string, slen, i); + } + +#if defined (HANDLE_MULTIBYTE) + FREE (wcharlist); +#endif + + temp = substring (string, *sindex, i); + *sindex = i; + + return (temp); +} + +/* Extract the $( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "$(". + Make (SINDEX) get the position of the matching ")". ) + XFLAGS is additional flags to pass to other extraction functions. */ +char * +extract_command_subst (string, sindex, xflags) + char *string; + int *sindex; + int xflags; +{ + char *ret; + + if (string[*sindex] == LPAREN || (xflags & SX_COMPLETE)) + return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/ + else + { + xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0); + ret = xparse_dolparen (string, string+*sindex, sindex, xflags); + return ret; + } +} + +/* Extract the $[ construct in STRING, and return a new string. (]) + Start extracting at (SINDEX) as if we had just seen "$[". + Make (SINDEX) get the position of the matching "]". */ +char * +extract_arithmetic_subst (string, sindex) + char *string; + int *sindex; +{ + return (extract_delimited_string (string, sindex, "$[", "[", "]", 0)); /*]*/ +} + +#if defined (PROCESS_SUBSTITUTION) +/* Extract the <( or >( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "<(". + Make (SINDEX) get the position of the matching ")". */ /*))*/ +char * +extract_process_subst (string, starter, sindex, xflags) + char *string; + char *starter; + int *sindex; + int xflags; +{ +#if 0 + /* XXX - check xflags&SX_COMPLETE here? */ + return (extract_delimited_string (string, sindex, starter, "(", ")", SX_COMMAND)); +#else + xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0); + return (xparse_dolparen (string, string+*sindex, sindex, xflags)); +#endif +} +#endif /* PROCESS_SUBSTITUTION */ + +#if defined (ARRAY_VARS) +/* This can be fooled by unquoted right parens in the passed string. If + each caller verifies that the last character in STRING is a right paren, + we don't even need to call extract_delimited_string. */ +char * +extract_array_assignment_list (string, sindex) + char *string; + int *sindex; +{ + int slen; + char *ret; + + slen = strlen (string); + if (string[slen - 1] == RPAREN) + { + ret = substring (string, *sindex, slen - 1); + *sindex = slen - 1; + return ret; + } + return 0; +} +#endif + +/* Extract and create a new string from the contents of STRING, a + character string delimited with OPENER and CLOSER. SINDEX is + the address of an int describing the current offset in STRING; + it should point to just after the first OPENER found. On exit, + SINDEX gets the position of the last character of the matching CLOSER. + If OPENER is more than a single character, ALT_OPENER, if non-null, + contains a character string that can also match CLOSER and thus + needs to be skipped. */ +static char * +extract_delimited_string (string, sindex, opener, alt_opener, closer, flags) + char *string; + int *sindex; + char *opener, *alt_opener, *closer; + int flags; +{ + int i, c, si; + size_t slen; + char *t, *result; + int pass_character, nesting_level, in_comment; + int len_closer, len_opener, len_alt_opener; + DECLARE_MBSTATE; + + slen = strlen (string + *sindex) + *sindex; + len_opener = STRLEN (opener); + len_alt_opener = STRLEN (alt_opener); + len_closer = STRLEN (closer); + + pass_character = in_comment = 0; + + nesting_level = 1; + i = *sindex; + + while (nesting_level) + { + c = string[i]; + + /* If a recursive call or a call to ADVANCE_CHAR leaves the index beyond + the end of the string, catch it and cut the loop. */ + if (i > slen) + { + i = slen; + c = string[i = slen]; + break; + } + + if (c == 0) + break; + + if (in_comment) + { + if (c == '\n') + in_comment = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + if (pass_character) /* previous char was backslash */ + { + pass_character = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + /* Not exactly right yet; should handle shell metacharacters and + multibyte characters, too. See COMMENT_BEGIN define in parse.y */ + if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1]))) + { + in_comment = 1; + ADVANCE_CHAR (string, slen, i); + continue; + } + + if (c == CTLESC || c == '\\') + { + pass_character++; + i++; + continue; + } + + /* Process a nested command substitution, but only if we're parsing an + arithmetic substitution. */ + if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_command_subst (string, &si, flags|SX_NOALLOC); + CHECK_STRING_OVERRUN (i, si, slen, c); + i = si + 1; + continue; + } + + /* Process a nested OPENER. */ + if (STREQN (string + i, opener, len_opener)) + { + si = i + len_opener; + t = extract_delimited_string (string, &si, opener, alt_opener, closer, flags|SX_NOALLOC); + CHECK_STRING_OVERRUN (i, si, slen, c); + i = si + 1; + continue; + } + + /* Process a nested ALT_OPENER */ + if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener)) + { + si = i + len_alt_opener; + t = extract_delimited_string (string, &si, alt_opener, alt_opener, closer, flags|SX_NOALLOC); + CHECK_STRING_OVERRUN (i, si, slen, c); + i = si + 1; + continue; + } + + /* If the current substring terminates the delimited string, decrement + the nesting level. */ + if (STREQN (string + i, closer, len_closer)) + { + i += len_closer - 1; /* move to last byte of the closer */ + nesting_level--; + if (nesting_level == 0) + break; + } + + /* Pass old-style command substitution through verbatim. */ + if (c == '`') + { + si = i + 1; + t = string_extract (string, &si, "`", flags|SX_NOALLOC); + CHECK_STRING_OVERRUN (i, si, slen, c); + i = si + 1; + continue; + } + + /* Pass single-quoted and double-quoted strings through verbatim. */ + if (c == '\'' || c == '"') + { + si = i + 1; + i = (c == '\'') ? skip_single_quoted (string, slen, si, 0) + : skip_double_quoted (string, slen, si, 0); + continue; + } + + /* move past this character, which was not special. */ + ADVANCE_CHAR (string, slen, i); + } + + if (c == 0 && nesting_level) + { + if (no_longjmp_on_fatal_error == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + report_error (_("bad substitution: no closing `%s' in %s"), closer, string); + exp_jump_to_top_level (DISCARD); + } + else + { + *sindex = i; + return (char *)NULL; + } + } + + si = i - *sindex - len_closer + 1; + if (flags & SX_NOALLOC) + result = (char *)NULL; + else + { + result = (char *)xmalloc (1 + si); + strncpy (result, string + *sindex, si); + result[si] = '\0'; + } + *sindex = i; + + return (result); +} + +/* A simplified version of extract_dollar_brace_string that exists to handle + $'...' and $"..." quoting in here-documents, since the here-document read + path doesn't. It's separate because we don't want to mess with the fast + common path. We already know we're going to allocate and return a new + string and quoted == Q_HERE_DOCUMENT. We might be able to cut it down + some more, but extracting strings and adding them as we go adds complexity. + This needs to match the logic in parse.y:parse_matched_pair so we get + consistent behavior between here-documents and double-quoted strings. */ +static char * +extract_heredoc_dolbrace_string (string, sindex, quoted, flags) + char *string; + int *sindex, quoted, flags; +{ + register int i, c; + size_t slen, tlen, result_index, result_size; + int pass_character, nesting_level, si, dolbrace_state; + char *result, *t, *send; + DECLARE_MBSTATE; + + pass_character = 0; + nesting_level = 1; + slen = strlen (string + *sindex) + *sindex; + send = string + slen; + + result_size = slen; + result_index = 0; + result = xmalloc (result_size + 1); + + /* This function isn't called if this condition is not true initially. */ + dolbrace_state = DOLBRACE_QUOTE; + + i = *sindex; + while (c = string[i]) + { + if (pass_character) + { + pass_character = 0; + RESIZE_MALLOCED_BUFFER (result, result_index, locale_mb_cur_max + 1, result_size, 64); + COPY_CHAR_I (result, result_index, string, send, i); + continue; + } + + /* CTLESCs and backslashes quote the next character. */ + if (c == CTLESC || c == '\\') + { + pass_character++; + RESIZE_MALLOCED_BUFFER (result, result_index, 2, result_size, 64); + result[result_index++] = c; + i++; + continue; + } + + /* The entire reason we have this separate function right here. */ + if (c == '$' && string[i+1] == '\'') + { + char *ttrans; + int ttranslen; + + if ((posixly_correct || extended_quote == 0) && dolbrace_state != DOLBRACE_QUOTE && dolbrace_state != DOLBRACE_QUOTE2) + { + RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, 64); + result[result_index++] = '$'; + result[result_index++] = '\''; + i += 2; + continue; + } + + si = i + 2; + t = string_extract_single_quoted (string, &si, 1); /* XXX */ + CHECK_STRING_OVERRUN (i, si, slen, c); + + tlen = si - i - 2; /* -2 since si is one after the close quote */ + ttrans = ansiexpand (t, 0, tlen, &ttranslen); + free (t); + + /* needed to correctly quote any embedded single quotes. */ + if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2) + { + t = sh_single_quote (ttrans); + tlen = strlen (t); + free (ttrans); + } + else if (extended_quote) /* dolbrace_state == DOLBRACE_PARAM */ + { + /* This matches what parse.y:parse_matched_pair() does */ + t = ttrans; + tlen = strlen (t); + } + + RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 1, result_size, 64); + strncpy (result + result_index, t, tlen); + result_index += tlen; + free (t); + i = si; + continue; + } + +#if defined (TRANSLATABLE_STRINGS) + if (c == '$' && string[i+1] == '"') + { + char *ttrans; + int ttranslen; + + si = i + 2; + t = string_extract_double_quoted (string, &si, flags); /* XXX */ + CHECK_STRING_OVERRUN (i, si, slen, c); + + tlen = si - i - 2; /* -2 since si is one after the close quote */ + ttrans = locale_expand (t, 0, tlen, line_number, &ttranslen); + free (t); + + t = singlequote_translations ? sh_single_quote (ttrans) : sh_mkdoublequoted (ttrans, ttranslen, 0); + tlen = strlen (t); + free (ttrans); + + RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 1, result_size, 64); + strncpy (result + result_index, t, tlen); + result_index += tlen; + free (t); + i = si; + continue; + } +#endif /* TRANSLATABLE_STRINGS */ + + if (c == '$' && string[i+1] == LBRACE) + { + nesting_level++; + RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, 64); + result[result_index++] = c; + result[result_index++] = string[i+1]; + i += 2; + if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2 || dolbrace_state == DOLBRACE_WORD) + dolbrace_state = DOLBRACE_PARAM; + continue; + } + + if (c == RBRACE) + { + nesting_level--; + if (nesting_level == 0) + break; + RESIZE_MALLOCED_BUFFER (result, result_index, 2, result_size, 64); + result[result_index++] = c; + i++; + continue; + } + + /* Pass the contents of old-style command substitutions through + verbatim. */ + if (c == '`') + { + si = i + 1; + t = string_extract (string, &si, "`", flags); /* already know (flags & SX_NOALLOC) == 0) */ + CHECK_STRING_OVERRUN (i, si, slen, c); + + tlen = si - i - 1; + RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 3, result_size, 64); + result[result_index++] = c; + strncpy (result + result_index, t, tlen); + result_index += tlen; + result[result_index++] = string[si]; + free (t); + i = si + 1; + continue; + } + + /* Pass the contents of new-style command substitutions and + arithmetic substitutions through verbatim. */ + if (string[i] == '$' && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_command_subst (string, &si, flags); + CHECK_STRING_OVERRUN (i, si, slen, c); + + tlen = si - i - 1; + RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 4, result_size, 64); + result[result_index++] = c; + result[result_index++] = LPAREN; + strncpy (result + result_index, t, tlen); + result_index += tlen; + result[result_index++] = string[si]; + free (t); + i = si + 1; + continue; + } + +#if defined (PROCESS_SUBSTITUTION) + /* Technically this should only work at the start of a word */ + if ((string[i] == '<' || string[i] == '>') && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_process_subst (string, (string[i] == '<' ? "<(" : ">)"), &si, flags); + CHECK_STRING_OVERRUN (i, si, slen, c); + + tlen = si - i - 1; + RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 4, result_size, 64); + result[result_index++] = c; + result[result_index++] = LPAREN; + strncpy (result + result_index, t, tlen); + result_index += tlen; + result[result_index++] = string[si]; + free (t); + i = si + 1; + continue; + } +#endif + + if (c == '\'' && posixly_correct && shell_compatibility_level > 42 && dolbrace_state != DOLBRACE_QUOTE) + { + COPY_CHAR_I (result, result_index, string, send, i); + continue; + } + + /* Pass the contents of single and double-quoted strings through verbatim. */ + if (c == '"' || c == '\'') + { + si = i + 1; + if (c == '"') + t = string_extract_double_quoted (string, &si, flags); + else + t = string_extract_single_quoted (string, &si, 0); + CHECK_STRING_OVERRUN (i, si, slen, c); + + tlen = si - i - 2; /* -2 since si is one after the close quote */ + RESIZE_MALLOCED_BUFFER (result, result_index, tlen + 3, result_size, 64); + result[result_index++] = c; + strncpy (result + result_index, t, tlen); + result_index += tlen; + result[result_index++] = string[si - 1]; + free (t); + i = si; + continue; + } + + /* copy this character, which was not special. */ + COPY_CHAR_I (result, result_index, string, send, i); + + /* This logic must agree with parse.y:parse_matched_pair, since they + share the same defines. */ + if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE2; /* XXX */ + else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + /* This is intended to handle all of the [:]op expansions and the substring/ + length/pattern removal/pattern substitution expansions. */ + else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0) + dolbrace_state = DOLBRACE_OP; + else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0) + dolbrace_state = DOLBRACE_WORD; + } + + if (c == 0 && nesting_level) + { + free (result); + if (no_longjmp_on_fatal_error == 0) + { /* { */ + last_command_exit_value = EXECUTION_FAILURE; + report_error (_("bad substitution: no closing `%s' in %s"), "}", string); + exp_jump_to_top_level (DISCARD); + } + else + { + *sindex = i; + return ((char *)NULL); + } + } + + *sindex = i; + result[result_index] = '\0'; + + return (result); +} + +/* Extract a parameter expansion expression within ${ and } from STRING. + Obey the Posix.2 rules for finding the ending `}': count braces while + skipping over enclosed quoted strings and command substitutions. + SINDEX is the address of an int describing the current offset in STRING; + it should point to just after the first `{' found. On exit, SINDEX + gets the position of the matching `}'. QUOTED is non-zero if this + occurs inside double quotes. */ +/* XXX -- this is very similar to extract_delimited_string -- XXX */ +static char * +extract_dollar_brace_string (string, sindex, quoted, flags) + char *string; + int *sindex, quoted, flags; +{ + register int i, c; + size_t slen; + int pass_character, nesting_level, si, dolbrace_state; + char *result, *t; + DECLARE_MBSTATE; + + /* The handling of dolbrace_state needs to agree with the code in parse.y: + parse_matched_pair(). The different initial value is to handle the + case where this function is called to parse the word in + ${param op word} (SX_WORD). */ + dolbrace_state = (flags & SX_WORD) ? DOLBRACE_WORD : DOLBRACE_PARAM; + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && (flags & SX_POSIXEXP)) + dolbrace_state = DOLBRACE_QUOTE; + + if (quoted == Q_HERE_DOCUMENT && dolbrace_state == DOLBRACE_QUOTE && (flags & SX_NOALLOC) == 0) + return (extract_heredoc_dolbrace_string (string, sindex, quoted, flags)); + + pass_character = 0; + nesting_level = 1; + slen = strlen (string + *sindex) + *sindex; + + i = *sindex; + while (c = string[i]) + { + if (pass_character) + { + pass_character = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + + /* CTLESCs and backslashes quote the next character. */ + if (c == CTLESC || c == '\\') + { + pass_character++; + i++; + continue; + } + + if (string[i] == '$' && string[i+1] == LBRACE) + { + nesting_level++; + i += 2; + if (dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_WORD) + dolbrace_state = DOLBRACE_PARAM; + continue; + } + + if (c == RBRACE) + { + nesting_level--; + if (nesting_level == 0) + break; + i++; + continue; + } + + /* Pass the contents of old-style command substitutions through + verbatim. */ + if (c == '`') + { + si = i + 1; + t = string_extract (string, &si, "`", flags|SX_NOALLOC); + + CHECK_STRING_OVERRUN (i, si, slen, c); + + i = si + 1; + continue; + } + + /* Pass the contents of new-style command substitutions and + arithmetic substitutions through verbatim. */ + if (string[i] == '$' && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_command_subst (string, &si, flags|SX_NOALLOC); + + CHECK_STRING_OVERRUN (i, si, slen, c); + + i = si + 1; + continue; + } + +#if defined (PROCESS_SUBSTITUTION) + /* Technically this should only work at the start of a word */ + if ((string[i] == '<' || string[i] == '>') && string[i+1] == LPAREN) + { + si = i + 2; + t = extract_process_subst (string, (string[i] == '<' ? "<(" : ">)"), &si, flags|SX_NOALLOC); + + CHECK_STRING_OVERRUN (i, si, slen, c); + + i = si + 1; + continue; + } +#endif + + /* Pass the contents of double-quoted strings through verbatim. */ + if (c == '"') + { + si = i + 1; + i = skip_double_quoted (string, slen, si, 0); + /* skip_XXX_quoted leaves index one past close quote */ + continue; + } + + if (c == '\'') + { +/*itrace("extract_dollar_brace_string: c == single quote flags = %d quoted = %d dolbrace_state = %d", flags, quoted, dolbrace_state);*/ + if (posixly_correct && shell_compatibility_level > 42 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ADVANCE_CHAR (string, slen, i); + else + { + si = i + 1; + i = skip_single_quoted (string, slen, si, 0); + } + + continue; + } + +#if defined (ARRAY_VARS) + if (c == LBRACK && dolbrace_state == DOLBRACE_PARAM) + { + si = skipsubscript (string, i, 0); + CHECK_STRING_OVERRUN (i, si, slen, c); + if (string[si] == RBRACK) + c = string[i = si]; + } +#endif + + /* move past this character, which was not special. */ + ADVANCE_CHAR (string, slen, i); + + /* This logic must agree with parse.y:parse_matched_pair, since they + share the same defines. */ + if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE2; /* XXX */ + else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1) + dolbrace_state = DOLBRACE_QUOTE; + /* This is intended to handle all of the [:]op expansions and the substring/ + length/pattern removal/pattern substitution expansions. */ + else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0) + dolbrace_state = DOLBRACE_OP; + else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0) + dolbrace_state = DOLBRACE_WORD; + } + + if (c == 0 && nesting_level) + { + if (no_longjmp_on_fatal_error == 0) + { /* { */ + last_command_exit_value = EXECUTION_FAILURE; + report_error (_("bad substitution: no closing `%s' in %s"), "}", string); + exp_jump_to_top_level (DISCARD); + } + else + { + *sindex = i; + return ((char *)NULL); + } + } + + result = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i); + *sindex = i; + + return (result); +} + +/* Remove backslashes which are quoting backquotes from STRING. Modifies + STRING, and returns a pointer to it. */ +char * +de_backslash (string) + char *string; +{ + register size_t slen; + register int i, j, prev_i; + DECLARE_MBSTATE; + + slen = strlen (string); + i = j = 0; + + /* Loop copying string[i] to string[j], i >= j. */ + while (i < slen) + { + if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' || + string[i + 1] == '$')) + i++; + prev_i = i; + ADVANCE_CHAR (string, slen, i); + if (j < prev_i) + do string[j++] = string[prev_i++]; while (prev_i < i); + else + j = i; + } + string[j] = '\0'; + + return (string); +} + +#if 0 +/*UNUSED*/ +/* Replace instances of \! in a string with !. */ +void +unquote_bang (string) + char *string; +{ + register int i, j; + register char *temp; + + temp = (char *)xmalloc (1 + strlen (string)); + + for (i = 0, j = 0; (temp[j] = string[i]); i++, j++) + { + if (string[i] == '\\' && string[i + 1] == '!') + { + temp[j] = '!'; + i++; + } + } + strcpy (string, temp); + free (temp); +} +#endif + +#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = oldjmp; return (x); } while (0) + +/* When FLAGS & 2 == 0, this function assumes STRING[I] == OPEN; when + FLAGS & 2 != 0, it assumes STRING[I] points to one character past OPEN; + returns with STRING[RET] == close; used to parse array subscripts. + FLAGS & 1 means not to attempt to skip over matched pairs of quotes or + backquotes, or skip word expansions; it is intended to be used after + expansion has been performed and during final assignment parsing (see + arrayfunc.c:assign_compound_array_list()) or during execution by a builtin + which has already undergone word expansion. */ +static int +skip_matched_pair (string, start, open, close, flags) + const char *string; + int start, open, close, flags; +{ + int i, pass_next, backq, si, c, count, oldjmp; + size_t slen; + char *temp, *ss; + DECLARE_MBSTATE; + + slen = strlen (string + start) + start; + oldjmp = no_longjmp_on_fatal_error; + no_longjmp_on_fatal_error = 1; + + /* Move to the first character after a leading OPEN. If FLAGS&2, we assume + that START already points to that character. If not, we need to skip over + it here. */ + i = (flags & 2) ? start : start + 1; + count = 1; + pass_next = backq = 0; + ss = (char *)string; + while (c = string[i]) + { + if (pass_next) + { + pass_next = 0; + if (c == 0) + CQ_RETURN(i); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if ((flags & 1) == 0 && c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (backq) + { + if (c == '`') + backq = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if ((flags & 1) == 0 && c == '`') + { + backq = 1; + i++; + continue; + } + else if ((flags & 1) == 0 && c == open) + { + count++; + i++; + continue; + } + else if (c == close) + { + count--; + if (count == 0) + break; + i++; + continue; + } + else if ((flags & 1) == 0 && (c == '\'' || c == '"')) + { + i = (c == '\'') ? skip_single_quoted (ss, slen, ++i, 0) + : skip_double_quoted (ss, slen, ++i, 0); + /* no increment, the skip functions increment past the closing quote. */ + } + else if ((flags & 1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE)) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + /* XXX - extract_command_subst here? */ + if (string[i+1] == LPAREN) + temp = extract_delimited_string (ss, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */ + else + temp = extract_dollar_brace_string (ss, &si, 0, SX_NOALLOC); + + CHECK_STRING_OVERRUN (i, si, slen, c); + + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(i); +} + +#if defined (ARRAY_VARS) +/* FLAGS has 1 as a reserved value, since skip_matched_pair uses it for + skipping over quoted strings and taking the first instance of the + closing character. FLAGS & 2 means that STRING[START] points one + character past the open bracket; FLAGS & 2 == 0 means that STRING[START] + points to the open bracket. skip_matched_pair knows how to deal with this. */ +int +skipsubscript (string, start, flags) + const char *string; + int start, flags; +{ + return (skip_matched_pair (string, start, '[', ']', flags)); +} +#endif + +/* Skip characters in STRING until we find a character in DELIMS, and return + the index of that character. START is the index into string at which we + begin. This is similar in spirit to strpbrk, but it returns an index into + STRING and takes a starting index. This little piece of code knows quite + a lot of shell syntax. It's very similar to skip_double_quoted and other + functions of that ilk. */ +int +skip_to_delim (string, start, delims, flags) + char *string; + int start; + char *delims; + int flags; +{ + int i, pass_next, backq, dquote, si, c, oldjmp; + int invert, skipquote, skipcmd, noprocsub, completeflag; + int arithexp, skipcol; + size_t slen; + char *temp, open[3]; + DECLARE_MBSTATE; + + slen = strlen (string + start) + start; + oldjmp = no_longjmp_on_fatal_error; + if (flags & SD_NOJMP) + no_longjmp_on_fatal_error = 1; + invert = (flags & SD_INVERT); + skipcmd = (flags & SD_NOSKIPCMD) == 0; + noprocsub = (flags & SD_NOPROCSUB); + completeflag = (flags & SD_COMPLETE) ? SX_COMPLETE : 0; + + arithexp = (flags & SD_ARITHEXP); + skipcol = 0; + + i = start; + pass_next = backq = dquote = 0; + while (c = string[i]) + { + /* If this is non-zero, we should not let quote characters be delimiters + and the current character is a single or double quote. We should not + test whether or not it's a delimiter until after we skip single- or + double-quoted strings. */ + skipquote = ((flags & SD_NOQUOTEDELIM) && (c == '\'' || c =='"')); + if (pass_next) + { + pass_next = 0; + if (c == 0) + CQ_RETURN(i); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (backq) + { + if (c == '`') + backq = 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '`') + { + backq = 1; + i++; + continue; + } + else if (arithexp && skipcol && c == ':') + { + skipcol--; + i++; + continue; + } + else if (arithexp && c == '?') + { + skipcol++; + i++; + continue; + } + else if (skipquote == 0 && invert == 0 && member (c, delims)) + break; + /* the usual case is to use skip_xxx_quoted, but we don't skip over double + quoted strings when looking for the history expansion character as a + delimiter. */ + /* special case for programmable completion which takes place before + parser converts backslash-escaped single quotes between $'...' to + `regular' single-quoted strings. */ + else if (completeflag && i > 0 && string[i-1] == '$' && c == '\'') + i = skip_single_quoted (string, slen, ++i, SX_COMPLETE); + else if (c == '\'') + i = skip_single_quoted (string, slen, ++i, 0); + else if (c == '"') + i = skip_double_quoted (string, slen, ++i, completeflag); + else if (c == LPAREN && arithexp) + { + si = i + 1; + if (string[si] == '\0') + CQ_RETURN(si); + + temp = extract_delimited_string (string, &si, "(", "(", ")", SX_NOALLOC); /* ) */ + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } + else if (c == '$' && ((skipcmd && string[i+1] == LPAREN) || string[i+1] == LBRACE)) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + if (string[i+1] == LPAREN) + temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND|completeflag); /* ) */ + else + temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC); + CHECK_STRING_OVERRUN (i, si, slen, c); + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } +#if defined (PROCESS_SUBSTITUTION) + else if (skipcmd && noprocsub == 0 && (c == '<' || c == '>') && string[i+1] == LPAREN) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + temp = extract_delimited_string (string, &si, (c == '<') ? "<(" : ">(", "(", ")", SX_COMMAND|SX_NOALLOC); /* )) */ + CHECK_STRING_OVERRUN (i, si, slen, c); + i = si; + if (string[i] == '\0') + break; + i++; + continue; + } +#endif /* PROCESS_SUBSTITUTION */ +#if defined (EXTENDED_GLOB) + else if ((flags & SD_EXTGLOB) && extended_glob && string[i+1] == LPAREN && member (c, "?*+!@")) + { + si = i + 2; + if (string[si] == '\0') + CQ_RETURN(si); + + open[0] = c; + open[1] = LPAREN; + open[2] = '\0'; + temp = extract_delimited_string (string, &si, open, "(", ")", SX_NOALLOC); /* ) */ + + CHECK_STRING_OVERRUN (i, si, slen, c); + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } +#endif + else if ((flags & SD_GLOB) && c == LBRACK) + { + si = i + 1; + if (string[si] == '\0') + CQ_RETURN(si); + + temp = extract_delimited_string (string, &si, "[", "[", "]", SX_NOALLOC); /* ] */ + + i = si; + if (string[i] == '\0') /* don't increment i past EOS in loop */ + break; + i++; + continue; + } + else if ((skipquote || invert) && (member (c, delims) == 0)) + break; + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(i); +} + +#if defined (BANG_HISTORY) +/* Skip to the history expansion character (delims[0]), paying attention to + quoted strings and command and process substitution. This is a stripped- + down version of skip_to_delims. The essential difference is that this + resets the quoting state when starting a command substitution */ +int +skip_to_histexp (string, start, delims, flags) + char *string; + int start; + char *delims; + int flags; +{ + int i, pass_next, backq, dquote, c, oldjmp; + int histexp_comsub, histexp_backq, old_dquote; + size_t slen; + DECLARE_MBSTATE; + + slen = strlen (string + start) + start; + oldjmp = no_longjmp_on_fatal_error; + if (flags & SD_NOJMP) + no_longjmp_on_fatal_error = 1; + + histexp_comsub = histexp_backq = old_dquote = 0; + + i = start; + pass_next = backq = dquote = 0; + while (c = string[i]) + { + if (pass_next) + { + pass_next = 0; + if (c == 0) + CQ_RETURN(i); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (backq && c == '`') + { + backq = 0; + histexp_backq--; + dquote = old_dquote; + i++; + continue; + } + else if (c == '`') + { + backq = 1; + histexp_backq++; + old_dquote = dquote; /* simple - one level for now */ + dquote = 0; + i++; + continue; + } + /* When in double quotes, act as if the double quote is a member of + history_no_expand_chars, like the history library does */ + else if (dquote && c == delims[0] && string[i+1] == '"') + { + i++; + continue; + } + else if (c == delims[0]) + break; + /* the usual case is to use skip_xxx_quoted, but we don't skip over double + quoted strings when looking for the history expansion character as a + delimiter. */ + else if (dquote && c == '\'') + { + i++; + continue; + } + else if (c == '\'') + i = skip_single_quoted (string, slen, ++i, 0); + /* The posixly_correct test makes posix-mode shells allow double quotes + to quote the history expansion character */ + else if (posixly_correct == 0 && c == '"') + { + dquote = 1 - dquote; + i++; + continue; + } + else if (c == '"') + i = skip_double_quoted (string, slen, ++i, 0); +#if defined (PROCESS_SUBSTITUTION) + else if ((c == '$' || c == '<' || c == '>') && string[i+1] == LPAREN && string[i+2] != LPAREN) +#else + else if (c == '$' && string[i+1] == LPAREN && string[i+2] != LPAREN) +#endif + { + if (string[i+2] == '\0') + CQ_RETURN(i+2); + i += 2; + histexp_comsub++; + old_dquote = dquote; + dquote = 0; + } + else if (histexp_comsub && c == RPAREN) + { + histexp_comsub--; + dquote = old_dquote; + i++; + continue; + } + else if (backq) /* placeholder */ + { + ADVANCE_CHAR (string, slen, i); + continue; + } + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(i); +} +#endif /* BANG_HISTORY */ + +#if defined (READLINE) +/* Return 1 if the portion of STRING ending at EINDEX is quoted (there is + an unclosed quoted string), or if the character at EINDEX is quoted + by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various + single and double-quoted string parsing functions should not return an + error if there are unclosed quotes or braces. The characters that this + recognizes need to be the same as the contents of + rl_completer_quote_characters. */ + +int +char_is_quoted (string, eindex) + char *string; + int eindex; +{ + int i, pass_next, c, oldjmp; + size_t slen; + DECLARE_MBSTATE; + + slen = strlen (string); + oldjmp = no_longjmp_on_fatal_error; + no_longjmp_on_fatal_error = 1; + i = pass_next = 0; + + /* If we have an open quoted string from a previous line, see if it's + closed before string[eindex], so we don't interpret that close quote + as starting a new quoted string. */ + if (current_command_line_count > 0 && dstack.delimiter_depth > 0) + { + c = dstack.delimiters[dstack.delimiter_depth - 1]; + if (c == '\'') + i = skip_single_quoted (string, slen, 0, 0); + else if (c == '"') + i = skip_double_quoted (string, slen, 0, SX_COMPLETE); + if (i > eindex) + CQ_RETURN (1); + } + + while (i <= eindex) + { + c = string[i]; + + if (pass_next) + { + pass_next = 0; + if (i >= eindex) /* XXX was if (i >= eindex - 1) */ + CQ_RETURN(1); + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (c == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (c == '$' && string[i+1] == '\'' && string[i+2]) + { + i += 2; + i = skip_single_quoted (string, slen, i, SX_COMPLETE); + if (i > eindex) + CQ_RETURN (i); + } + else if (c == '\'' || c == '"') + { + i = (c == '\'') ? skip_single_quoted (string, slen, ++i, 0) + : skip_double_quoted (string, slen, ++i, SX_COMPLETE); + if (i > eindex) + CQ_RETURN(1); + /* no increment, the skip_xxx functions go one past end */ + } + else + ADVANCE_CHAR (string, slen, i); + } + + CQ_RETURN(0); +} + +int +unclosed_pair (string, eindex, openstr) + char *string; + int eindex; + char *openstr; +{ + int i, pass_next, openc, olen; + size_t slen; + DECLARE_MBSTATE; + + slen = strlen (string); + olen = strlen (openstr); + i = pass_next = openc = 0; + while (i <= eindex) + { + if (pass_next) + { + pass_next = 0; + if (i >= eindex) /* XXX was if (i >= eindex - 1) */ + return 0; + ADVANCE_CHAR (string, slen, i); + continue; + } + else if (string[i] == '\\') + { + pass_next = 1; + i++; + continue; + } + else if (STREQN (string + i, openstr, olen)) + { + openc = 1 - openc; + i += olen; + } + /* XXX - may want to handle $'...' specially here */ + else if (string[i] == '\'' || string[i] == '"') + { + i = (string[i] == '\'') ? skip_single_quoted (string, slen, i, 0) + : skip_double_quoted (string, slen, i, SX_COMPLETE); + if (i > eindex) + return 0; + } + else + ADVANCE_CHAR (string, slen, i); + } + return (openc); +} + +/* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the + individual words. If DELIMS is NULL, the current value of $IFS is used + to split the string, and the function follows the shell field splitting + rules. SENTINEL is an index to look for. NWP, if non-NULL, + gets the number of words in the returned list. CWP, if non-NULL, gets + the index of the word containing SENTINEL. Non-whitespace chars in + DELIMS delimit separate fields. This is used by programmable completion. */ +WORD_LIST * +split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp) + char *string; + int slen; + const char *delims; + int sentinel, flags; + int *nwp, *cwp; +{ + int ts, te, i, nw, cw, ifs_split, dflags; + char *token, *d, *d2; + WORD_LIST *ret, *tl; + + if (string == 0 || *string == '\0') + { + if (nwp) + *nwp = 0; + if (cwp) + *cwp = 0; + return ((WORD_LIST *)NULL); + } + + d = (delims == 0) ? ifs_value : (char *)delims; + ifs_split = delims == 0; + + /* Make d2 the non-whitespace characters in delims */ + d2 = 0; + if (delims) + { + size_t slength; +#if defined (HANDLE_MULTIBYTE) + size_t mblength = 1; +#endif + DECLARE_MBSTATE; + + slength = strlen (delims); + d2 = (char *)xmalloc (slength + 1); + i = ts = 0; + while (delims[i]) + { +#if defined (HANDLE_MULTIBYTE) + mbstate_t state_bak; + state_bak = state; + mblength = MBRLEN (delims + i, slength, &state); + if (MB_INVALIDCH (mblength)) + state = state_bak; + else if (mblength > 1) + { + memcpy (d2 + ts, delims + i, mblength); + ts += mblength; + i += mblength; + slength -= mblength; + continue; + } +#endif + if (whitespace (delims[i]) == 0) + d2[ts++] = delims[i]; + + i++; + slength--; + } + d2[ts] = '\0'; + } + + ret = (WORD_LIST *)NULL; + + /* Remove sequences of whitespace characters at the start of the string, as + long as those characters are delimiters. */ + for (i = 0; member (string[i], d) && spctabnl (string[i]); i++) + ; + if (string[i] == '\0') + { + FREE (d2); + return (ret); + } + + ts = i; + nw = 0; + cw = -1; + dflags = flags|SD_NOJMP; + while (1) + { + te = skip_to_delim (string, ts, d, dflags); + + /* If we have a non-whitespace delimiter character, use it to make a + separate field. This is just about what $IFS splitting does and + is closer to the behavior of the shell parser. */ + if (ts == te && d2 && member (string[ts], d2)) + { + te = ts + 1; + /* If we're using IFS splitting, the non-whitespace delimiter char + and any additional IFS whitespace delimits a field. */ + if (ifs_split) + while (member (string[te], d) && spctabnl (string[te]) && ((flags&SD_NOQUOTEDELIM) == 0 || (string[te] != '\'' && string[te] != '"'))) + te++; + else + while (member (string[te], d2) && ((flags&SD_NOQUOTEDELIM) == 0 || (string[te] != '\'' && string[te] != '"'))) + te++; + } + + token = substring (string, ts, te); + + ret = add_string_to_list (token, ret); /* XXX */ + free (token); + nw++; + + if (sentinel >= ts && sentinel <= te) + cw = nw; + + /* If the cursor is at whitespace just before word start, set the + sentinel word to the current word. */ + if (cwp && cw == -1 && sentinel == ts-1) + cw = nw; + + /* If the cursor is at whitespace between two words, make a new, empty + word, add it before (well, after, since the list is in reverse order) + the word we just added, and set the current word to that one. */ + if (cwp && cw == -1 && sentinel < ts) + { + tl = make_word_list (make_word (""), ret->next); + ret->next = tl; + cw = nw; + nw++; + } + + if (string[te] == 0) + break; + + i = te; + /* XXX - honor SD_NOQUOTEDELIM here */ + while (member (string[i], d) && (ifs_split || spctabnl(string[i])) && ((flags&SD_NOQUOTEDELIM) == 0 || (string[te] != '\'' && string[te] != '"'))) + i++; + + if (string[i]) + ts = i; + else + break; + } + + /* Special case for SENTINEL at the end of STRING. If we haven't found + the word containing SENTINEL yet, and the index we're looking for is at + the end of STRING (or past the end of the previously-found token, + possible if the end of the line is composed solely of IFS whitespace) + add an additional null argument and set the current word pointer to that. */ + if (cwp && cw == -1 && (sentinel >= slen || sentinel >= te)) + { + if (whitespace (string[sentinel - 1])) + { + token = ""; + ret = add_string_to_list (token, ret); + nw++; + } + cw = nw; + } + + if (nwp) + *nwp = nw; + if (cwp) + *cwp = cw; + + FREE (d2); + + return (REVERSE_LIST (ret, WORD_LIST *)); +} +#endif /* READLINE */ + +#if 0 +/* UNUSED */ +/* Extract the name of the variable to bind to from the assignment string. */ +char * +assignment_name (string) + char *string; +{ + int offset; + char *temp; + + offset = assignment (string, 0); + if (offset == 0) + return (char *)NULL; + temp = substring (string, 0, offset); + return (temp); +} +#endif + +/* **************************************************************** */ +/* */ +/* Functions to convert strings to WORD_LISTs and vice versa */ +/* */ +/* **************************************************************** */ + +/* Return a single string of all the words in LIST. SEP is the separator + to put between individual elements of LIST in the output string. */ +char * +string_list_internal (list, sep) + WORD_LIST *list; + char *sep; +{ + register WORD_LIST *t; + char *result, *r; + size_t word_len, sep_len, result_size; + + if (list == 0) + return ((char *)NULL); + + /* Short-circuit quickly if we don't need to separate anything. */ + if (list->next == 0) + return (savestring (list->word->word)); + + /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */ + sep_len = STRLEN (sep); + result_size = 0; + + for (t = list; t; t = t->next) + { + if (t != list) + result_size += sep_len; + result_size += strlen (t->word->word); + } + + r = result = (char *)xmalloc (result_size + 1); + + for (t = list; t; t = t->next) + { + if (t != list && sep_len) + { + if (sep_len > 1) + { + FASTCOPY (sep, r, sep_len); + r += sep_len; + } + else + *r++ = sep[0]; + } + + word_len = strlen (t->word->word); + FASTCOPY (t->word->word, r, word_len); + r += word_len; + } + + *r = '\0'; + return (result); +} + +/* Return a single string of all the words present in LIST, separating + each word with a space. */ +char * +string_list (list) + WORD_LIST *list; +{ + return (string_list_internal (list, " ")); +} + +/* An external interface that can be used by the rest of the shell to + obtain a string containing the first character in $IFS. Handles all + the multibyte complications. If LENP is non-null, it is set to the + length of the returned string. */ +char * +ifs_firstchar (lenp) + int *lenp; +{ + char *ret; + int len; + + ret = xmalloc (MB_LEN_MAX + 1); +#if defined (HANDLE_MULTIBYTE) + if (ifs_firstc_len == 1) + { + ret[0] = ifs_firstc[0]; + ret[1] = '\0'; + len = ret[0] ? 1 : 0; + } + else + { + memcpy (ret, ifs_firstc, ifs_firstc_len); + ret[len = ifs_firstc_len] = '\0'; + } +#else + ret[0] = ifs_firstc; + ret[1] = '\0'; + len = ret[0] ? 0 : 1; +#endif + + if (lenp) + *lenp = len; + + return ret; +} + +/* Return a single string of all the words present in LIST, obeying the + quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the + expansion [of $*] appears within a double quoted string, it expands + to a single field with the value of each parameter separated by the + first character of the IFS variable, or by a if IFS is unset." */ +/* Posix interpretation 888 changes this when IFS is null by specifying + that when unquoted, this expands to separate arguments */ +char * +string_list_dollar_star (list, quoted, flags) + WORD_LIST *list; + int quoted, flags; +{ + char *ret; +#if defined (HANDLE_MULTIBYTE) +# if defined (__GNUC__) + char sep[MB_CUR_MAX + 1]; +# else + char *sep = 0; +# endif +#else + char sep[2]; +#endif + +#if defined (HANDLE_MULTIBYTE) +# if !defined (__GNUC__) + sep = (char *)xmalloc (MB_CUR_MAX + 1); +# endif /* !__GNUC__ */ + if (ifs_firstc_len == 1) + { + sep[0] = ifs_firstc[0]; + sep[1] = '\0'; + } + else + { + memcpy (sep, ifs_firstc, ifs_firstc_len); + sep[ifs_firstc_len] = '\0'; + } +#else + sep[0] = ifs_firstc; + sep[1] = '\0'; +#endif + + ret = string_list_internal (list, sep); +#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__) + free (sep); +#endif + return ret; +} + +/* Turn $@ into a string. If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + is non-zero, the $@ appears within double quotes, and we should quote + the list before converting it into a string. If IFS is unset, and the + word is not quoted, we just need to quote CTLESC and CTLNUL characters + in the words in the list, because the default value of $IFS is + , IFS characters in the words in the list should + also be split. If IFS is null, and the word is not quoted, we need + to quote the words in the list to preserve the positional parameters + exactly. + Valid values for the FLAGS argument are the PF_ flags in command.h, + the only one we care about is PF_ASSIGNRHS. $@ is supposed to expand + to the positional parameters separated by spaces no matter what IFS is + set to if in a context where word splitting is not performed. The only + one that we didn't handle before is assignment statement arguments to + declaration builtins like `declare'. */ +char * +string_list_dollar_at (list, quoted, flags) + WORD_LIST *list; + int quoted; + int flags; +{ + char *ifs, *ret; +#if defined (HANDLE_MULTIBYTE) +# if defined (__GNUC__) + char sep[MB_CUR_MAX + 1]; +# else + char *sep = 0; +# endif /* !__GNUC__ */ +#else + char sep[2]; +#endif + WORD_LIST *tlist; + + /* XXX this could just be ifs = ifs_value; */ + ifs = ifs_var ? value_cell (ifs_var) : (char *)0; + +#if defined (HANDLE_MULTIBYTE) +# if !defined (__GNUC__) + sep = (char *)xmalloc (MB_CUR_MAX + 1); +# endif /* !__GNUC__ */ + /* XXX - testing PF_ASSIGNRHS to make sure positional parameters are + separated with a space even when word splitting will not occur. */ + if (flags & PF_ASSIGNRHS) + { + sep[0] = ' '; + sep[1] = '\0'; + } + else if (ifs && *ifs) + { + if (ifs_firstc_len == 1) + { + sep[0] = ifs_firstc[0]; + sep[1] = '\0'; + } + else + { + memcpy (sep, ifs_firstc, ifs_firstc_len); + sep[ifs_firstc_len] = '\0'; + } + } + else + { + sep[0] = ' '; + sep[1] = '\0'; + } +#else /* !HANDLE_MULTIBYTE */ + /* XXX - PF_ASSIGNRHS means no word splitting, so we want positional + parameters separated by a space. */ + sep[0] = ((flags & PF_ASSIGNRHS) || ifs == 0 || *ifs == 0) ? ' ' : *ifs; + sep[1] = '\0'; +#endif /* !HANDLE_MULTIBYTE */ + + /* XXX -- why call quote_list if ifs == 0? we can get away without doing + it now that quote_escapes quotes spaces */ + tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE)) + ? quote_list (list) + : list_quote_escapes (list); + + ret = string_list_internal (tlist, sep); +#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__) + free (sep); +#endif + return ret; +} + +/* Turn the positional parameters into a string, understanding quoting and + the various subtleties of using the first character of $IFS as the + separator. Calls string_list_dollar_at, string_list_dollar_star, and + string_list as appropriate. */ +/* This needs to fully understand the additional contexts where word + splitting does not occur (W_ASSIGNRHS, etc.) */ +char * +string_list_pos_params (pchar, list, quoted, pflags) + int pchar; + WORD_LIST *list; + int quoted, pflags; +{ + char *ret; + WORD_LIST *tlist; + + if (pchar == '*' && (quoted & Q_DOUBLE_QUOTES)) + { + tlist = quote_list (list); + word_list_remove_quoted_nulls (tlist); + ret = string_list_dollar_star (tlist, 0, 0); + } + else if (pchar == '*' && (quoted & Q_HERE_DOCUMENT)) + { + tlist = quote_list (list); + word_list_remove_quoted_nulls (tlist); + ret = string_list (tlist); + } + else if (pchar == '*' && quoted == 0 && ifs_is_null) /* XXX */ + ret = expand_no_split_dollar_star ? string_list_dollar_star (list, quoted, 0) : string_list_dollar_at (list, quoted, 0); /* Posix interp 888 */ + else if (pchar == '*' && quoted == 0 && (pflags & PF_ASSIGNRHS)) /* XXX */ + ret = expand_no_split_dollar_star ? string_list_dollar_star (list, quoted, 0) : string_list_dollar_at (list, quoted, 0); /* Posix interp 888 */ + else if (pchar == '*') + { + /* Even when unquoted, string_list_dollar_star does the right thing + making sure that the first character of $IFS is used as the + separator. */ + ret = string_list_dollar_star (list, quoted, 0); + } + else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + /* We use string_list_dollar_at, but only if the string is quoted, since + that quotes the escapes if it's not, which we don't want. We could + use string_list (the old code did), but that doesn't do the right + thing if the first character of $IFS is not a space. We use + string_list_dollar_star if the string is unquoted so we make sure that + the elements of $@ are separated by the first character of $IFS for + later splitting. */ + ret = string_list_dollar_at (list, quoted, 0); + else if (pchar == '@' && quoted == 0 && ifs_is_null) /* XXX */ + ret = string_list_dollar_at (list, quoted, 0); /* Posix interp 888 */ + else if (pchar == '@' && quoted == 0 && (pflags & PF_ASSIGNRHS)) + ret = string_list_dollar_at (list, quoted, pflags); /* Posix interp 888 */ + else if (pchar == '@') + ret = string_list_dollar_star (list, quoted, 0); + else + ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list); + + return ret; +} + +/* Return the list of words present in STRING. Separate the string into + words at any of the characters found in SEPARATORS. If QUOTED is + non-zero then word in the list will have its quoted flag set, otherwise + the quoted flag is left as make_word () deemed fit. + + This obeys the P1003.2 word splitting semantics. If `separators' is + exactly , then the splitting algorithm is that of + the Bourne shell, which treats any sequence of characters from `separators' + as a delimiter. If IFS is unset, which results in `separators' being set + to "", no splitting occurs. If separators has some other value, the + following rules are applied (`IFS white space' means zero or more + occurrences of , , or , as long as those characters + are in `separators'): + + 1) IFS white space is ignored at the start and the end of the + string. + 2) Each occurrence of a character in `separators' that is not + IFS white space, along with any adjacent occurrences of + IFS white space delimits a field. + 3) Any nonzero-length sequence of IFS white space delimits a field. + */ + +/* BEWARE! list_string strips null arguments. Don't call it twice and + expect to have "" preserved! */ + +/* This performs word splitting and quoted null character removal on + STRING. */ +#define issep(c) \ + (((separators)[0]) ? ((separators)[1] ? isifs(c) \ + : (c) == (separators)[0]) \ + : 0) + +/* member of the space character class in the current locale */ +#define ifs_whitespace(c) ISSPACE(c) + +/* "adjacent IFS white space" */ +#define ifs_whitesep(c) ((sh_style_split || separators == 0) ? spctabnl (c) \ + : ifs_whitespace (c)) + +WORD_LIST * +list_string (string, separators, quoted) + register char *string, *separators; + int quoted; +{ + WORD_LIST *result; + WORD_DESC *t; + char *current_word, *s; + int sindex, sh_style_split, whitesep, xflags, free_word; + size_t slen; + + if (!string || !*string) + return ((WORD_LIST *)NULL); + + sh_style_split = separators && separators[0] == ' ' && + separators[1] == '\t' && + separators[2] == '\n' && + separators[3] == '\0'; + for (xflags = 0, s = ifs_value; s && *s; s++) + { + if (*s == CTLESC) xflags |= SX_NOCTLESC; + else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL; + } + + slen = 0; + /* Remove sequences of whitespace at the beginning of STRING, as + long as those characters appear in IFS. Do not do this if + STRING is quoted or if there are no separator characters. We use the + Posix definition of whitespace as a member of the space character + class in the current locale. */ +#if 0 + if (!quoted || !separators || !*separators) +#else + /* issep() requires that separators be non-null, and always returns 0 if + separator is the empty string, so don't bother if we get an empty string + for separators. We already returned NULL above if STRING is empty. */ + if (!quoted && separators && *separators) +#endif + { + for (s = string; *s && issep (*s) && ifs_whitespace (*s); s++); + + if (!*s) + return ((WORD_LIST *)NULL); + + string = s; + } + + /* OK, now STRING points to a word that does not begin with white space. + The splitting algorithm is: + extract a word, stopping at a separator + skip sequences of whitespace characters as long as they are separators + This obeys the field splitting rules in Posix.2. */ + slen = STRLEN (string); + for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; ) + { + /* Don't need string length in ADVANCE_CHAR unless multibyte chars are + possible, but need it in string_extract_verbatim for bounds checking */ + current_word = string_extract_verbatim (string, slen, &sindex, separators, xflags); + if (current_word == 0) + break; + + free_word = 1; /* If non-zero, we free current_word */ + + /* If we have a quoted empty string, add a quoted null argument. We + want to preserve the quoted null character iff this is a quoted + empty string; otherwise the quoted null characters are removed + below. */ + if (QUOTED_NULL (current_word)) + { + t = alloc_word_desc (); + t->word = make_quoted_char ('\0'); + t->flags |= W_QUOTED|W_HASQUOTEDNULL; + result = make_word_list (t, result); + } + else if (current_word[0] != '\0') + { + /* If we have something, then add it regardless. However, + perform quoted null character removal on the current word. */ + remove_quoted_nulls (current_word); + + /* We don't want to set the word flags based on the string contents + here -- that's mostly for the parser -- so we just allocate a + WORD_DESC *, assign current_word (noting that we don't want to + free it), and skip all of make_word. */ + t = alloc_word_desc (); + t->word = current_word; + result = make_word_list (t, result); + free_word = 0; + result->word->flags &= ~W_HASQUOTEDNULL; /* just to be sure */ + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + result->word->flags |= W_QUOTED; + /* If removing quoted null characters leaves an empty word, note + that we saw this for the caller to act on. */ + if (current_word == 0 || current_word[0] == '\0') + result->word->flags |= W_SAWQUOTEDNULL; + } + + /* If we're not doing sequences of separators in the traditional + Bourne shell style, then add a quoted null argument. */ + else if (!sh_style_split && !ifs_whitespace (string[sindex])) + { + t = alloc_word_desc (); + t->word = make_quoted_char ('\0'); + t->flags |= W_QUOTED|W_HASQUOTEDNULL; + result = make_word_list (t, result); + } + + if (free_word) + free (current_word); + + /* Note whether or not the separator is IFS whitespace, used later. */ + whitesep = string[sindex] && ifs_whitesep (string[sindex]); + + /* Move past the current separator character. */ + if (string[sindex]) + { + DECLARE_MBSTATE; + ADVANCE_CHAR (string, slen, sindex); + } + + /* Now skip sequences of whitespace characters if they are + in the list of separators. */ + while (string[sindex] && ifs_whitesep (string[sindex]) && issep (string[sindex])) + sindex++; + + /* If the first separator was IFS whitespace and the current character + is a non-whitespace IFS character, it should be part of the current + field delimiter, not a separate delimiter that would result in an + empty field. Look at POSIX.2, 3.6.5, (3)(b). */ + if (string[sindex] && whitesep && issep (string[sindex]) && !ifs_whitesep (string[sindex])) + { + sindex++; + /* An IFS character that is not IFS white space, along with any + adjacent IFS white space, shall delimit a field. (SUSv3) */ + while (string[sindex] && ifs_whitesep (string[sindex]) && isifs (string[sindex])) + sindex++; + } + } + return (REVERSE_LIST (result, WORD_LIST *)); +} + +/* Parse a single word from STRING, using SEPARATORS to separate fields. + ENDPTR is set to the first character after the word. This is used by + the `read' builtin. + + This is never called with SEPARATORS != $IFS, and takes advantage of that. + + XXX - this function is very similar to list_string; they should be + combined - XXX */ + +/* character is in $IFS */ +#define islocalsep(c) (local_cmap[(unsigned char)(c)] != 0) + +char * +get_word_from_string (stringp, separators, endptr) + char **stringp, *separators, **endptr; +{ + register char *s; + char *current_word; + int sindex, sh_style_split, whitesep, xflags; + unsigned char local_cmap[UCHAR_MAX+1]; /* really only need single-byte chars here */ + size_t slen; + + if (!stringp || !*stringp || !**stringp) + return ((char *)NULL); + + sh_style_split = separators && separators[0] == ' ' && + separators[1] == '\t' && + separators[2] == '\n' && + separators[3] == '\0'; + memset (local_cmap, '\0', sizeof (local_cmap)); + for (xflags = 0, s = separators; s && *s; s++) + { + if (*s == CTLESC) xflags |= SX_NOCTLESC; + if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL; + local_cmap[(unsigned char)*s] = 1; /* local charmap of separators */ + } + + s = *stringp; + slen = 0; + + /* Remove sequences of whitespace at the beginning of STRING, as + long as those characters appear in SEPARATORS. This happens if + SEPARATORS == $' \t\n' or if IFS is unset. */ + if (sh_style_split || separators == 0) + for (; *s && spctabnl (*s) && islocalsep (*s); s++); + else + for (; *s && ifs_whitespace (*s) && islocalsep (*s); s++); + + /* If the string is nothing but whitespace, update it and return. */ + if (!*s) + { + *stringp = s; + if (endptr) + *endptr = s; + return ((char *)NULL); + } + + /* OK, S points to a word that does not begin with white space. + Now extract a word, stopping at a separator, save a pointer to + the first character after the word, then skip sequences of spc, + tab, or nl as long as they are separators. + + This obeys the field splitting rules in Posix.2. */ + sindex = 0; + /* Don't need string length in ADVANCE_CHAR unless multibyte chars are + possible, but need it in string_extract_verbatim for bounds checking */ + slen = STRLEN (s); + current_word = string_extract_verbatim (s, slen, &sindex, separators, xflags); + + /* Set ENDPTR to the first character after the end of the word. */ + if (endptr) + *endptr = s + sindex; + + /* Note whether or not the separator is IFS whitespace, used later. */ + whitesep = s[sindex] && ifs_whitesep (s[sindex]); + + /* Move past the current separator character. */ + if (s[sindex]) + { + DECLARE_MBSTATE; + ADVANCE_CHAR (s, slen, sindex); + } + + /* Now skip sequences of space, tab, or newline characters if they are + in the list of separators. */ + while (s[sindex] && spctabnl (s[sindex]) && islocalsep (s[sindex])) + sindex++; + + /* If the first separator was IFS whitespace and the current character is + a non-whitespace IFS character, it should be part of the current field + delimiter, not a separate delimiter that would result in an empty field. + Look at POSIX.2, 3.6.5, (3)(b). */ + if (s[sindex] && whitesep && islocalsep (s[sindex]) && !ifs_whitesep (s[sindex])) + { + sindex++; + /* An IFS character that is not IFS white space, along with any adjacent + IFS white space, shall delimit a field. */ + while (s[sindex] && ifs_whitesep (s[sindex]) && islocalsep(s[sindex])) + sindex++; + } + + /* Update STRING to point to the next field. */ + *stringp = s + sindex; + return (current_word); +} + +/* Remove IFS white space at the end of STRING. Start at the end + of the string and walk backwards until the beginning of the string + or we find a character that's not IFS white space and not CTLESC. + Only let CTLESC escape a white space character if SAW_ESCAPE is + non-zero. */ +char * +strip_trailing_ifs_whitespace (string, separators, saw_escape) + char *string, *separators; + int saw_escape; +{ + char *s; + + s = string + STRLEN (string) - 1; + while (s > string && ((spctabnl (*s) && isifs (*s)) || + (saw_escape && *s == CTLESC && spctabnl (s[1])))) + s--; + *++s = '\0'; + return string; +} + +#if 0 +/* UNUSED */ +/* Split STRING into words at whitespace. Obeys shell-style quoting with + backslashes, single and double quotes. */ +WORD_LIST * +list_string_with_quotes (string) + char *string; +{ + WORD_LIST *list; + char *token, *s; + size_t s_len; + int c, i, tokstart, len; + + for (s = string; s && *s && spctabnl (*s); s++) + ; + if (s == 0 || *s == 0) + return ((WORD_LIST *)NULL); + + s_len = strlen (s); + tokstart = i = 0; + list = (WORD_LIST *)NULL; + while (1) + { + c = s[i]; + if (c == '\\') + { + i++; + if (s[i]) + i++; + } + else if (c == '\'') + i = skip_single_quoted (s, s_len, ++i, 0); + else if (c == '"') + i = skip_double_quoted (s, s_len, ++i, 0); + else if (c == 0 || spctabnl (c)) + { + /* We have found the end of a token. Make a word out of it and + add it to the word list. */ + token = substring (s, tokstart, i); + list = add_string_to_list (token, list); + free (token); + while (spctabnl (s[i])) + i++; + if (s[i]) + tokstart = i; + else + break; + } + else + i++; /* normal character */ + } + return (REVERSE_LIST (list, WORD_LIST *)); +} +#endif + +/********************************************************/ +/* */ +/* Functions to perform assignment statements */ +/* */ +/********************************************************/ + +#if defined (ARRAY_VARS) +static SHELL_VAR * +do_compound_assignment (name, value, flags) + char *name, *value; + int flags; +{ + SHELL_VAR *v; + int mklocal, mkassoc, mkglobal, chklocal; + WORD_LIST *list; + char *newname; /* used for local nameref references */ + + mklocal = flags & ASS_MKLOCAL; + mkassoc = flags & ASS_MKASSOC; + mkglobal = flags & ASS_MKGLOBAL; + chklocal = flags & ASS_CHKLOCAL; + + if (mklocal && variable_context) + { + v = find_variable (name); /* follows namerefs */ + newname = (v == 0) ? nameref_transform_name (name, flags) : v->name; + if (v && ((readonly_p (v) && (flags & ASS_FORCE) == 0) || noassign_p (v))) + { + if (readonly_p (v)) + err_readonly (name); + return (v); /* XXX */ + } + list = expand_compound_array_assignment (v, value, flags); + if (mkassoc) + v = make_local_assoc_variable (newname, 0); + else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context) + v = make_local_array_variable (newname, 0); + if (v) + assign_compound_array_list (v, list, flags); + if (list) + dispose_words (list); + } + /* In a function but forcing assignment in global context. CHKLOCAL means to + check for an existing local variable first. */ + else if (mkglobal && variable_context) + { + v = chklocal ? find_variable (name) : 0; + if (v && (local_p (v) == 0 || v->context != variable_context)) + v = 0; + if (v == 0) + v = find_global_variable (name); + if (v && ((readonly_p (v) && (flags & ASS_FORCE) == 0) || noassign_p (v))) + { + if (readonly_p (v)) + err_readonly (name); + return (v); /* XXX */ + } + /* sanity check */ + newname = (v == 0) ? nameref_transform_name (name, flags) : name; + list = expand_compound_array_assignment (v, value, flags); + if (v == 0 && mkassoc) + v = make_new_assoc_variable (newname); + else if (v && mkassoc && assoc_p (v) == 0) + v = convert_var_to_assoc (v); + else if (v == 0) + v = make_new_array_variable (newname); + else if (v && mkassoc == 0 && array_p (v) == 0) + v = convert_var_to_array (v); + if (v) + assign_compound_array_list (v, list, flags); + if (list) + dispose_words (list); + } + else + { + v = assign_array_from_string (name, value, flags); + if (v && ((readonly_p (v) && (flags & ASS_FORCE) == 0) || noassign_p (v))) + { + if (readonly_p (v)) + err_readonly (name); + return (v); /* XXX */ + } + } + + return (v); +} +#endif + +/* Given STRING, an assignment string, get the value of the right side + of the `=', and bind it to the left side. If EXPAND is true, then + perform parameter expansion, command substitution, and arithmetic + expansion on the right-hand side. Perform tilde expansion in any + case. Do not perform word splitting on the result of expansion. */ +static int +do_assignment_internal (word, expand) + const WORD_DESC *word; + int expand; +{ + int offset, appendop, assign_list, aflags, retval; + char *name, *value, *temp; + SHELL_VAR *entry; +#if defined (ARRAY_VARS) + char *t; + int ni; +#endif + const char *string; + + if (word == 0 || word->word == 0) + return 0; + + appendop = assign_list = aflags = 0; + string = word->word; + offset = assignment (string, 0); + name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + if (name[offset - 1] == '+') + { + appendop = 1; + name[offset - 1] = '\0'; + } + + name[offset] = 0; /* might need this set later */ + temp = name + offset + 1; + +#if defined (ARRAY_VARS) + if (expand && (word->flags & W_COMPASSIGN)) + { + assign_list = ni = 1; + value = extract_array_assignment_list (temp, &ni); + } + else +#endif + if (expand && temp[0]) + value = expand_string_if_necessary (temp, 0, expand_string_assignment); + else + value = savestring (temp); + } + + if (value == 0) + { + value = (char *)xmalloc (1); + value[0] = '\0'; + } + + if (echo_command_at_execute) + { + if (appendop) + name[offset - 1] = '+'; + xtrace_print_assignment (name, value, assign_list, 1); + if (appendop) + name[offset - 1] = '\0'; + } + +#define ASSIGN_RETURN(r) do { FREE (value); free (name); return (r); } while (0) + + if (appendop) + aflags |= ASS_APPEND; + +#if defined (ARRAY_VARS) + if (t = mbschr (name, LBRACK)) + { + if (assign_list) + { + report_error (_("%s: cannot assign list to array member"), name); + ASSIGN_RETURN (0); + } + aflags |= ASS_ALLOWALLSUB; /* allow a[@]=value for existing associative arrays */ + entry = assign_array_element (name, value, aflags, (array_eltstate_t *)0); + if (entry == 0) + ASSIGN_RETURN (0); + } + else if (assign_list) + { + if ((word->flags & W_ASSIGNARG) && (word->flags & W_CHKLOCAL)) + aflags |= ASS_CHKLOCAL; + if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL) == 0) + aflags |= ASS_MKLOCAL; + if ((word->flags & W_ASSIGNARG) && (word->flags & W_ASSNGLOBAL)) + aflags |= ASS_MKGLOBAL; + if (word->flags & W_ASSIGNASSOC) + aflags |= ASS_MKASSOC; + entry = do_compound_assignment (name, value, aflags); + } + else +#endif /* ARRAY_VARS */ + entry = bind_variable (name, value, aflags); + + if (entry) + stupidly_hack_special_variables (entry->name); /* might be a nameref */ + else + stupidly_hack_special_variables (name); + + /* Return 1 if the assignment seems to have been performed correctly. */ + if (entry == 0 || readonly_p (entry)) + retval = 0; /* assignment failure */ + else if (noassign_p (entry)) + { + set_exit_status (EXECUTION_FAILURE); + retval = 1; /* error status, but not assignment failure */ + } + else + retval = 1; + + if (entry && retval != 0 && noassign_p (entry) == 0) + VUNSETATTR (entry, att_invisible); + + ASSIGN_RETURN (retval); +} + +/* Perform the assignment statement in STRING, and expand the + right side by doing tilde, command and parameter expansion. */ +int +do_assignment (string) + char *string; +{ + WORD_DESC td; + + td.flags = W_ASSIGNMENT; + td.word = string; + + return do_assignment_internal (&td, 1); +} + +int +do_word_assignment (word, flags) + WORD_DESC *word; + int flags; +{ + return do_assignment_internal (word, 1); +} + +/* Given STRING, an assignment string, get the value of the right side + of the `=', and bind it to the left side. Do not perform any word + expansions on the right hand side. */ +int +do_assignment_no_expand (string) + char *string; +{ + WORD_DESC td; + + td.flags = W_ASSIGNMENT; + td.word = string; + + return (do_assignment_internal (&td, 0)); +} + +/*************************************************** + * * + * Functions to manage the positional parameters * + * * + ***************************************************/ + +/* Return the word list that corresponds to `$*'. */ +WORD_LIST * +list_rest_of_args () +{ + register WORD_LIST *list, *args; + int i; + + /* Break out of the loop as soon as one of the dollar variables is null. */ + for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++) + list = make_word_list (make_bare_word (dollar_vars[i]), list); + + for (args = rest_of_args; args; args = args->next) + list = make_word_list (make_bare_word (args->word->word), list); + + return (REVERSE_LIST (list, WORD_LIST *)); +} + +/* Return the value of a positional parameter. This handles values > 10. */ +char * +get_dollar_var_value (ind) + intmax_t ind; +{ + char *temp; + WORD_LIST *p; + + if (ind < 10) + temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL; + else /* We want something like ${11} */ + { + ind -= 10; + for (p = rest_of_args; p && ind--; p = p->next) + ; + temp = p ? savestring (p->word->word) : (char *)NULL; + } + return (temp); +} + +/* Make a single large string out of the dollar digit variables, + and the rest_of_args. If DOLLAR_STAR is 1, then obey the special + case of "$*" with respect to IFS. */ +char * +string_rest_of_args (dollar_star) + int dollar_star; +{ + register WORD_LIST *list; + char *string; + + list = list_rest_of_args (); + string = dollar_star ? string_list_dollar_star (list, 0, 0) : string_list (list); + dispose_words (list); + return (string); +} + +/* Return a string containing the positional parameters from START to + END, inclusive. If STRING[0] == '*', we obey the rules for $*, + which only makes a difference if QUOTED is non-zero. If QUOTED includes + Q_HERE_DOCUMENT or Q_DOUBLE_QUOTES, this returns a quoted list, otherwise + no quoting chars are added. */ +static char * +pos_params (string, start, end, quoted, pflags) + char *string; + int start, end, quoted, pflags; +{ + WORD_LIST *save, *params, *h, *t; + char *ret; + int i; + + /* see if we can short-circuit. if start == end, we want 0 parameters. */ + if (start == end) + return ((char *)NULL); + + save = params = list_rest_of_args (); + if (save == 0 && start > 0) + return ((char *)NULL); + + if (start == 0) /* handle ${@:0[:x]} specially */ + { + t = make_word_list (make_word (dollar_vars[0]), params); + save = params = t; + } + + for (i = start ? 1 : 0; params && i < start; i++) + params = params->next; + if (params == 0) + { + dispose_words (save); + return ((char *)NULL); + } + for (h = t = params; params && i < end; i++) + { + t = params; + params = params->next; + } + t->next = (WORD_LIST *)NULL; + + ret = string_list_pos_params (string[0], h, quoted, pflags); + + if (t != params) + t->next = params; + + dispose_words (save); + return (ret); +} + +/******************************************************************/ +/* */ +/* Functions to expand strings to strings or WORD_LISTs */ +/* */ +/******************************************************************/ + +#if defined (PROCESS_SUBSTITUTION) +#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC || s == '~') +#else +#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC || s == '~') +#endif + +/* If there are any characters in STRING that require full expansion, + then call FUNC to expand STRING; otherwise just perform quote + removal if necessary. This returns a new string. */ +static char * +expand_string_if_necessary (string, quoted, func) + char *string; + int quoted; + EXPFUNC *func; +{ + WORD_LIST *list; + size_t slen; + int i, saw_quote; + char *ret; + DECLARE_MBSTATE; + + /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? strlen (string) : 0; + i = saw_quote = 0; + while (string[i]) + { + if (EXP_CHAR (string[i])) + break; + else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"') + saw_quote = 1; + ADVANCE_CHAR (string, slen, i); + } + + if (string[i]) + { + list = (*func) (string, quoted); + if (list) + { + ret = string_list (list); + dispose_words (list); + } + else + ret = (char *)NULL; + } + else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + ret = string_quote_removal (string, quoted); + else + ret = savestring (string); + + return ret; +} + +static inline char * +expand_string_to_string_internal (string, quoted, func) + char *string; + int quoted; + EXPFUNC *func; +{ + WORD_LIST *list; + char *ret; + + if (string == 0 || *string == '\0') + return ((char *)NULL); + + list = (*func) (string, quoted); + if (list) + { + ret = string_list (list); + dispose_words (list); + } + else + ret = (char *)NULL; + + return (ret); +} + +char * +expand_string_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string)); +} + +char * +expand_string_unsplit_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string_unsplit)); +} + +char * +expand_assignment_string_to_string (string, quoted) + char *string; + int quoted; +{ + return (expand_string_to_string_internal (string, quoted, expand_string_assignment)); +} + +/* Kind of like a combination of dequote_string and quote_string_for_globbing; + try to remove CTLESC quoting characters and convert CTLESC escaping a `&' + or a backslash into a backslash. The output of this function must eventually + be processed by strcreplace(). */ +static char * +quote_string_for_repl (string, flags) + char *string; + int flags; +{ + size_t slen; + char *result, *t; + const char *s, *send; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + result = (char *)xmalloc (slen * 2 + 1); + + if (string[0] == CTLESC && string[1] == 0) + { + result[0] = CTLESC; + result[1] = '\0'; + return (result); + } + + /* This is awkward. We want to translate CTLESC-\ to \\ if we will + eventually send this string through strcreplace(), which we will do + only if shouldexp_replacement() determines that there is something + to replace. We can either make sure to escape backslashes here and + have shouldexp_replacement() signal that we should send the string to + strcreplace() if it sees an escaped backslash, or we can scan the + string before copying it and turn CTLESC-\ into \\ only if we encounter + a CTLESC-& or a &. This does the former and changes shouldexp_replacement(). + If we double the backslashes here, we'll get doubled backslashes in any + result that doesn't get passed to strcreplace(). */ + + for (s = string, t = result; *s; ) + { + /* This function's result has to be processed by strcreplace() */ + if (*s == CTLESC && (s[1] == '&' || s[1] == '\\')) + { + *t++ = '\\'; + s++; + *t++ = *s++; + continue; + } + /* Dequote it */ + if (*s == CTLESC) + { + s++; + if (*s == '\0') + break; + } + COPY_CHAR_P (t, s, send); + } + + *t = '\0'; + return (result); +} + +/* This does not perform word splitting on the WORD_LIST it returns and + it treats $* as if it were quoted. It dequotes the WORD_LIST, adds + backslash escapes before CTLESC-quoted backslash and `& if + patsub_replacement is enabled. */ +static char * +expand_string_for_patsub (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *value; + char *ret, *t; + + if (string == 0 || *string == '\0') + return (char *)NULL; + + value = expand_string_for_pat (string, quoted, (int *)0, (int *)0); + + if (value && value->word) + { + remove_quoted_nulls (value->word->word); /* XXX */ + value->word->flags &= ~W_HASQUOTEDNULL; + } + + if (value) + { + t = (value->next) ? string_list (value) : value->word->word; + ret = quote_string_for_repl (t, quoted); + if (t != value->word->word) + free (t); + dispose_words (value); + } + else + ret = (char *)NULL; + + return (ret); +} + +char * +expand_arith_string (string, quoted) + char *string; + int quoted; +{ + WORD_DESC td; + WORD_LIST *list, *tlist; + size_t slen; + int i, saw_quote; + char *ret; + DECLARE_MBSTATE; + + /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? strlen (string) : 0; + i = saw_quote = 0; + while (string[i]) + { + if (EXP_CHAR (string[i])) + break; + else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"') + saw_quote = string[i]; + ADVANCE_CHAR (string, slen, i); + } + + if (string[i]) + { + /* This is expanded version of expand_string_internal as it's called by + expand_string_leave_quoted */ + td.flags = W_NOPROCSUB|W_NOTILDE; /* don't want process substitution or tilde expansion */ +#if 0 /* TAG: bash-5.2 */ + if (quoted & Q_ARRAYSUB) + td.flags |= W_NOCOMSUB; +#endif + td.word = savestring (string); + list = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + /* This takes care of the calls from expand_string_leave_quoted and + expand_string */ + if (list) + { + tlist = word_list_split (list); + dispose_words (list); + list = tlist; + if (list) + dequote_list (list); + } + /* This comes from expand_string_if_necessary */ + if (list) + { + ret = string_list (list); + dispose_words (list); + } + else + ret = (char *)NULL; + FREE (td.word); + } + else if (saw_quote && (quoted & Q_ARITH)) + ret = string_quote_removal (string, quoted); + else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + ret = string_quote_removal (string, quoted); + else + ret = savestring (string); + + return ret; +} + +#if defined (COND_COMMAND) +/* Just remove backslashes in STRING. Returns a new string. */ +char * +remove_backslashes (string) + char *string; +{ + char *r, *ret, *s; + + r = ret = (char *)xmalloc (strlen (string) + 1); + for (s = string; s && *s; ) + { + if (*s == '\\') + s++; + if (*s == 0) + break; + *r++ = *s++; + } + *r = '\0'; + return ret; +} + +/* This needs better error handling. */ +/* Expand W for use as an argument to a unary or binary operator in a + [[...]] expression. If SPECIAL is 1, this is the rhs argument + to the != or == operator, and should be treated as a pattern. In + this case, we quote the string specially for the globbing code. If + SPECIAL is 2, this is an rhs argument for the =~ operator, and should + be quoted appropriately for regcomp/regexec. If SPECIAL is 3, this is + an array subscript and should be quoted after expansion so it's only + expanded once (Q_ARITH). The caller is responsible + for removing the backslashes if the unquoted word is needed later. In + any case, since we don't perform word splitting, we need to do quoted + null character removal. */ +char * +cond_expand_word (w, special) + WORD_DESC *w; + int special; +{ + char *r, *p; + WORD_LIST *l; + int qflags; + + if (w->word == 0 || w->word[0] == '\0') + return ((char *)NULL); + + expand_no_split_dollar_star = 1; + w->flags |= W_NOSPLIT2; + qflags = (special == 3) ? Q_ARITH : 0; + l = call_expand_word_internal (w, qflags, 0, (int *)0, (int *)0); + expand_no_split_dollar_star = 0; + if (l) + { + if (special == 0) /* LHS */ + { + if (l->word) + word_list_remove_quoted_nulls (l); + dequote_list (l); + r = string_list (l); + } + else if (special == 3) /* arithmetic expression, Q_ARITH */ + { + if (l->word) + word_list_remove_quoted_nulls (l); /* for now */ + dequote_list (l); + r = string_list (l); + } + else + { + /* Need to figure out whether or not we should call dequote_escapes + or a new dequote_ctlnul function here, and under what + circumstances. */ + qflags = QGLOB_CVTNULL|QGLOB_CTLESC; + if (special == 2) + qflags |= QGLOB_REGEXP; + word_list_remove_quoted_nulls (l); + p = string_list (l); + r = quote_string_for_globbing (p, qflags); + free (p); + } + dispose_words (l); + } + else + r = (char *)NULL; + + return r; +} +#endif + +/* Expand $'...' and $"..." in a string for code paths that don't do it. The + FLAGS argument is 1 if this function should treat CTLESC as a quote + character (e.g., for here-documents) or not (e.g., for shell_expand_line). */ +char * +expand_string_dollar_quote (string, flags) + char *string; + int flags; +{ + size_t slen, retind, retsize; + int sindex, c, translen, peekc, news; + char *ret, *trans, *send, *t; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + sindex = 0; + + retsize = slen + 1; + ret = xmalloc (retsize); + retind = 0; + + while (c = string[sindex]) + { + switch (c) + { + default: + RESIZE_MALLOCED_BUFFER (ret, retind, locale_mb_cur_max + 1, retsize, 64); + COPY_CHAR_I (ret, retind, string, send, sindex); + break; + + case '\\': + RESIZE_MALLOCED_BUFFER (ret, retind, locale_mb_cur_max + 2, retsize, 64); + ret[retind++] = string[sindex++]; + + if (string[sindex]) + COPY_CHAR_I (ret, retind, string, send, sindex); + break; + + case '\'': + case '"': + if (c == '\'') + news = skip_single_quoted (string, slen, ++sindex, SX_COMPLETE); + else + news = skip_double_quoted (string, slen, ++sindex, SX_COMPLETE); + translen = news - sindex - 1; + RESIZE_MALLOCED_BUFFER (ret, retind, translen + 3, retsize, 64); + ret[retind++] = c; + if (translen > 0) + { + strncpy (ret + retind, string + sindex, translen); + retind += translen; + } + if (news > sindex && string[news - 1] == c) + ret[retind++] = c; + sindex = news; + break; + + case CTLESC: + RESIZE_MALLOCED_BUFFER (ret, retind, locale_mb_cur_max + 2, retsize, 64); + if (flags) + ret[retind++] = string[sindex++]; + if (string[sindex]) + COPY_CHAR_I (ret, retind, string, send, sindex); + break; + + case '$': + peekc = string[++sindex]; +#if defined (TRANSLATABLE_STRINGS) + if (peekc != '\'' && peekc != '"') +#else + if (peekc != '\'') +#endif + { + RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 16); + ret[retind++] = c; + break; + } + if (string[sindex + 1] == '\0') /* don't bother */ + { + RESIZE_MALLOCED_BUFFER (ret, retind, 3, retsize, 16); + ret[retind++] = c; + ret[retind++] = peekc; + sindex++; + break; + } + if (peekc == '\'') + { + /* SX_COMPLETE is the equivalent of ALLOWESC here */ + /* We overload SX_COMPLETE below */ + news = skip_single_quoted (string, slen, ++sindex, SX_COMPLETE); + /* Check for unclosed string and don't bother if so */ + if (news > sindex && string[news] == '\0' && string[news-1] != peekc) + { + RESIZE_MALLOCED_BUFFER (ret, retind, 3, retsize, 16); + ret[retind++] = c; + ret[retind++] = peekc; + continue; + } + t = substring (string, sindex, news - 1); + trans = ansiexpand (t, 0, news-sindex-1, &translen); + free (t); + t = sh_single_quote (trans); + sindex = news; + } +#if defined (TRANSLATABLE_STRINGS) + else + { + news = ++sindex; + t = string_extract_double_quoted (string, &news, SX_COMPLETE); + /* Check for unclosed string and don't bother if so */ + if (news > sindex && string[news] == '\0' && string[news-1] != peekc) + { + RESIZE_MALLOCED_BUFFER (ret, retind, 3, retsize, 16); + ret[retind++] = c; + ret[retind++] = peekc; + free (t); + continue; + } + trans = locale_expand (t, 0, news-sindex, 0, &translen); + free (t); + if (singlequote_translations && + ((news-sindex-1) != translen || STREQN (t, trans, translen) == 0)) + t = sh_single_quote (trans); + else + t = sh_mkdoublequoted (trans, translen, 0); + sindex = news; + } +#endif /* TRANSLATABLE_STRINGS */ + free (trans); + trans = t; + translen = strlen (trans); + + RESIZE_MALLOCED_BUFFER (ret, retind, translen + 1, retsize, 128); + strcpy (ret + retind, trans); + retind += translen; + FREE (trans); + break; + } + } + + ret[retind] = 0; + return ret; +} + +/* Call expand_word_internal to expand W and handle error returns. + A convenience function for functions that don't want to handle + any errors or free any memory before aborting. */ +static WORD_LIST * +call_expand_word_internal (w, q, i, c, e) + WORD_DESC *w; + int q, i, *c, *e; +{ + WORD_LIST *result; + + result = expand_word_internal (w, q, i, c, e); + if (result == &expand_word_error || result == &expand_word_fatal) + { + /* By convention, each time this error is returned, w->word has + already been freed (it sometimes may not be in the fatal case, + but that doesn't result in a memory leak because we're going + to exit in most cases). */ + w->word = (char *)NULL; + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF); + /* NOTREACHED */ + return (NULL); + } + else + return (result); +} + +/* Perform parameter expansion, command substitution, and arithmetic + expansion on STRING, as if it were a word. Leave the result quoted. + Since this does not perform word splitting, it leaves quoted nulls + in the result. */ +static WORD_LIST * +expand_string_internal (string, quoted) + char *string; + int quoted; +{ + WORD_DESC td; + WORD_LIST *tresult; + + if (string == 0 || *string == 0) + return ((WORD_LIST *)NULL); + + td.flags = 0; + td.word = savestring (string); + + tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + + FREE (td.word); + return (tresult); +} + +/* Expand STRING by performing parameter expansion, command substitution, + and arithmetic expansion. Dequote the resulting WORD_LIST before + returning it, but do not perform word splitting. The call to + remove_quoted_nulls () is in here because word splitting normally + takes care of quote removal. */ +WORD_LIST * +expand_string_unsplit (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *value; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + expand_no_split_dollar_star = 1; + value = expand_string_internal (string, quoted); + expand_no_split_dollar_star = 0; + + if (value) + { + if (value->word) + { + remove_quoted_nulls (value->word->word); /* XXX */ + value->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (value); + } + return (value); +} + +/* Expand the rhs of an assignment statement */ +WORD_LIST * +expand_string_assignment (string, quoted) + char *string; + int quoted; +{ + WORD_DESC td; + WORD_LIST *value; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + expand_no_split_dollar_star = 1; + +#if 0 + /* Other shells (ksh93) do it this way, which affects how $@ is expanded + in constructs like bar=${@#0} (preserves the spaces resulting from the + expansion of $@ in a context where you don't do word splitting); Posix + interp 888 makes the expansion of $@ in contexts where word splitting + is not performed unspecified. */ + td.flags = W_ASSIGNRHS|W_NOSPLIT2; /* Posix interp 888 */ +#else + td.flags = W_ASSIGNRHS; +#endif + td.flags |= (W_NOGLOB|W_TILDEEXP); + td.word = savestring (string); + value = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + FREE (td.word); + + expand_no_split_dollar_star = 0; + + if (value) + { + if (value->word) + { + remove_quoted_nulls (value->word->word); /* XXX */ + value->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (value); + } + return (value); +} + +/* Expand one of the PS? prompt strings. This is a sort of combination of + expand_string_unsplit and expand_string_internal, but returns the + passed string when an error occurs. Might want to trap other calls + to jump_to_top_level here so we don't endlessly loop. */ +WORD_LIST * +expand_prompt_string (string, quoted, wflags) + char *string; + int quoted; + int wflags; +{ + WORD_LIST *value; + WORD_DESC td; + + if (string == 0 || *string == 0) + return ((WORD_LIST *)NULL); + + td.flags = wflags; + td.word = savestring (string); + + no_longjmp_on_fatal_error = 1; + value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + no_longjmp_on_fatal_error = 0; + + if (value == &expand_word_error || value == &expand_word_fatal) + { + value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL); + return value; + } + FREE (td.word); + if (value) + { + if (value->word) + { + remove_quoted_nulls (value->word->word); /* XXX */ + value->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (value); + } + return (value); +} + +/* Expand STRING just as if you were expanding a word, but do not dequote + the resultant WORD_LIST. This is called only from within this file, + and is used to correctly preserve quoted characters when expanding + things like ${1+"$@"}. This does parameter expansion, command + substitution, arithmetic expansion, and word splitting. */ +static WORD_LIST * +expand_string_leave_quoted (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *tlist; + WORD_LIST *tresult; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + tlist = expand_string_internal (string, quoted); + + if (tlist) + { + tresult = word_list_split (tlist); + dispose_words (tlist); + return (tresult); + } + return ((WORD_LIST *)NULL); +} + +/* This does not perform word splitting or dequote the WORD_LIST + it returns. */ +static WORD_LIST * +expand_string_for_rhs (string, quoted, op, pflags, dollar_at_p, expanded_p) + char *string; + int quoted, op, pflags; + int *dollar_at_p, *expanded_p; +{ + WORD_DESC td; + WORD_LIST *tresult; + int old_nosplit; + + if (string == 0 || *string == '\0') + return (WORD_LIST *)NULL; + + /* We want field splitting to be determined by what is going to be done with + the entire ${parameterOPword} expansion, so we don't want to split the RHS + we expand here. However, the expansion of $* is determined by whether we + are going to eventually perform word splitting, so we want to set this + depending on whether or not are are going to be splitting: if the expansion + is quoted, if the OP is `=', or if IFS is set to the empty string, we + are not going to be splitting, so we set expand_no_split_dollar_star to + note this to callees. + We pass through PF_ASSIGNRHS as W_ASSIGNRHS if this is on the RHS of an + assignment statement. */ + /* The updated treatment of $* is the result of Posix interp 888 */ + /* This was further clarified on the austin-group list in March, 2017 and + in Posix bug 1129 */ + old_nosplit = expand_no_split_dollar_star; + expand_no_split_dollar_star = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || op == '=' || ifs_is_null == 0; /* XXX - was 1 */ + td.flags = W_EXPANDRHS; /* expanding RHS of ${paramOPword} */ + td.flags |= W_NOSPLIT2; /* no splitting, remove "" and '' */ + if (pflags & PF_ASSIGNRHS) /* pass through */ + td.flags |= W_ASSIGNRHS; + if (op == '=') +#if 0 + td.flags |= W_ASSIGNRHS; /* expand b in ${a=b} like assignment */ +#else + td.flags |= W_ASSIGNRHS|W_NOASSNTILDE; /* expand b in ${a=b} like assignment */ +#endif + td.word = savestring (string); + tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, expanded_p); + expand_no_split_dollar_star = old_nosplit; + free (td.word); + + return (tresult); +} + +/* This does not perform word splitting or dequote the WORD_LIST + it returns and it treats $* as if it were quoted. */ +static WORD_LIST * +expand_string_for_pat (string, quoted, dollar_at_p, expanded_p) + char *string; + int quoted, *dollar_at_p, *expanded_p; +{ + WORD_DESC td; + WORD_LIST *tresult; + int oexp; + + if (string == 0 || *string == '\0') + return (WORD_LIST *)NULL; + + oexp = expand_no_split_dollar_star; + expand_no_split_dollar_star = 1; + td.flags = W_NOSPLIT2; /* no splitting, remove "" and '' */ + td.word = savestring (string); + tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, expanded_p); + expand_no_split_dollar_star = oexp; + free (td.word); + + return (tresult); +} + +/* Expand STRING just as if you were expanding a word. This also returns + a list of words. Note that filename globbing is *NOT* done for word + or string expansion, just when the shell is expanding a command. This + does parameter expansion, command substitution, arithmetic expansion, + and word splitting. Dequote the resultant WORD_LIST before returning. */ +WORD_LIST * +expand_string (string, quoted) + char *string; + int quoted; +{ + WORD_LIST *result; + + if (string == 0 || *string == '\0') + return ((WORD_LIST *)NULL); + + result = expand_string_leave_quoted (string, quoted); + return (result ? dequote_list (result) : result); +} + +/******************************************* + * * + * Functions to expand WORD_DESCs * + * * + *******************************************/ + +/* Expand WORD, performing word splitting on the result. This does + parameter expansion, command substitution, arithmetic expansion, + word splitting, and quote removal. */ + +WORD_LIST * +expand_word (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result, *tresult; + + tresult = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + result = word_list_split (tresult); + dispose_words (tresult); + return (result ? dequote_list (result) : result); +} + +/* Expand WORD, but do not perform word splitting on the result. This + does parameter expansion, command substitution, arithmetic expansion, + and quote removal. */ +WORD_LIST * +expand_word_unsplit (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result; + + result = expand_word_leave_quoted (word, quoted); + return (result ? dequote_list (result) : result); +} + +/* Perform shell expansions on WORD, but do not perform word splitting or + quote removal on the result. Virtually identical to expand_word_unsplit; + could be combined if implementations don't diverge. */ +WORD_LIST * +expand_word_leave_quoted (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_LIST *result; + + expand_no_split_dollar_star = 1; + if (ifs_is_null) + word->flags |= W_NOSPLIT; + word->flags |= W_NOSPLIT2; + result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); + expand_no_split_dollar_star = 0; + + return result; +} + +/*************************************************** + * * + * Functions to handle quoting chars * + * * + ***************************************************/ + +/* Conventions: + + A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string. + The parser passes CTLNUL as CTLESC CTLNUL. */ + +/* Quote escape characters in string s, but no other characters. This is + used to protect CTLESC and CTLNUL in variable values from the rest of + the word expansion process after the variable is expanded (word splitting + and filename generation). If IFS is null, we quote spaces as well, just + in case we split on spaces later (in the case of unquoted $@, we will + eventually attempt to split the entire word on spaces). Corresponding + code exists in dequote_escapes. Even if we don't end up splitting on + spaces, quoting spaces is not a problem. This should never be called on + a string that is quoted with single or double quotes or part of a here + document (effectively double-quoted). + FLAGS says whether or not we are going to split the result. If we are not, + and there is a CTLESC or CTLNUL in IFS, we need to quote CTLESC and CTLNUL, + respectively, to prevent them from being removed as part of dequoting. */ +static char * +quote_escapes_internal (string, flags) + const char *string; + int flags; +{ + const char *s, *send; + char *t, *result; + size_t slen; + int quote_spaces, skip_ctlesc, skip_ctlnul, nosplit; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + quote_spaces = (ifs_value && *ifs_value == 0); + nosplit = (flags & PF_NOSPLIT2); + + for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++) + { + skip_ctlesc |= (nosplit == 0 && *s == CTLESC); + skip_ctlnul |= (nosplit == 0 && *s == CTLNUL); + } + + t = result = (char *)xmalloc ((slen * 2) + 1); + s = string; + + while (*s) + { + if ((skip_ctlesc == 0 && *s == CTLESC) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' ')) + *t++ = CTLESC; + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + + return (result); +} + +char * +quote_escapes (string) + const char *string; +{ + return (quote_escapes_internal (string, 0)); +} + +char * +quote_rhs (string) + const char *string; +{ + return (quote_escapes_internal (string, PF_NOSPLIT2)); +} + +static WORD_LIST * +list_quote_escapes (list) + WORD_LIST *list; +{ + register WORD_LIST *w; + char *t; + + for (w = list; w; w = w->next) + { + t = w->word->word; + w->word->word = quote_escapes (t); + free (t); + } + return list; +} + +/* Inverse of quote_escapes; remove CTLESC protecting CTLESC or CTLNUL. + + The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL. + This is necessary to make unquoted CTLESC and CTLNUL characters in the + data stream pass through properly. + + We need to remove doubled CTLESC characters inside quoted strings before + quoting the entire string, so we do not double the number of CTLESC + characters. + + Also used by parts of the pattern substitution code. */ +char * +dequote_escapes (string) + const char *string; +{ + const char *s, *send; + char *t, *result; + size_t slen; + int quote_spaces; + DECLARE_MBSTATE; + + if (string == 0) + return (char *)0; + + slen = strlen (string); + send = string + slen; + + t = result = (char *)xmalloc (slen + 1); + + if (strchr (string, CTLESC) == 0) + return (strcpy (result, string)); + + quote_spaces = (ifs_value && *ifs_value == 0); + + s = string; + while (*s) + { + if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL || (quote_spaces && s[1] == ' '))) + { + s++; + if (*s == '\0') + break; + } + COPY_CHAR_P (t, s, send); + } + *t = '\0'; + + return result; +} + +#if defined (INCLUDE_UNUSED) +static WORD_LIST * +list_dequote_escapes (list) + WORD_LIST *list; +{ + register WORD_LIST *w; + char *t; + + for (w = list; w; w = w->next) + { + t = w->word->word; + w->word->word = dequote_escapes (t); + free (t); + } + return list; +} +#endif + +/* Return a new string with the quoted representation of character C. + This turns "" into QUOTED_NULL, so the W_HASQUOTEDNULL flag needs to be + set in any resultant WORD_DESC where this value is the word. */ +static char * +make_quoted_char (c) + int c; +{ + char *temp; + + temp = (char *)xmalloc (3); + if (c == 0) + { + temp[0] = CTLNUL; + temp[1] = '\0'; + } + else + { + temp[0] = CTLESC; + temp[1] = c; + temp[2] = '\0'; + } + return (temp); +} + +/* Quote STRING, returning a new string. This turns "" into QUOTED_NULL, so + the W_HASQUOTEDNULL flag needs to be set in any resultant WORD_DESC where + this value is the word. */ +char * +quote_string (string) + char *string; +{ + register char *t; + size_t slen; + char *result, *send; + + if (*string == 0) + { + result = (char *)xmalloc (2); + result[0] = CTLNUL; + result[1] = '\0'; + } + else + { + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + result = (char *)xmalloc ((slen * 2) + 1); + + for (t = result; string < send; ) + { + *t++ = CTLESC; + COPY_CHAR_P (t, string, send); + } + *t = '\0'; + } + return (result); +} + +/* De-quote quoted characters in STRING. */ +char * +dequote_string (string) + char *string; +{ + register char *s, *t; + size_t slen; + char *result, *send; + DECLARE_MBSTATE; + + if (string[0] == CTLESC && string[1] == 0) + internal_debug ("dequote_string: string with bare CTLESC"); + + slen = STRLEN (string); + + t = result = (char *)xmalloc (slen + 1); + + if (QUOTED_NULL (string)) + { + result[0] = '\0'; + return (result); + } + + /* A string consisting of only a single CTLESC should pass through unchanged */ + if (string[0] == CTLESC && string[1] == 0) + { + result[0] = CTLESC; + result[1] = '\0'; + return (result); + } + + /* If no character in the string can be quoted, don't bother examining + each character. Just return a copy of the string passed to us. */ + if (strchr (string, CTLESC) == NULL) + return (strcpy (result, string)); + + send = string + slen; + s = string; + while (*s) + { + if (*s == CTLESC) + { + s++; + if (*s == '\0') + break; + } + COPY_CHAR_P (t, s, send); + } + + *t = '\0'; + return (result); +} + +/* Quote the entire WORD_LIST list. */ +static WORD_LIST * +quote_list (list) + WORD_LIST *list; +{ + register WORD_LIST *w; + char *t; + + for (w = list; w; w = w->next) + { + t = w->word->word; + w->word->word = quote_string (t); + if (*t == 0) + w->word->flags |= W_HASQUOTEDNULL; /* XXX - turn on W_HASQUOTEDNULL here? */ + w->word->flags |= W_QUOTED; + free (t); + } + return list; +} + +WORD_DESC * +dequote_word (word) + WORD_DESC *word; +{ + register char *s; + + s = dequote_string (word->word); + if (QUOTED_NULL (word->word)) + word->flags &= ~W_HASQUOTEDNULL; + free (word->word); + word->word = s; + + return word; +} + +/* De-quote quoted characters in each word in LIST. */ +WORD_LIST * +dequote_list (list) + WORD_LIST *list; +{ + register char *s; + register WORD_LIST *tlist; + + for (tlist = list; tlist; tlist = tlist->next) + { + s = dequote_string (tlist->word->word); + if (QUOTED_NULL (tlist->word->word)) + tlist->word->flags &= ~W_HASQUOTEDNULL; + free (tlist->word->word); + tlist->word->word = s; + } + return list; +} + +/* Remove CTLESC protecting a CTLESC or CTLNUL in place. Return the passed + string. */ +char * +remove_quoted_escapes (string) + char *string; +{ + char *t; + + if (string) + { + t = dequote_escapes (string); + strcpy (string, t); + free (t); + } + + return (string); +} + +/* Remove quoted $IFS characters from STRING. Quoted IFS characters are + added to protect them from word splitting, but we need to remove them + if no word splitting takes place. This returns newly-allocated memory, + so callers can use it to replace savestring(). */ +char * +remove_quoted_ifs (string) + char *string; +{ + register size_t slen; + register int i, j; + char *ret, *send; + DECLARE_MBSTATE; + + slen = strlen (string); + send = string + slen; + + i = j = 0; + ret = (char *)xmalloc (slen + 1); + + while (i < slen) + { + if (string[i] == CTLESC) + { + i++; + if (string[i] == 0 || isifs (string[i]) == 0) + ret[j++] = CTLESC; + if (i == slen) + break; + } + + COPY_CHAR_I (ret, j, string, send, i); + } + ret[j] = '\0'; + + return (ret); +} + +char * +remove_quoted_nulls (string) + char *string; +{ + register size_t slen; + register int i, j, prev_i; + DECLARE_MBSTATE; + + if (strchr (string, CTLNUL) == 0) /* XXX */ + return string; /* XXX */ + + slen = strlen (string); + i = j = 0; + + while (i < slen) + { + if (string[i] == CTLESC) + { + /* Old code had j++, but we cannot assume that i == j at this + point -- what if a CTLNUL has already been removed from the + string? We don't want to drop the CTLESC or recopy characters + that we've already copied down. */ + i++; + string[j++] = CTLESC; + if (i == slen) + break; + } + else if (string[i] == CTLNUL) + { + i++; + continue; + } + + prev_i = i; + ADVANCE_CHAR (string, slen, i); /* COPY_CHAR_I? */ + if (j < prev_i) + { + do string[j++] = string[prev_i++]; while (prev_i < i); + } + else + j = i; + } + string[j] = '\0'; + + return (string); +} + +/* Perform quoted null character removal on each element of LIST. + This modifies LIST. */ +void +word_list_remove_quoted_nulls (list) + WORD_LIST *list; +{ + register WORD_LIST *t; + + for (t = list; t; t = t->next) + { + remove_quoted_nulls (t->word->word); + t->word->flags &= ~W_HASQUOTEDNULL; + } +} + +/* **************************************************************** */ +/* */ +/* Functions for Matching and Removing Patterns */ +/* */ +/* **************************************************************** */ + +#if defined (HANDLE_MULTIBYTE) +# ifdef INCLUDE_UNUSED +static unsigned char * +mb_getcharlens (string, len) + char *string; + int len; +{ + int i, offset, last; + unsigned char *ret; + char *p; + DECLARE_MBSTATE; + + i = offset = 0; + last = 0; + ret = (unsigned char *)xmalloc (len); + memset (ret, 0, len); + while (string[last]) + { + ADVANCE_CHAR (string, len, offset); + ret[last] = offset - last; + last = offset; + } + return ret; +} +# endif +#endif + +/* Remove the portion of PARAM matched by PATTERN according to OP, where OP + can have one of 4 values: + RP_LONG_LEFT remove longest matching portion at start of PARAM + RP_SHORT_LEFT remove shortest matching portion at start of PARAM + RP_LONG_RIGHT remove longest matching portion at end of PARAM + RP_SHORT_RIGHT remove shortest matching portion at end of PARAM +*/ + +#define RP_LONG_LEFT 1 +#define RP_SHORT_LEFT 2 +#define RP_LONG_RIGHT 3 +#define RP_SHORT_RIGHT 4 + +/* Returns its first argument if nothing matched; new memory otherwise */ +static char * +remove_upattern (param, pattern, op) + char *param, *pattern; + int op; +{ + register size_t len; + register char *end; + register char *p, *ret, c; + + len = STRLEN (param); + end = param + len; + + switch (op) + { + case RP_LONG_LEFT: /* remove longest match at start */ + for (p = end; p >= param; p--) + { + c = *p; *p = '\0'; + if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + *p = c; + return (savestring (p)); + } + *p = c; + + } + break; + + case RP_SHORT_LEFT: /* remove shortest match at start */ + for (p = param; p <= end; p++) + { + c = *p; *p = '\0'; + if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + *p = c; + return (savestring (p)); + } + *p = c; + } + break; + + case RP_LONG_RIGHT: /* remove longest match at end */ + for (p = param; p <= end; p++) + { + if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + c = *p; *p = '\0'; + ret = savestring (param); + *p = c; + return (ret); + } + } + break; + + case RP_SHORT_RIGHT: /* remove shortest match at end */ + for (p = end; p >= param; p--) + { + if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + c = *p; *p = '\0'; + ret = savestring (param); + *p = c; + return (ret); + } + } + break; + } + + return (param); /* no match, return original string */ +} + +#if defined (HANDLE_MULTIBYTE) +/* Returns its first argument if nothing matched; new memory otherwise */ +static wchar_t * +remove_wpattern (wparam, wstrlen, wpattern, op) + wchar_t *wparam; + size_t wstrlen; + wchar_t *wpattern; + int op; +{ + wchar_t wc, *ret; + int n; + + switch (op) + { + case RP_LONG_LEFT: /* remove longest match at start */ + for (n = wstrlen; n >= 0; n--) + { + wc = wparam[n]; wparam[n] = L'\0'; + if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wparam[n] = wc; + return (wcsdup (wparam + n)); + } + wparam[n] = wc; + } + break; + + case RP_SHORT_LEFT: /* remove shortest match at start */ + for (n = 0; n <= wstrlen; n++) + { + wc = wparam[n]; wparam[n] = L'\0'; + if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wparam[n] = wc; + return (wcsdup (wparam + n)); + } + wparam[n] = wc; + } + break; + + case RP_LONG_RIGHT: /* remove longest match at end */ + for (n = 0; n <= wstrlen; n++) + { + if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wc = wparam[n]; wparam[n] = L'\0'; + ret = wcsdup (wparam); + wparam[n] = wc; + return (ret); + } + } + break; + + case RP_SHORT_RIGHT: /* remove shortest match at end */ + for (n = wstrlen; n >= 0; n--) + { + if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH) + { + wc = wparam[n]; wparam[n] = L'\0'; + ret = wcsdup (wparam); + wparam[n] = wc; + return (ret); + } + } + break; + } + + return (wparam); /* no match, return original string */ +} +#endif /* HANDLE_MULTIBYTE */ + +static char * +remove_pattern (param, pattern, op) + char *param, *pattern; + int op; +{ + char *xret; + + if (param == NULL) + return (param); + if (*param == '\0' || pattern == NULL || *pattern == '\0') /* minor optimization */ + return (savestring (param)); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + { + wchar_t *ret, *oret; + size_t n; + wchar_t *wparam, *wpattern; + mbstate_t ps; + + /* XXX - could optimize here by checking param and pattern for multibyte + chars with mbsmbchar and calling remove_upattern. */ + + n = xdupmbstowcs (&wpattern, NULL, pattern); + if (n == (size_t)-1) + { + xret = remove_upattern (param, pattern, op); + return ((xret == param) ? savestring (param) : xret); + } + n = xdupmbstowcs (&wparam, NULL, param); + + if (n == (size_t)-1) + { + free (wpattern); + xret = remove_upattern (param, pattern, op); + return ((xret == param) ? savestring (param) : xret); + } + oret = ret = remove_wpattern (wparam, n, wpattern, op); + /* Don't bother to convert wparam back to multibyte string if nothing + matched; just return copy of original string */ + if (ret == wparam) + { + free (wparam); + free (wpattern); + return (savestring (param)); + } + + free (wparam); + free (wpattern); + + n = strlen (param); + xret = (char *)xmalloc (n + 1); + memset (&ps, '\0', sizeof (mbstate_t)); + n = wcsrtombs (xret, (const wchar_t **)&ret, n, &ps); + xret[n] = '\0'; /* just to make sure */ + free (oret); + return xret; + } + else +#endif + { + xret = remove_upattern (param, pattern, op); + return ((xret == param) ? savestring (param) : xret); + } +} + +/* Match PAT anywhere in STRING and return the match boundaries. + This returns 1 in case of a successful match, 0 otherwise. SP + and EP are pointers into the string where the match begins and + ends, respectively. MTYPE controls what kind of match is attempted. + MATCH_BEG and MATCH_END anchor the match at the beginning and end + of the string, respectively. The longest match is returned. */ +static int +match_upattern (string, pat, mtype, sp, ep) + char *string, *pat; + int mtype; + char **sp, **ep; +{ + int c, mlen; + size_t len; + register char *p, *p1, *npat; + char *end; + + /* If the pattern doesn't match anywhere in the string, go ahead and + short-circuit right away. A minor optimization, saves a bunch of + unnecessary calls to strmatch (up to N calls for a string of N + characters) if the match is unsuccessful. To preserve the semantics + of the substring matches below, we make sure that the pattern has + `*' as first and last character, making a new pattern if necessary. */ + /* XXX - check this later if I ever implement `**' with special meaning, + since this will potentially result in `**' at the beginning or end */ + len = STRLEN (pat); + if (pat[0] != '*' || (pat[0] == '*' && pat[1] == LPAREN && extended_glob) || pat[len - 1] != '*') + { + int unescaped_backslash; + char *pp; + + p = npat = (char *)xmalloc (len + 3); + p1 = pat; + if ((mtype != MATCH_BEG) && (*p1 != '*' || (*p1 == '*' && p1[1] == LPAREN && extended_glob))) + *p++ = '*'; + while (*p1) + *p++ = *p1++; +#if 1 + /* Need to also handle a pattern that ends with an unescaped backslash. + For right now, we ignore it because the pattern matching code will + fail the match anyway */ + /* If the pattern ends with a `*' we leave it alone if it's preceded by + an even number of backslashes, but if it's escaped by a backslash + we need to add another `*'. */ + if ((mtype != MATCH_END) && (p1[-1] == '*' && (unescaped_backslash = p1[-2] == '\\'))) + { + pp = p1 - 3; + while (pp >= pat && *pp-- == '\\') + unescaped_backslash = 1 - unescaped_backslash; + if (unescaped_backslash) + *p++ = '*'; + } + else if (mtype != MATCH_END && p1[-1] != '*') + *p++ = '*'; +#else + if (p1[-1] != '*' || p1[-2] == '\\') + *p++ = '*'; +#endif + *p = '\0'; + } + else + npat = pat; + c = strmatch (npat, string, FNMATCH_EXTFLAG | FNMATCH_IGNCASE); + if (npat != pat) + free (npat); + if (c == FNM_NOMATCH) + return (0); + + len = STRLEN (string); + end = string + len; + + mlen = umatchlen (pat, len); + if (mlen > (int)len) + return (0); + + switch (mtype) + { + case MATCH_ANY: + for (p = string; p <= end; p++) + { + if (match_pattern_char (pat, p, FNMATCH_IGNCASE)) + { + p1 = (mlen == -1) ? end : p + mlen; + /* p1 - p = length of portion of string to be considered + p = current position in string + mlen = number of characters consumed by match (-1 for entire string) + end = end of string + we want to break immediately if the potential match len + is greater than the number of characters remaining in the + string + */ + if (p1 > end) + break; + for ( ; p1 >= p; p1--) + { + c = *p1; *p1 = '\0'; + if (strmatch (pat, p, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0) + { + *p1 = c; + *sp = p; + *ep = p1; + return 1; + } + *p1 = c; +#if 1 + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; +#endif + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_char (pat, string, FNMATCH_IGNCASE) == 0) + return (0); + + for (p = (mlen == -1) ? end : string + mlen; p >= string; p--) + { + c = *p; *p = '\0'; + if (strmatch (pat, string, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0) + { + *p = c; + *sp = string; + *ep = p; + return 1; + } + *p = c; + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; + } + + return (0); + + case MATCH_END: + for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++) + { + if (strmatch (pat, p, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0) + { + *sp = p; + *ep = end; + return 1; + } + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; + } + + return (0); + } + + return (0); +} + +#if defined (HANDLE_MULTIBYTE) + +#define WFOLD(c) (match_ignore_case && iswupper (c) ? towlower (c) : (c)) + +/* Match WPAT anywhere in WSTRING and return the match boundaries. + This returns 1 in case of a successful match, 0 otherwise. Wide + character version. */ +static int +match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep) + wchar_t *wstring; + char **indices; + size_t wstrlen; + wchar_t *wpat; + int mtype; + char **sp, **ep; +{ + wchar_t wc, *wp, *nwpat, *wp1; + size_t len; + int mlen; + int n, n1, n2, simple; + + simple = (wpat[0] != L'\\' && wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'['); +#if defined (EXTENDED_GLOB) + if (extended_glob) + simple &= (wpat[1] != L'(' || (wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'+' && wpat[0] != L'!' && wpat[0] != L'@')); /*)*/ +#endif + + /* If the pattern doesn't match anywhere in the string, go ahead and + short-circuit right away. A minor optimization, saves a bunch of + unnecessary calls to strmatch (up to N calls for a string of N + characters) if the match is unsuccessful. To preserve the semantics + of the substring matches below, we make sure that the pattern has + `*' as first and last character, making a new pattern if necessary. */ + len = wcslen (wpat); + if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == WLPAREN && extended_glob) || wpat[len - 1] != L'*') + { + int unescaped_backslash; + wchar_t *wpp; + + wp = nwpat = (wchar_t *)xmalloc ((len + 3) * sizeof (wchar_t)); + wp1 = wpat; + if (*wp1 != L'*' || (*wp1 == '*' && wp1[1] == WLPAREN && extended_glob)) + *wp++ = L'*'; + while (*wp1 != L'\0') + *wp++ = *wp1++; +#if 1 + /* See comments above in match_upattern. */ + if (wp1[-1] == L'*' && (unescaped_backslash = wp1[-2] == L'\\')) + { + wpp = wp1 - 3; + while (wpp >= wpat && *wpp-- == L'\\') + unescaped_backslash = 1 - unescaped_backslash; + if (unescaped_backslash) + *wp++ = L'*'; + } + else if (wp1[-1] != L'*') + *wp++ = L'*'; +#else + if (wp1[-1] != L'*' || wp1[-2] == L'\\') + *wp++ = L'*'; +#endif + *wp = '\0'; + } + else + nwpat = wpat; + len = wcsmatch (nwpat, wstring, FNMATCH_EXTFLAG | FNMATCH_IGNCASE); + if (nwpat != wpat) + free (nwpat); + if (len == FNM_NOMATCH) + return (0); + + mlen = wmatchlen (wpat, wstrlen); + if (mlen > (int)wstrlen) + return (0); + +/* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */ + switch (mtype) + { + case MATCH_ANY: + for (n = 0; n <= wstrlen; n++) + { + n2 = simple ? (WFOLD(*wpat) == WFOLD(wstring[n])) : match_pattern_wchar (wpat, wstring + n, FNMATCH_IGNCASE); + if (n2) + { + n1 = (mlen == -1) ? wstrlen : n + mlen; + if (n1 > wstrlen) + break; + + for ( ; n1 >= n; n1--) + { + wc = wstring[n1]; wstring[n1] = L'\0'; + if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0) + { + wstring[n1] = wc; + *sp = indices[n]; + *ep = indices[n1]; + return 1; + } + wstring[n1] = wc; + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; + } + } + } + + return (0); + + case MATCH_BEG: + if (match_pattern_wchar (wpat, wstring, FNMATCH_IGNCASE) == 0) + return (0); + + for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--) + { + wc = wstring[n]; wstring[n] = L'\0'; + if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0) + { + wstring[n] = wc; + *sp = indices[0]; + *ep = indices[n]; + return 1; + } + wstring[n] = wc; + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; + } + + return (0); + + case MATCH_END: + for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++) + { + if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG | FNMATCH_IGNCASE) == 0) + { + *sp = indices[n]; + *ep = indices[wstrlen]; + return 1; + } + /* If MLEN != -1, we have a fixed length pattern. */ + if (mlen != -1) + break; + } + + return (0); + } + + return (0); +} +#undef WFOLD +#endif /* HANDLE_MULTIBYTE */ + +static int +match_pattern (string, pat, mtype, sp, ep) + char *string, *pat; + int mtype; + char **sp, **ep; +{ +#if defined (HANDLE_MULTIBYTE) + int ret; + size_t n; + wchar_t *wstring, *wpat; + char **indices; +#endif + + if (string == 0 || pat == 0 || *pat == 0) + return (0); + +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + { + if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0) + return (match_upattern (string, pat, mtype, sp, ep)); + + n = xdupmbstowcs (&wpat, NULL, pat); + if (n == (size_t)-1) + return (match_upattern (string, pat, mtype, sp, ep)); + n = xdupmbstowcs (&wstring, &indices, string); + if (n == (size_t)-1) + { + free (wpat); + return (match_upattern (string, pat, mtype, sp, ep)); + } + ret = match_wpattern (wstring, indices, n, wpat, mtype, sp, ep); + + free (wpat); + free (wstring); + free (indices); + + return (ret); + } + else +#endif + return (match_upattern (string, pat, mtype, sp, ep)); +} + +static int +getpatspec (c, value) + int c; + char *value; +{ + if (c == '#') + return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT); + else /* c == '%' */ + return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT); +} + +/* Posix.2 says that the WORD should be run through tilde expansion, + parameter expansion, command substitution and arithmetic expansion. + This leaves the result quoted, so quote_string_for_globbing () has + to be called to fix it up for strmatch (). If QUOTED is non-zero, + it means that the entire expression was enclosed in double quotes. + This means that quoting characters in the pattern do not make any + special pattern characters quoted. For example, the `*' in the + following retains its special meaning: "${foo#'*'}". */ +static char * +getpattern (value, quoted, expandpat) + char *value; + int quoted, expandpat; +{ + char *pat, *tword; + WORD_LIST *l; +#if 0 + int i; +#endif + /* There is a problem here: how to handle single or double quotes in the + pattern string when the whole expression is between double quotes? + POSIX.2 says that enclosing double quotes do not cause the pattern to + be quoted, but does that leave us a problem with @ and array[@] and their + expansions inside a pattern? */ +#if 0 + if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword) + { + i = 0; + pat = string_extract_double_quoted (tword, &i, SX_STRIPDQ); + free (tword); + tword = pat; + } +#endif + + /* expand_string_for_pat () leaves WORD quoted and does not perform + word splitting. */ + l = *value ? expand_string_for_pat (value, + (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_PATQUOTE : quoted, + (int *)NULL, (int *)NULL) + : (WORD_LIST *)0; + if (l) + word_list_remove_quoted_nulls (l); + pat = string_list (l); + dispose_words (l); + if (pat) + { + tword = quote_string_for_globbing (pat, QGLOB_CVTNULL); + free (pat); + pat = tword; + } + return (pat); +} + +#if 0 +/* Handle removing a pattern from a string as a result of ${name%[%]value} + or ${name#[#]value}. */ +static char * +variable_remove_pattern (value, pattern, patspec, quoted) + char *value, *pattern; + int patspec, quoted; +{ + char *tword; + + tword = remove_pattern (value, pattern, patspec); + + return (tword); +} +#endif + +static char * +list_remove_pattern (list, pattern, patspec, itype, quoted) + WORD_LIST *list; + char *pattern; + int patspec, itype, quoted; +{ + WORD_LIST *new, *l; + WORD_DESC *w; + char *tword; + + for (new = (WORD_LIST *)NULL, l = list; l; l = l->next) + { + tword = remove_pattern (l->word->word, pattern, patspec); + w = alloc_word_desc (); + w->word = tword ? tword : savestring (""); + new = make_word_list (w, new); + } + + l = REVERSE_LIST (new, WORD_LIST *); + tword = string_list_pos_params (itype, l, quoted, 0); + dispose_words (l); + + return (tword); +} + +static char * +parameter_list_remove_pattern (itype, pattern, patspec, quoted) + int itype; + char *pattern; + int patspec, quoted; +{ + char *ret; + WORD_LIST *list; + + list = list_rest_of_args (); + if (list == 0) + return ((char *)NULL); + ret = list_remove_pattern (list, pattern, patspec, itype, quoted); + dispose_words (list); + return (ret); +} + +#if defined (ARRAY_VARS) +static char * +array_remove_pattern (var, pattern, patspec, starsub, quoted) + SHELL_VAR *var; + char *pattern; + int patspec; + int starsub; /* so we can figure out how it's indexed */ + int quoted; +{ + ARRAY *a; + HASH_TABLE *h; + int itype; + char *ret; + WORD_LIST *list; + SHELL_VAR *v; + + v = var; /* XXX - for now */ + + itype = starsub ? '*' : '@'; + + a = (v && array_p (v)) ? array_cell (v) : 0; + h = (v && assoc_p (v)) ? assoc_cell (v) : 0; + + list = a ? array_to_word_list (a) : (h ? assoc_to_word_list (h) : 0); + if (list == 0) + return ((char *)NULL); + ret = list_remove_pattern (list, pattern, patspec, itype, quoted); + dispose_words (list); + + return ret; +} +#endif /* ARRAY_VARS */ + +static char * +parameter_brace_remove_pattern (varname, value, estatep, patstr, rtype, quoted, flags) + char *varname, *value; + array_eltstate_t *estatep; + char *patstr; + int rtype, quoted, flags; +{ + int vtype, patspec, starsub; + char *temp1, *val, *pattern, *oname; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + oname = this_command_name; + this_command_name = varname; + + vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val); + if (vtype == -1) + { + this_command_name = oname; + return ((char *)NULL); + } + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + patspec = getpatspec (rtype, patstr); + if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT) + patstr++; + + /* Need to pass getpattern newly-allocated memory in case of expansion -- + the expansion code will free the passed string on an error. */ + temp1 = savestring (patstr); + pattern = getpattern (temp1, quoted, 1); + free (temp1); + + temp1 = (char *)NULL; /* shut up gcc */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp1 = remove_pattern (val, pattern, patspec); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp1) + { + val = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + ? quote_string (temp1) + : quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp1 = array_remove_pattern (v, pattern, patspec, starsub, quoted); + if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#endif + case VT_POSPARMS: + temp1 = parameter_list_remove_pattern (varname[0], pattern, patspec, quoted); + if (temp1 && quoted == 0 && ifs_is_null) + { + /* Posix interp 888 */ + } + else if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; + } + + this_command_name = oname; + + FREE (pattern); + return temp1; +} + +#if defined (PROCESS_SUBSTITUTION) + +static void reap_some_procsubs PARAMS((int)); + +/*****************************************************************/ +/* */ +/* Hacking Process Substitution */ +/* */ +/*****************************************************************/ + +#if !defined (HAVE_DEV_FD) +/* Named pipes must be removed explicitly with `unlink'. This keeps a list + of FIFOs the shell has open. unlink_fifo_list will walk the list and + unlink the ones that don't have a living process on the other end. + unlink_all_fifos will walk the list and unconditionally unlink them, trying + to open and close the FIFO first to release any child processes sleeping on + the FIFO. add_fifo_list adds the name of an open FIFO to the list. + NFIFO is a count of the number of FIFOs in the list. */ +#define FIFO_INCR 20 + +/* PROC value of -1 means the process has been reaped and the FIFO needs to + be removed. PROC value of 0 means the slot is unused. */ +struct temp_fifo { + char *file; + pid_t proc; +}; + +static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL; +static int nfifo; +static int fifo_list_size; + +void +clear_fifo_list () +{ + int i; + + for (i = 0; i < fifo_list_size; i++) + { + if (fifo_list[i].file) + free (fifo_list[i].file); + fifo_list[i].file = NULL; + fifo_list[i].proc = 0; + } + nfifo = 0; +} + +void * +copy_fifo_list (sizep) + int *sizep; +{ + if (sizep) + *sizep = 0; + return (void *)NULL; +} + +static void +add_fifo_list (pathname) + char *pathname; +{ + int osize, i; + + if (nfifo >= fifo_list_size - 1) + { + osize = fifo_list_size; + fifo_list_size += FIFO_INCR; + fifo_list = (struct temp_fifo *)xrealloc (fifo_list, + fifo_list_size * sizeof (struct temp_fifo)); + for (i = osize; i < fifo_list_size; i++) + { + fifo_list[i].file = (char *)NULL; + fifo_list[i].proc = 0; /* unused */ + } + } + + fifo_list[nfifo].file = savestring (pathname); + nfifo++; +} + +void +unlink_fifo (i) + int i; +{ + if ((fifo_list[i].proc == (pid_t)-1) || (fifo_list[i].proc > 0 && (kill(fifo_list[i].proc, 0) == -1))) + { + unlink (fifo_list[i].file); + free (fifo_list[i].file); + fifo_list[i].file = (char *)NULL; + fifo_list[i].proc = 0; + } +} + +void +unlink_fifo_list () +{ + int saved, i, j; + + if (nfifo == 0) + return; + + for (i = saved = 0; i < nfifo; i++) + { + if ((fifo_list[i].proc == (pid_t)-1) || (fifo_list[i].proc > 0 && (kill(fifo_list[i].proc, 0) == -1))) + { + unlink (fifo_list[i].file); + free (fifo_list[i].file); + fifo_list[i].file = (char *)NULL; + fifo_list[i].proc = 0; + } + else + saved++; + } + + /* If we didn't remove some of the FIFOs, compact the list. */ + if (saved) + { + for (i = j = 0; i < nfifo; i++) + if (fifo_list[i].file) + { + if (i != j) + { + fifo_list[j].file = fifo_list[i].file; + fifo_list[j].proc = fifo_list[i].proc; + fifo_list[i].file = (char *)NULL; + fifo_list[i].proc = 0; + } + j++; + } + nfifo = j; + } + else + nfifo = 0; +} + +void +unlink_all_fifos () +{ + int i, fd; + + if (nfifo == 0) + return; + + for (i = 0; i < nfifo; i++) + { + fifo_list[i].proc = (pid_t)-1; +#if defined (O_NONBLOCK) + fd = open (fifo_list[i].file, O_RDWR|O_NONBLOCK); +#else + fd = -1; +#endif + unlink_fifo (i); + if (fd >= 0) + close (fd); + } + + nfifo = 0; +} + +/* Take LIST, which is a bitmap denoting active FIFOs in fifo_list + from some point in the past, and close all open FIFOs in fifo_list + that are not marked as active in LIST. If LIST is NULL, close + everything in fifo_list. LSIZE is the number of elements in LIST, in + case it's larger than fifo_list_size (size of fifo_list). */ +void +close_new_fifos (list, lsize) + void *list; + int lsize; +{ + int i; + char *plist; + + if (list == 0) + { + unlink_fifo_list (); + return; + } + + for (plist = (char *)list, i = 0; i < lsize; i++) + if (plist[i] == 0 && i < fifo_list_size && fifo_list[i].proc != -1) + unlink_fifo (i); + + for (i = lsize; i < fifo_list_size; i++) + unlink_fifo (i); +} + +int +find_procsub_child (pid) + pid_t pid; +{ + int i; + + for (i = 0; i < nfifo; i++) + if (fifo_list[i].proc == pid) + return i; + return -1; +} + +void +set_procsub_status (ind, pid, status) + int ind; + pid_t pid; + int status; +{ + if (ind >= 0 && ind < nfifo) + fifo_list[ind].proc = (pid_t)-1; /* sentinel */ +} + +/* If we've marked the process for this procsub as dead, close the + associated file descriptor and delete the FIFO. */ +static void +reap_some_procsubs (max) + int max; +{ + int i; + + for (i = 0; i < max; i++) + if (fifo_list[i].proc == (pid_t)-1) /* reaped */ + unlink_fifo (i); +} + +void +reap_procsubs () +{ + reap_some_procsubs (nfifo); +} + +#if 0 +/* UNUSED */ +void +wait_procsubs () +{ + int i, r; + + for (i = 0; i < nfifo; i++) + { + if (fifo_list[i].proc != (pid_t)-1 && fifo_list[i].proc > 0) + { + r = wait_for (fifo_list[i].proc, 0); + save_proc_status (fifo_list[i].proc, r); + fifo_list[i].proc = (pid_t)-1; + } + } +} +#endif + +int +fifos_pending () +{ + return nfifo; +} + +int +num_fifos () +{ + return nfifo; +} + +static char * +make_named_pipe () +{ + char *tname; + + tname = sh_mktmpname ("sh-np", MT_USERANDOM|MT_USETMPDIR); + if (mkfifo (tname, 0600) < 0) + { + free (tname); + return ((char *)NULL); + } + + add_fifo_list (tname); + return (tname); +} + +#else /* HAVE_DEV_FD */ + +/* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell + has open to children. NFDS is a count of the number of bits currently + set in DEV_FD_LIST. TOTFDS is a count of the highest possible number + of open files. */ +/* dev_fd_list[I] value of -1 means the process has been reaped and file + descriptor I needs to be closed. Value of 0 means the slot is unused. */ + +static pid_t *dev_fd_list = (pid_t *)NULL; +static int nfds; +static int totfds; /* The highest possible number of open files. */ + +void +clear_fifo (i) + int i; +{ + if (dev_fd_list[i]) + { + dev_fd_list[i] = 0; + nfds--; + } +} + +void +clear_fifo_list () +{ + register int i; + + if (nfds == 0) + return; + + for (i = 0; nfds && i < totfds; i++) + clear_fifo (i); + + nfds = 0; +} + +void * +copy_fifo_list (sizep) + int *sizep; +{ + void *ret; + + if (nfds == 0 || totfds == 0) + { + if (sizep) + *sizep = 0; + return (void *)NULL; + } + + if (sizep) + *sizep = totfds; + ret = xmalloc (totfds * sizeof (pid_t)); + return (memcpy (ret, dev_fd_list, totfds * sizeof (pid_t))); +} + +static void +add_fifo_list (fd) + int fd; +{ + if (dev_fd_list == 0 || fd >= totfds) + { + int ofds; + + ofds = totfds; + totfds = getdtablesize (); + if (totfds < 0 || totfds > 256) + totfds = 256; + if (fd >= totfds) + totfds = fd + 2; + + dev_fd_list = (pid_t *)xrealloc (dev_fd_list, totfds * sizeof (dev_fd_list[0])); + /* XXX - might need a loop for this */ + memset (dev_fd_list + ofds, '\0', (totfds - ofds) * sizeof (pid_t)); + } + + dev_fd_list[fd] = 1; /* marker; updated later */ + nfds++; +} + +int +fifos_pending () +{ + return 0; /* used for cleanup; not needed with /dev/fd */ +} + +int +num_fifos () +{ + return nfds; +} + +void +unlink_fifo (fd) + int fd; +{ + if (dev_fd_list[fd]) + { + close (fd); + dev_fd_list[fd] = 0; + nfds--; + } +} + +void +unlink_fifo_list () +{ + register int i; + + if (nfds == 0) + return; + + for (i = totfds-1; nfds && i >= 0; i--) + unlink_fifo (i); + + nfds = 0; +} + +void +unlink_all_fifos () +{ + unlink_fifo_list (); +} + +/* Take LIST, which is a snapshot copy of dev_fd_list from some point in + the past, and close all open fds in dev_fd_list that are not marked + as open in LIST. If LIST is NULL, close everything in dev_fd_list. + LSIZE is the number of elements in LIST, in case it's larger than + totfds (size of dev_fd_list). */ +void +close_new_fifos (list, lsize) + void *list; + int lsize; +{ + int i; + pid_t *plist; + + if (list == 0) + { + unlink_fifo_list (); + return; + } + + for (plist = (pid_t *)list, i = 0; i < lsize; i++) + if (plist[i] == 0 && i < totfds && dev_fd_list[i]) + unlink_fifo (i); + + for (i = lsize; i < totfds; i++) + unlink_fifo (i); +} + +int +find_procsub_child (pid) + pid_t pid; +{ + int i; + + if (nfds == 0) + return -1; + + for (i = 0; i < totfds; i++) + if (dev_fd_list[i] == pid) + return i; + + return -1; +} + +void +set_procsub_status (ind, pid, status) + int ind; + pid_t pid; + int status; +{ + if (ind >= 0 && ind < totfds) + dev_fd_list[ind] = (pid_t)-1; /* sentinel */ +} + +/* If we've marked the process for this procsub as dead, close the + associated file descriptor. */ +static void +reap_some_procsubs (max) + int max; +{ + int i; + + for (i = 0; nfds > 0 && i < max; i++) + if (dev_fd_list[i] == (pid_t)-1) + unlink_fifo (i); +} + +void +reap_procsubs () +{ + reap_some_procsubs (totfds); +} + +#if 0 +/* UNUSED */ +void +wait_procsubs () +{ + int i, r; + + for (i = 0; nfds > 0 && i < totfds; i++) + { + if (dev_fd_list[i] != (pid_t)-1 && dev_fd_list[i] > 0) + { + r = wait_for (dev_fd_list[i], 0); + save_proc_status (dev_fd_list[i], r); + dev_fd_list[i] = (pid_t)-1; + } + } +} +#endif + +#if defined (NOTDEF) +print_dev_fd_list () +{ + register int i; + + fprintf (stderr, "pid %ld: dev_fd_list:", (long)getpid ()); + fflush (stderr); + + for (i = 0; i < totfds; i++) + { + if (dev_fd_list[i]) + fprintf (stderr, " %d", i); + } + fprintf (stderr, "\n"); +} +#endif /* NOTDEF */ + +static char * +make_dev_fd_filename (fd) + int fd; +{ + char *ret, intbuf[INT_STRLEN_BOUND (int) + 1], *p; + + ret = (char *)xmalloc (sizeof (DEV_FD_PREFIX) + 8); + + strcpy (ret, DEV_FD_PREFIX); + p = inttostr (fd, intbuf, sizeof (intbuf)); + strcpy (ret + sizeof (DEV_FD_PREFIX) - 1, p); + + add_fifo_list (fd); + return (ret); +} + +#endif /* HAVE_DEV_FD */ + +/* Return a filename that will open a connection to the process defined by + executing STRING. HAVE_DEV_FD, if defined, means open a pipe and return + a filename in /dev/fd corresponding to a descriptor that is one of the + ends of the pipe. If not defined, we use named pipes on systems that have + them. Systems without /dev/fd and named pipes are out of luck. + + OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or + use the read end of the pipe and dup that file descriptor to fd 0 in + the child. If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for + writing or use the write end of the pipe in the child, and dup that + file descriptor to fd 1 in the child. The parent does the opposite. */ + +static char * +process_substitute (string, open_for_read_in_child) + char *string; + int open_for_read_in_child; +{ + char *pathname; + int fd, result, rc, function_value; + pid_t old_pid, pid; +#if defined (HAVE_DEV_FD) + int parent_pipe_fd, child_pipe_fd; + int fildes[2]; +#endif /* HAVE_DEV_FD */ +#if defined (JOB_CONTROL) + pid_t old_pipeline_pgrp; +#endif + + if (!string || !*string || wordexp_only) + return ((char *)NULL); + +#if !defined (HAVE_DEV_FD) + pathname = make_named_pipe (); +#else /* HAVE_DEV_FD */ + if (pipe (fildes) < 0) + { + sys_error ("%s", _("cannot make pipe for process substitution")); + return ((char *)NULL); + } + /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of + the pipe in the parent, otherwise the read end. */ + parent_pipe_fd = fildes[open_for_read_in_child]; + child_pipe_fd = fildes[1 - open_for_read_in_child]; + /* Move the parent end of the pipe to some high file descriptor, to + avoid clashes with FDs used by the script. */ + parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64); + + pathname = make_dev_fd_filename (parent_pipe_fd); +#endif /* HAVE_DEV_FD */ + + if (pathname == 0) + { + sys_error ("%s", _("cannot make pipe for process substitution")); + return ((char *)NULL); + } + + old_pid = last_made_pid; + +#if defined (JOB_CONTROL) + old_pipeline_pgrp = pipeline_pgrp; + if (pipeline_pgrp == 0 || (subshell_environment & (SUBSHELL_PIPE|SUBSHELL_FORK|SUBSHELL_ASYNC)) == 0) + pipeline_pgrp = shell_pgrp; + save_pipeline (1); +#endif /* JOB_CONTROL */ + + pid = make_child ((char *)NULL, FORK_ASYNC); + if (pid == 0) + { +#if 0 + int old_interactive; + + old_interactive = interactive; +#endif + /* The currently-executing shell is not interactive */ + interactive = 0; + + reset_terminating_signals (); /* XXX */ + free_pushed_string_input (); + /* Cancel traps, in trap.c. */ + restore_original_signals (); /* XXX - what about special builtins? bash-4.2 */ + subshell_environment &= ~SUBSHELL_IGNTRAP; + QUIT; /* catch any interrupts we got post-fork */ + setup_async_signals (); +#if 0 + if (open_for_read_in_child == 0 && old_interactive && (bash_input.type == st_stdin || bash_input.type == st_stream)) + async_redirect_stdin (); +#endif + + subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB|SUBSHELL_ASYNC; + + /* We don't inherit the verbose option for command substitutions now, so + let's try it for process substitutions. */ + change_flag ('v', FLAG_OFF); + + /* if we're expanding a redirection, we shouldn't have access to the + temporary environment, but commands in the subshell should have + access to their own temporary environment. */ + if (expanding_redir) + flush_temporary_env (); + } + +#if defined (JOB_CONTROL) + set_sigchld_handler (); + stop_making_children (); + /* XXX - should we only do this in the parent? (as in command subst) */ + pipeline_pgrp = old_pipeline_pgrp; +#else + stop_making_children (); +#endif /* JOB_CONTROL */ + + if (pid < 0) + { + sys_error ("%s", _("cannot make child for process substitution")); + free (pathname); +#if defined (HAVE_DEV_FD) + close (parent_pipe_fd); + close (child_pipe_fd); +#endif /* HAVE_DEV_FD */ +#if defined (JOB_CONTROL) + restore_pipeline (1); +#endif + return ((char *)NULL); + } + + if (pid > 0) + { +#if defined (JOB_CONTROL) + last_procsub_child = restore_pipeline (0); + /* We assume that last_procsub_child->next == last_procsub_child because + of how jobs.c:add_process() works. */ + last_procsub_child->next = 0; + procsub_add (last_procsub_child); +#endif + +#if defined (HAVE_DEV_FD) + dev_fd_list[parent_pipe_fd] = pid; +#else + fifo_list[nfifo-1].proc = pid; +#endif + + last_made_pid = old_pid; + +#if defined (JOB_CONTROL) && defined (PGRP_PIPE) + close_pgrp_pipe (); +#endif /* JOB_CONTROL && PGRP_PIPE */ + +#if defined (HAVE_DEV_FD) + close (child_pipe_fd); +#endif /* HAVE_DEV_FD */ + + return (pathname); + } + + set_sigint_handler (); + +#if defined (JOB_CONTROL) + /* make sure we don't have any job control */ + set_job_control (0); + + /* Clear out any existing list of process substitutions */ + procsub_clear (); + + /* The idea is that we want all the jobs we start from an async process + substitution to be in the same process group, but not the same pgrp + as our parent shell, since we don't want to affect our parent shell's + jobs if we get a SIGHUP and end up calling hangup_all_jobs, for example. + If pipeline_pgrp != shell_pgrp, we assume that there is a job control + shell somewhere in our parent process chain (since make_child initializes + pipeline_pgrp to shell_pgrp if job_control == 0). What we do in this + case is to set pipeline_pgrp to our PID, so all jobs started by this + process have that same pgrp and we are basically the process group leader. + This should not have negative effects on child processes surviving + after we exit, since we wait for the children we create, but that is + something to watch for. */ + + if (pipeline_pgrp != shell_pgrp) + pipeline_pgrp = getpid (); +#endif /* JOB_CONTROL */ + +#if !defined (HAVE_DEV_FD) + /* Open the named pipe in the child. */ + fd = open (pathname, open_for_read_in_child ? O_RDONLY : O_WRONLY); + if (fd < 0) + { + /* Two separate strings for ease of translation. */ + if (open_for_read_in_child) + sys_error (_("cannot open named pipe %s for reading"), pathname); + else + sys_error (_("cannot open named pipe %s for writing"), pathname); + + exit (127); + } + if (open_for_read_in_child) + { + if (sh_unset_nodelay_mode (fd) < 0) + { + sys_error (_("cannot reset nodelay mode for fd %d"), fd); + exit (127); + } + } +#else /* HAVE_DEV_FD */ + fd = child_pipe_fd; +#endif /* HAVE_DEV_FD */ + + /* Discard buffered stdio output before replacing the underlying file + descriptor. */ + if (open_for_read_in_child == 0) + fpurge (stdout); + + if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0) + { + sys_error (_("cannot duplicate named pipe %s as fd %d"), pathname, + open_for_read_in_child ? 0 : 1); + exit (127); + } + + if (fd != (open_for_read_in_child ? 0 : 1)) + close (fd); + + /* Need to close any files that this process has open to pipes inherited + from its parent. */ + if (current_fds_to_close) + { + close_fd_bitmap (current_fds_to_close); + current_fds_to_close = (struct fd_bitmap *)NULL; + } + +#if defined (HAVE_DEV_FD) + /* Make sure we close the parent's end of the pipe and clear the slot + in the fd list so it is not closed later, if reallocated by, for + instance, pipe(2). */ + close (parent_pipe_fd); + dev_fd_list[parent_pipe_fd] = 0; +#endif /* HAVE_DEV_FD */ + + /* subshells shouldn't have this flag, which controls using the temporary + environment for variable lookups. We have already flushed the temporary + environment above in the case we're expanding a redirection, so processes + executed by this command need to be able to set it independently of their + parent. */ + expanding_redir = 0; + + remove_quoted_escapes (string); + + startup_state = 2; /* see if we can avoid a fork */ + parse_and_execute_level = 0; + + /* Give process substitution a place to jump back to on failure, + so we don't go back up to main (). */ + result = setjmp_nosigs (top_level); + + /* If we're running a process substitution inside a shell function, + trap `return' so we don't return from the function in the subshell + and go off to never-never land. */ + if (result == 0 && return_catch_flag) + function_value = setjmp_nosigs (return_catch); + else + function_value = 0; + + if (result == ERREXIT) + rc = last_command_exit_value; + else if (result == EXITPROG || result == EXITBLTIN) + rc = last_command_exit_value; + else if (result) + rc = EXECUTION_FAILURE; + else if (function_value) + rc = return_catch_value; + else + { + subshell_level++; + rc = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST)); + /* leave subshell level intact for any exit trap */ + } + +#if !defined (HAVE_DEV_FD) + /* Make sure we close the named pipe in the child before we exit. */ + close (open_for_read_in_child ? 0 : 1); +#endif /* !HAVE_DEV_FD */ + + last_command_exit_value = rc; + rc = run_exit_trap (); + exit (rc); + /*NOTREACHED*/ +} +#endif /* PROCESS_SUBSTITUTION */ + +/***********************************/ +/* */ +/* Command Substitution */ +/* */ +/***********************************/ + +#define COMSUB_PIPEBUF 4096 + +static char * +optimize_cat_file (r, quoted, flags, flagp) + REDIRECT *r; + int quoted, flags, *flagp; +{ + char *ret; + int fd; + + fd = open_redir_file (r, (char **)0); + if (fd < 0) + return &expand_param_error; + + ret = read_comsub (fd, quoted, flags, flagp); + close (fd); + + return ret; +} + +static char * +read_comsub (fd, quoted, flags, rflag) + int fd, quoted, flags; + int *rflag; +{ + char *istring, buf[COMSUB_PIPEBUF], *bufp; + int c, tflag, skip_ctlesc, skip_ctlnul; + int mb_cur_max; + size_t istring_index; + size_t istring_size; + ssize_t bufn; + int nullbyte; +#if defined (HANDLE_MULTIBYTE) + mbstate_t ps; + wchar_t wc; + size_t mblen; + int i; +#endif + + istring = (char *)NULL; + istring_index = istring_size = bufn = tflag = 0; + + skip_ctlesc = ifs_cmap[CTLESC]; + skip_ctlnul = ifs_cmap[CTLNUL]; + + mb_cur_max = MB_CUR_MAX; + nullbyte = 0; + + /* Read the output of the command through the pipe. */ + while (1) + { + if (fd < 0) + break; + if (--bufn <= 0) + { + bufn = zread (fd, buf, sizeof (buf)); + if (bufn <= 0) + break; + bufp = buf; + } + c = *bufp++; + + if (c == 0) + { +#if 1 + if (nullbyte == 0) + { + internal_warning ("%s", _("command substitution: ignored null byte in input")); + nullbyte = 1; + } +#endif + continue; + } + + /* Add the character to ISTRING, possibly after resizing it. */ + RESIZE_MALLOCED_BUFFER (istring, istring_index, mb_cur_max+1, istring_size, 512); + + /* This is essentially quote_string inline */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) /* || c == CTLESC || c == CTLNUL */) + istring[istring_index++] = CTLESC; + else if ((flags & PF_ASSIGNRHS) && skip_ctlesc && c == CTLESC) + istring[istring_index++] = CTLESC; + /* Escape CTLESC and CTLNUL in the output to protect those characters + from the rest of the word expansions (word splitting and globbing.) + This is essentially quote_escapes inline. */ + else if (skip_ctlesc == 0 && c == CTLESC) + istring[istring_index++] = CTLESC; + else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0))) + istring[istring_index++] = CTLESC; + +#if defined (HANDLE_MULTIBYTE) + if ((locale_utf8locale && (c & 0x80)) || + (locale_utf8locale == 0 && mb_cur_max > 1 && (unsigned char)c > 127)) + { + /* read a multibyte character from buf */ + /* punt on the hard case for now */ + memset (&ps, '\0', sizeof (mbstate_t)); + mblen = mbrtowc (&wc, bufp-1, bufn, &ps); + if (MB_INVALIDCH (mblen) || mblen == 0 || mblen == 1) + istring[istring_index++] = c; + else + { + istring[istring_index++] = c; + for (i = 0; i < mblen-1; i++) + istring[istring_index++] = *bufp++; + bufn -= mblen - 1; + } + continue; + } +#endif + + istring[istring_index++] = c; + } + + if (istring) + istring[istring_index] = '\0'; + + /* If we read no output, just return now and save ourselves some + trouble. */ + if (istring_index == 0) + { + FREE (istring); + if (rflag) + *rflag = tflag; + return (char *)NULL; + } + + /* Strip trailing newlines from the output of the command. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + { + while (istring_index > 0) + { + if (istring[istring_index - 1] == '\n') + { + --istring_index; + + /* If the newline was quoted, remove the quoting char. */ + if (istring[istring_index - 1] == CTLESC) + --istring_index; + } + else + break; + } + istring[istring_index] = '\0'; + } + else + strip_trailing (istring, istring_index - 1, 1); + + if (rflag) + *rflag = tflag; + return istring; +} + +/* Perform command substitution on STRING. This returns a WORD_DESC * with the + contained string possibly quoted. */ +WORD_DESC * +command_substitute (string, quoted, flags) + char *string; + int quoted; + int flags; +{ + pid_t pid, old_pid, old_pipeline_pgrp, old_async_pid; + char *istring, *s; + int result, fildes[2], function_value, pflags, rc, tflag, fork_flags; + WORD_DESC *ret; + sigset_t set, oset; + + istring = (char *)NULL; + + /* Don't fork () if there is no need to. In the case of no command to + run, just return NULL. */ + for (s = string; s && *s && (shellblank (*s) || *s == '\n'); s++) + ; + if (s == 0 || *s == 0) + return ((WORD_DESC *)NULL); + + if (*s == '<' && (s[1] != '<' && s[1] != '>' && s[1] != '&')) + { + COMMAND *cmd; + + cmd = parse_string_to_command (string, 0); /* XXX - flags */ + if (cmd && can_optimize_cat_file (cmd)) + { + tflag = 0; + istring = optimize_cat_file (cmd->value.Simple->redirects, quoted, flags, &tflag); + if (istring == &expand_param_error) + { + last_command_exit_value = EXECUTION_FAILURE; + istring = 0; + } + else + last_command_exit_value = EXECUTION_SUCCESS; /* compat */ + last_command_subst_pid = dollar_dollar_pid; + + dispose_command (cmd); + ret = alloc_word_desc (); + ret->word = istring; + ret->flags = tflag; + + return ret; + } + dispose_command (cmd); + } + + if (wordexp_only && read_but_dont_execute) + { + last_command_exit_value = EX_WEXPCOMSUB; + jump_to_top_level (EXITPROG); + } + + /* We're making the assumption here that the command substitution will + eventually run a command from the file system. Since we'll run + maybe_make_export_env in this subshell before executing that command, + the parent shell and any other shells it starts will have to remake + the environment. If we make it before we fork, other shells won't + have to. Don't bother if we have any temporary variable assignments, + though, because the export environment will be remade after this + command completes anyway, but do it if all the words to be expanded + are variable assignments. */ + if (subst_assign_varlist == 0 || garglist == 0) + maybe_make_export_env (); /* XXX */ + + /* Flags to pass to parse_and_execute() */ + pflags = (interactive && sourcelevel == 0) ? SEVAL_RESETLINE : 0; + + old_pid = last_made_pid; + + /* Pipe the output of executing STRING into the current shell. */ + if (pipe (fildes) < 0) + { + sys_error ("%s", _("cannot make pipe for command substitution")); + goto error_exit; + } + +#if defined (JOB_CONTROL) + old_pipeline_pgrp = pipeline_pgrp; + /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline or + we've already forked to run a disk command (and are expanding redirections, + for example). */ + if ((subshell_environment & (SUBSHELL_FORK|SUBSHELL_PIPE)) == 0) + pipeline_pgrp = shell_pgrp; + cleanup_the_pipeline (); +#endif /* JOB_CONTROL */ + + old_async_pid = last_asynchronous_pid; + fork_flags = (subshell_environment&SUBSHELL_ASYNC) ? FORK_ASYNC : 0; + pid = make_child ((char *)NULL, fork_flags|FORK_NOTERM); + last_asynchronous_pid = old_async_pid; + + if (pid == 0) + { + /* Reset the signal handlers in the child, but don't free the + trap strings. Set a flag noting that we have to free the + trap strings if we run trap to change a signal disposition. */ + reset_signal_handlers (); + if (ISINTERRUPT) + { + kill (getpid (), SIGINT); + CLRINTERRUPT; /* if we're ignoring SIGINT somehow */ + } + QUIT; /* catch any interrupts we got post-fork */ + subshell_environment |= SUBSHELL_RESETTRAP; + subshell_environment &= ~SUBSHELL_IGNTRAP; + } + +#if defined (JOB_CONTROL) + /* XXX DO THIS ONLY IN PARENT ? XXX */ + set_sigchld_handler (); + stop_making_children (); + if (pid != 0) + pipeline_pgrp = old_pipeline_pgrp; +#else + stop_making_children (); +#endif /* JOB_CONTROL */ + + if (pid < 0) + { + sys_error (_("cannot make child for command substitution")); + error_exit: + + last_made_pid = old_pid; + + FREE (istring); + close (fildes[0]); + close (fildes[1]); + return ((WORD_DESC *)NULL); + } + + if (pid == 0) + { + /* The currently executing shell is not interactive. */ + interactive = 0; + +#if defined (JOB_CONTROL) + /* Invariant: in child processes started to run command substitutions, + pipeline_pgrp == shell_pgrp. Other parts of the shell assume this. */ + if (pipeline_pgrp > 0 && pipeline_pgrp != shell_pgrp) + shell_pgrp = pipeline_pgrp; +#endif + + set_sigint_handler (); /* XXX */ + + free_pushed_string_input (); + + /* Discard buffered stdio output before replacing the underlying file + descriptor. */ + fpurge (stdout); + + if (dup2 (fildes[1], 1) < 0) + { + sys_error ("%s", _("command_substitute: cannot duplicate pipe as fd 1")); + exit (EXECUTION_FAILURE); + } + + /* If standard output is closed in the parent shell + (such as after `exec >&-'), file descriptor 1 will be + the lowest available file descriptor, and end up in + fildes[0]. This can happen for stdin and stderr as well, + but stdout is more important -- it will cause no output + to be generated from this command. */ + if ((fildes[1] != fileno (stdin)) && + (fildes[1] != fileno (stdout)) && + (fildes[1] != fileno (stderr))) + close (fildes[1]); + + if ((fildes[0] != fileno (stdin)) && + (fildes[0] != fileno (stdout)) && + (fildes[0] != fileno (stderr))) + close (fildes[0]); + +#ifdef __CYGWIN__ + /* Let stdio know the fd may have changed from text to binary mode, and + make sure to preserve stdout line buffering. */ + freopen (NULL, "w", stdout); + sh_setlinebuf (stdout); +#endif /* __CYGWIN__ */ + + /* This is a subshell environment. */ + subshell_environment |= SUBSHELL_COMSUB; + + /* Many shells do not appear to inherit the -v option for command + substitutions. */ + change_flag ('v', FLAG_OFF); + + /* When inherit_errexit option is not enabled, command substitution does + not inherit the -e flag. It is enabled when Posix mode is enabled */ + if (inherit_errexit == 0) + { + builtin_ignoring_errexit = 0; + change_flag ('e', FLAG_OFF); + } + set_shellopts (); + + /* If we are expanding a redirection, we can dispose of any temporary + environment we received, since redirections are not supposed to have + access to the temporary environment. We will have to see whether this + affects temporary environments supplied to `eval', but the temporary + environment gets copied to builtin_env at some point. */ + if (expanding_redir) + { + flush_temporary_env (); + expanding_redir = 0; + } + + remove_quoted_escapes (string); + + /* We want to expand aliases on this pass if we are not in posix mode + for backwards compatibility. */ + if (expand_aliases) + expand_aliases = posixly_correct == 0; + + startup_state = 2; /* see if we can avoid a fork */ + parse_and_execute_level = 0; + + /* Give command substitution a place to jump back to on failure, + so we don't go back up to main (). */ + result = setjmp_nosigs (top_level); + + /* If we're running a command substitution inside a shell function, + trap `return' so we don't return from the function in the subshell + and go off to never-never land. */ + if (result == 0 && return_catch_flag) + function_value = setjmp_nosigs (return_catch); + else + function_value = 0; + + if (result == ERREXIT) + rc = last_command_exit_value; + else if (result == EXITPROG || result == EXITBLTIN) + rc = last_command_exit_value; + else if (result) + rc = EXECUTION_FAILURE; + else if (function_value) + rc = return_catch_value; + else + { + subshell_level++; + rc = parse_and_execute (string, "command substitution", pflags|SEVAL_NOHIST); + /* leave subshell level intact for any exit trap */ + } + + last_command_exit_value = rc; + rc = run_exit_trap (); +#if defined (PROCESS_SUBSTITUTION) + unlink_fifo_list (); +#endif + exit (rc); + } + else + { + int dummyfd; + +#if defined (JOB_CONTROL) && defined (PGRP_PIPE) + close_pgrp_pipe (); +#endif /* JOB_CONTROL && PGRP_PIPE */ + + close (fildes[1]); + + begin_unwind_frame ("read-comsub"); + dummyfd = fildes[0]; + add_unwind_protect (close, dummyfd); + + /* Block SIGINT while we're reading from the pipe. If the child + process gets a SIGINT, it will either handle it or die, and the + read will return. */ + BLOCK_SIGNAL (SIGINT, set, oset); + tflag = 0; + istring = read_comsub (fildes[0], quoted, flags, &tflag); + + close (fildes[0]); + discard_unwind_frame ("read-comsub"); + UNBLOCK_SIGNAL (oset); + + current_command_subst_pid = pid; + last_command_exit_value = wait_for (pid, JWAIT_NOTERM); + last_command_subst_pid = pid; + last_made_pid = old_pid; + +#if defined (JOB_CONTROL) + /* If last_command_exit_value > 128, then the substituted command + was terminated by a signal. If that signal was SIGINT, then send + SIGINT to ourselves. This will break out of loops, for instance. */ + if (last_command_exit_value == (128 + SIGINT) && last_command_exit_signal == SIGINT) + kill (getpid (), SIGINT); +#endif /* JOB_CONTROL */ + + ret = alloc_word_desc (); + ret->word = istring; + ret->flags = tflag; + + return ret; + } +} + +/******************************************************** + * * + * Utility functions for parameter expansion * + * * + ********************************************************/ + +#if defined (ARRAY_VARS) + +static arrayind_t +array_length_reference (s) + char *s; +{ + int len; + arrayind_t ind; + char *akey; + char *t, c; + ARRAY *array; + HASH_TABLE *h; + SHELL_VAR *var; + + var = array_variable_part (s, 0, &t, &len); + + /* If unbound variables should generate an error, report one and return + failure. */ + if ((var == 0 || invisible_p (var) || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error) + { + c = *--t; + *t = '\0'; + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (s); + *t = c; + return (-1); + } + else if (var == 0 || invisible_p (var)) + return 0; + + /* We support a couple of expansions for variables that are not arrays. + We'll return the length of the value for v[0], and 1 for v[@] or + v[*]. Return 0 for everything else. */ + + array = array_p (var) ? array_cell (var) : (ARRAY *)NULL; + h = assoc_p (var) ? assoc_cell (var) : (HASH_TABLE *)NULL; + + if (ALL_ELEMENT_SUB (t[0]) && t[1] == RBRACK) + { + if (assoc_p (var)) + return (h ? assoc_num_elements (h) : 0); + else if (array_p (var)) + return (array ? array_num_elements (array) : 0); + else + return (var_isset (var) ? 1 : 0); + } + + if (assoc_p (var)) + { + t[len - 1] = '\0'; + akey = expand_subscript_string (t, 0); /* [ */ + t[len - 1] = RBRACK; + if (akey == 0 || *akey == 0) + { + err_badarraysub (t); + FREE (akey); + return (-1); + } + t = assoc_reference (assoc_cell (var), akey); + free (akey); + } + else + { + ind = array_expand_index (var, t, len, 0); + /* negative subscripts to indexed arrays count back from end */ + if (var && array_p (var) && ind < 0) + ind = array_max_index (array_cell (var)) + 1 + ind; + if (ind < 0) + { + err_badarraysub (t); + return (-1); + } + if (array_p (var)) + t = array_reference (array, ind); + else + t = (ind == 0) ? value_cell (var) : (char *)NULL; + } + + len = MB_STRLEN (t); + return (len); +} +#endif /* ARRAY_VARS */ + +static int +valid_brace_expansion_word (name, var_is_special) + char *name; + int var_is_special; +{ + if (DIGIT (*name) && all_digits (name)) + return 1; + else if (var_is_special) + return 1; +#if defined (ARRAY_VARS) + else if (valid_array_reference (name, 0)) + return 1; +#endif /* ARRAY_VARS */ + else if (legal_identifier (name)) + return 1; + else + return 0; +} + +static int +chk_atstar (name, quoted, pflags, quoted_dollar_atp, contains_dollar_at) + char *name; + int quoted, pflags; + int *quoted_dollar_atp, *contains_dollar_at; +{ + char *temp1; + + if (name == 0) + { + if (quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + return 0; + } + + /* check for $@ and $* */ + if (name[0] == '@' && name[1] == 0) + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + else if (name[0] == '*' && name[1] == '\0' && quoted == 0) + { + /* Need more checks here that parallel what string_list_pos_params and + param_expand do. Check expand_no_split_dollar_star and ??? */ + if (contains_dollar_at && expand_no_split_dollar_star == 0) + *contains_dollar_at = 1; + return 1; + } + + /* Now check for ${array[@]} and ${array[*]} */ +#if defined (ARRAY_VARS) + else if (valid_array_reference (name, 0)) + { + temp1 = mbschr (name, LBRACK); + if (temp1 && temp1[1] == '@' && temp1[2] == RBRACK) + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + /* ${array[*]}, when unquoted, should be treated like ${array[@]}, + which should result in separate words even when IFS is unset. */ + if (temp1 && temp1[1] == '*' && temp1[2] == RBRACK && quoted == 0) + { + if (contains_dollar_at) + *contains_dollar_at = 1; + return 1; + } + } +#endif + return 0; +} + +/* Parameter expand NAME, and return a new string which is the expansion, + or NULL if there was no expansion. NAME is as given in ${NAMEcWORD}. + VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in + the shell, e.g., "@", "$", "*", etc. QUOTED, if non-zero, means that + NAME was found inside of a double-quoted expression. */ +static WORD_DESC * +parameter_brace_expand_word (name, var_is_special, quoted, pflags, estatep) + char *name; + int var_is_special, quoted, pflags; + array_eltstate_t *estatep; +{ + WORD_DESC *ret; + char *temp, *tt; + intmax_t arg_index; + SHELL_VAR *var; + int rflags; + array_eltstate_t es; + + ret = 0; + temp = 0; + rflags = 0; + +#if defined (ARRAY_VARS) + if (estatep) + es = *estatep; /* structure copy */ + else + { + init_eltstate (&es); + es.ind = INTMAX_MIN; + } +#endif + + /* Handle multiple digit arguments, as in ${11}. */ + if (legal_number (name, &arg_index)) + { + tt = get_dollar_var_value (arg_index); + if (tt) + temp = (*tt && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (tt) + : quote_escapes (tt); + else + temp = (char *)NULL; + FREE (tt); + } + else if (var_is_special) /* ${@} */ + { + int sindex; + tt = (char *)xmalloc (2 + strlen (name)); + tt[sindex = 0] = '$'; + strcpy (tt + 1, name); + + ret = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL, + (int *)NULL, (int *)NULL, pflags); + + /* Make sure we note that we saw a quoted null string and pass the flag back + to the caller in addition to the value. */ + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && STR_DOLLAR_AT_STAR (name) && + ret && ret->word && QUOTED_NULL (ret->word)) + ret->flags |= W_HASQUOTEDNULL; + + free (tt); + } +#if defined (ARRAY_VARS) + else if (valid_array_reference (name, 0)) + { +expand_arrayref: + var = array_variable_part (name, 0, &tt, (int *)0); + /* These are the cases where word splitting will not be performed */ + if (pflags & PF_ASSIGNRHS) + { + if (ALL_ELEMENT_SUB (tt[0]) && tt[1] == RBRACK) + { + /* Only treat as double quoted if array variable */ + if (var && (array_p (var) || assoc_p (var))) + temp = array_value (name, quoted|Q_DOUBLE_QUOTES, AV_ASSIGNRHS, &es); + else + temp = array_value (name, quoted, 0, &es); + } + else + temp = array_value (name, quoted, 0, &es); + } + /* Posix interp 888 */ + else if (pflags & PF_NOSPLIT2) + { + /* Special cases, then general case, for each of A[@], A[*], A[n] */ +#if defined (HANDLE_MULTIBYTE) + if (tt[0] == '@' && tt[1] == RBRACK && var && quoted == 0 && ifs_is_set && ifs_is_null == 0 && ifs_firstc[0] != ' ') +#else + if (tt[0] == '@' && tt[1] == RBRACK && var && quoted == 0 && ifs_is_set && ifs_is_null == 0 && ifs_firstc != ' ') +#endif + temp = array_value (name, Q_DOUBLE_QUOTES, AV_ASSIGNRHS, &es); + else if (tt[0] == '@' && tt[1] == RBRACK) + temp = array_value (name, quoted, 0, &es); + else if (tt[0] == '*' && tt[1] == RBRACK && expand_no_split_dollar_star && ifs_is_null) + temp = array_value (name, Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT, 0, &es); + else if (tt[0] == '*' && tt[1] == RBRACK) + temp = array_value (name, quoted, 0, &es); + else + temp = array_value (name, quoted, 0, &es); + } + else if (tt[0] == '*' && tt[1] == RBRACK && expand_no_split_dollar_star && ifs_is_null) + temp = array_value (name, Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT, 0, &es); + else + temp = array_value (name, quoted, 0, &es); + if (es.subtype == 0 && temp) + { + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); + rflags |= W_ARRAYIND; + if (estatep) + *estatep = es; /* structure copy */ + } + /* Note that array[*] and array[@] expanded to a quoted null string by + returning the W_HASQUOTEDNULL flag to the caller in addition to TEMP. */ + else if (es.subtype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + rflags |= W_HASQUOTEDNULL; + else if (es.subtype == 2 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + rflags |= W_HASQUOTEDNULL; + + if (estatep == 0) + flush_eltstate (&es); + } +#endif + else if (var = find_variable (name)) + { + if (var_isset (var) && invisible_p (var) == 0) + { +#if defined (ARRAY_VARS) + /* We avoid a memory leak by saving TT as the memory allocated by + assoc_to_string or array_to_string and leaving it 0 otherwise, + then freeing TT after quoting temp. */ + tt = (char *)NULL; + if ((pflags & PF_ALLINDS) && assoc_p (var)) + tt = temp = assoc_empty (assoc_cell (var)) ? (char *)NULL : assoc_to_string (assoc_cell (var), " ", quoted); + else if ((pflags & PF_ALLINDS) && array_p (var)) + tt = temp = array_empty (array_cell (var)) ? (char *)NULL : array_to_string (array_cell (var), " ", quoted); + else if (assoc_p (var)) + temp = assoc_reference (assoc_cell (var), "0"); + else if (array_p (var)) + temp = array_reference (array_cell (var), 0); + else + temp = value_cell (var); +#else + temp = value_cell (var); +#endif + + if (temp) + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : ((pflags & PF_ASSIGNRHS) ? quote_rhs (temp) + : quote_escapes (temp)); + FREE (tt); + } + else + temp = (char *)NULL; + } + else if (var = find_variable_last_nameref (name, 0)) + { + temp = nameref_cell (var); +#if defined (ARRAY_VARS) + /* Handle expanding nameref whose value is x[n] */ + if (temp && *temp && valid_array_reference (temp, 0)) + { + name = temp; + goto expand_arrayref; + } + else +#endif + /* y=2 ; typeset -n x=y; echo ${x} is not the same as echo ${2} in ksh */ + if (temp && *temp && legal_identifier (temp) == 0) + { + set_exit_status (EXECUTION_FAILURE); + report_error (_("%s: invalid variable name for name reference"), temp); + temp = &expand_param_error; + } + else + temp = (char *)NULL; + } + else + temp = (char *)NULL; + + if (ret == 0) + { + ret = alloc_word_desc (); + ret->word = temp; + ret->flags |= rflags; + } + return ret; +} + +static char * +parameter_brace_find_indir (name, var_is_special, quoted, find_nameref) + char *name; + int var_is_special, quoted, find_nameref; +{ + char *temp, *t; + WORD_DESC *w; + SHELL_VAR *v; + int pflags, oldex; + + if (find_nameref && var_is_special == 0 && (v = find_variable_last_nameref (name, 0)) && + nameref_p (v) && (t = nameref_cell (v)) && *t) + return (savestring (t)); + + /* If var_is_special == 0, and name is not an array reference, this does + more expansion than necessary. It should really look up the variable's + value and not try to expand it. */ + pflags = PF_IGNUNBOUND; + /* Note that we're not going to be doing word splitting here */ + if (var_is_special) + { + pflags |= PF_ASSIGNRHS; /* suppresses word splitting */ + oldex = expand_no_split_dollar_star; + expand_no_split_dollar_star = 1; + } + w = parameter_brace_expand_word (name, var_is_special, quoted, pflags, 0); + if (var_is_special) + expand_no_split_dollar_star = oldex; + + t = w->word; + /* Have to dequote here if necessary */ + if (t) + { + temp = ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || var_is_special) + ? dequote_string (t) + : dequote_escapes (t); + free (t); + t = temp; + } + dispose_word_desc (w); + + return t; +} + +/* Expand an indirect reference to a variable: ${!NAME} expands to the + value of the variable whose name is the value of NAME. */ +static WORD_DESC * +parameter_brace_expand_indir (name, var_is_special, quoted, pflags, quoted_dollar_atp, contains_dollar_at) + char *name; + int var_is_special, quoted, pflags; + int *quoted_dollar_atp, *contains_dollar_at; +{ + char *t; + WORD_DESC *w; + SHELL_VAR *v; + + /* See if it's a nameref first, behave in ksh93-compatible fashion. + There is at least one incompatibility: given ${!foo[0]} where foo=bar, + bash performs an indirect lookup on foo[0] and expands the result; + ksh93 expands bar[0]. We could do that here -- there are enough usable + primitives to do that -- but do not at this point. */ + if (var_is_special == 0 && (v = find_variable_last_nameref (name, 0))) + { + if (nameref_p (v) && (t = nameref_cell (v)) && *t) + { + w = alloc_word_desc (); + w->word = savestring (t); + w->flags = 0; + return w; + } + } + + /* An indirect reference to a positional parameter or a special parameter + is ok. Indirect references to array references, as explained above, are + ok (currently). Only references to unset variables are errors at this + point. */ + if (legal_identifier (name) && v == 0) + { + report_error (_("%s: invalid indirect expansion"), name); + w = alloc_word_desc (); + w->word = &expand_param_error; + w->flags = 0; + return (w); + } + + t = parameter_brace_find_indir (name, var_is_special, quoted, 0); + + chk_atstar (t, quoted, pflags, quoted_dollar_atp, contains_dollar_at); + +#if defined (ARRAY_VARS) + /* Array references to unset variables are also an error */ + if (t == 0 && valid_array_reference (name, 0)) + { + v = array_variable_part (name, 0, (char **)0, (int *)0); + if (v == 0) + { + report_error (_("%s: invalid indirect expansion"), name); + w = alloc_word_desc (); + w->word = &expand_param_error; + w->flags = 0; + return (w); + } + else + return (WORD_DESC *)NULL; + } +#endif + + if (t == 0) + return (WORD_DESC *)NULL; + + if (valid_brace_expansion_word (t, SPECIAL_VAR (t, 0)) == 0) + { + report_error (_("%s: invalid variable name"), t); + free (t); + w = alloc_word_desc (); + w->word = &expand_param_error; + w->flags = 0; + return (w); + } + + w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, pflags, 0); + free (t); + + return w; +} + +/* Expand the right side of a parameter expansion of the form ${NAMEcVALUE}, + depending on the value of C, the separating character. C can be one of + "-", "+", or "=". QUOTED is true if the entire brace expression occurs + between double quotes. */ +static WORD_DESC * +parameter_brace_expand_rhs (name, value, op, quoted, pflags, qdollaratp, hasdollarat) + char *name, *value; + int op, quoted, pflags, *qdollaratp, *hasdollarat; +{ + WORD_DESC *w; + WORD_LIST *l, *tl; + char *t, *t1, *temp, *vname, *newval; + int l_hasdollat, sindex, arrayref; + SHELL_VAR *v; + array_eltstate_t es; + +/*itrace("parameter_brace_expand_rhs: %s:%s pflags = %d", name, value, pflags);*/ + /* If the entire expression is between double quotes, we want to treat + the value as a double-quoted string, with the exception that we strip + embedded unescaped double quotes (for sh backwards compatibility). */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *value) + { + sindex = 0; + temp = string_extract_double_quoted (value, &sindex, SX_STRIPDQ); + } + else + temp = value; + + w = alloc_word_desc (); + l_hasdollat = 0; + l = *temp ? expand_string_for_rhs (temp, quoted, op, pflags, &l_hasdollat, (int *)NULL) + : (WORD_LIST *)0; + if (hasdollarat) + *hasdollarat = l_hasdollat || (l && l->next); + if (temp != value) + free (temp); + + /* list_string takes multiple CTLNULs and turns them into an empty word + with W_SAWQUOTEDNULL set. Turn it back into a single CTLNUL for the + rest of this function and the caller. */ + for (tl = l; tl; tl = tl->next) + { + if (tl->word && (tl->word->word == 0 || tl->word->word[0] == 0) && + (tl->word->flags | W_SAWQUOTEDNULL)) + { + t = make_quoted_char ('\0'); + FREE (tl->word->word); + tl->word->word = t; + tl->word->flags |= W_QUOTED|W_HASQUOTEDNULL; + tl->word->flags &= ~W_SAWQUOTEDNULL; + } + } + + if (l) + { + /* If l->next is not null, we know that TEMP contained "$@", since that + is the only expansion that creates more than one word. */ + if (qdollaratp && ((l_hasdollat && quoted) || l->next)) + { +/*itrace("parameter_brace_expand_rhs: %s:%s: l != NULL, set *qdollaratp", name, value);*/ + *qdollaratp = 1; + } + + /* The expansion of TEMP returned something. We need to treat things + slightly differently if L_HASDOLLAT is non-zero. If we have "$@", + the individual words have already been quoted. We need to turn them + into a string with the words separated by the first character of + $IFS without any additional quoting, so string_list_dollar_at won't + do the right thing. If IFS is null, we want "$@" to split into + separate arguments, not be concatenated, so we use string_list_internal + and mark the word to be split on spaces later. We use + string_list_dollar_star for "$@" otherwise. */ + if (l->next && ifs_is_null) + { + temp = string_list_internal (l, " "); + w->flags |= W_SPLITSPACE; + } + else if (l_hasdollat || l->next) + temp = string_list_dollar_star (l, quoted, 0); + else + { + temp = string_list (l); + if (temp && (QUOTED_NULL (temp) == 0) && (l->word->flags & W_SAWQUOTEDNULL)) + w->flags |= W_SAWQUOTEDNULL; /* XXX */ + } + + /* If we have a quoted null result (QUOTED_NULL(temp)) and the word is + a quoted null (l->next == 0 && QUOTED_NULL(l->word->word)), the + flags indicate it (l->word->flags & W_HASQUOTEDNULL), and the + expansion is quoted (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + (which is more paranoia than anything else), we need to return the + quoted null string and set the flags to indicate it. */ + if (l->next == 0 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp) && QUOTED_NULL (l->word->word) && (l->word->flags & W_HASQUOTEDNULL)) + { + w->flags |= W_HASQUOTEDNULL; +/*itrace("parameter_brace_expand_rhs (%s:%s): returning quoted null, turning off qdollaratp", name, value);*/ + /* If we return a quoted null with L_HASDOLLARAT, we either have a + construct like "${@-$@}" or "${@-${@-$@}}" with no positional + parameters or a quoted expansion of "$@" with $1 == ''. In either + case, we don't want to enable special handling of $@. */ + if (qdollaratp && l_hasdollat) + *qdollaratp = 0; + } + dispose_words (l); + } + else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && l_hasdollat) + { + /* Posix interp 221 changed the rules on this. The idea is that + something like "$xxx$@" should expand the same as "${foo-$xxx$@}" + when foo and xxx are unset. The problem is that it's not in any + way backwards compatible and few other shells do it. We're eventually + going to try and split the difference (heh) a little bit here. */ + /* l_hasdollat == 1 means we saw a quoted dollar at. */ + + /* The brace expansion occurred between double quotes and there was + a $@ in TEMP. It does not matter if the $@ is quoted, as long as + it does not expand to anything. In this case, we want to return + a quoted empty string. Posix interp 888 */ + temp = make_quoted_char ('\0'); + w->flags |= W_HASQUOTEDNULL; +/*itrace("parameter_brace_expand_rhs (%s:%s): returning quoted null", name, value);*/ + } + else + temp = (char *)NULL; + + if (op == '-' || op == '+') + { + w->word = temp; + return w; + } + + /* op == '=' */ + t1 = temp ? dequote_string (temp) : savestring (""); + free (temp); + + /* bash-4.4/5.0 */ + vname = name; + if (*name == '!' && + (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1]) || VALID_INDIR_PARAM (name[1]))) + { + vname = parameter_brace_find_indir (name + 1, SPECIAL_VAR (name, 1), quoted, 1); + if (vname == 0 || *vname == 0) + { + report_error (_("%s: invalid indirect expansion"), name); + free (vname); + free (t1); + dispose_word (w); + return &expand_wdesc_error; + } + if (legal_identifier (vname) == 0) + { + report_error (_("%s: invalid variable name"), vname); + free (vname); + free (t1); + dispose_word (w); + return &expand_wdesc_error; + } + } + + arrayref = 0; +#if defined (ARRAY_VARS) + if (valid_array_reference (vname, 0)) + { + init_eltstate (&es); + v = assign_array_element (vname, t1, ASS_ALLOWALLSUB, &es); + arrayref = 1; + newval = es.value; + } + else +#endif /* ARRAY_VARS */ + v = bind_variable (vname, t1, 0); + + if (v == 0 || readonly_p (v) || noassign_p (v)) /* expansion error */ + { + if ((v == 0 || readonly_p (v)) && interactive_shell == 0 && posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (FORCE_EOF); + } + else + { + if (vname != name) + free (vname); + last_command_exit_value = EX_BADUSAGE; + exp_jump_to_top_level (DISCARD); + } + } + + stupidly_hack_special_variables (vname); + + /* "In all cases, the final value of parameter shall be substituted." */ + if (shell_compatibility_level > 51) + { + FREE (t1); +#if defined (ARRAY_VARS) + if (arrayref) + { + t1 = newval; + flush_eltstate (&es); + } + else + t1 = get_variable_value (v); +#else + t1 = value_cell (v); +#endif + } + + if (vname != name) + free (vname); + + /* From Posix group discussion Feb-March 2010. Issue 7 0000221 */ + + /* If we are double-quoted or if we are not going to be performing word + splitting, we want to quote the value we return appropriately, like + the other expansions this function handles. */ + w->word = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) ? quote_string (t1) : quote_escapes (t1); + /* If we have something that's non-null, but not a quoted null string, + and we're not going to be performing word splitting (we know we're not + because the operator is `='), we can forget we saw a quoted null. */ + if (w->word && w->word[0] && QUOTED_NULL (w->word) == 0) + w->flags &= ~W_SAWQUOTEDNULL; + + /* If we convert a null string into a quoted null, make sure the caller + knows it. */ + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && QUOTED_NULL (w->word)) + w->flags |= W_HASQUOTEDNULL; + + return w; +} + +/* Deal with the right hand side of a ${name:?value} expansion in the case + that NAME is null or not set. If VALUE is non-null it is expanded and + used as the error message to print, otherwise a standard message is + printed. */ +static void +parameter_brace_expand_error (name, value, check_null) + char *name, *value; + int check_null; +{ + WORD_LIST *l; + char *temp; + + set_exit_status (EXECUTION_FAILURE); /* ensure it's non-zero */ + if (value && *value) + { + l = expand_string (value, 0); + temp = string_list (l); + report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */ + FREE (temp); + dispose_words (l); + } + else if (check_null == 0) + report_error (_("%s: parameter not set"), name); + else + report_error (_("%s: parameter null or not set"), name); + + /* Free the data we have allocated during this expansion, since we + are about to longjmp out. */ + free (name); + FREE (value); +} + +/* Return 1 if NAME is something for which parameter_brace_expand_length is + OK to do. */ +static int +valid_length_expression (name) + char *name; +{ + return (name[1] == '\0' || /* ${#} */ + ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') || /* special param */ + (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */ +#if defined (ARRAY_VARS) + valid_array_reference (name + 1, 0) || /* ${#a[7]} */ +#endif + legal_identifier (name + 1)); /* ${#PS1} */ +} + +/* Handle the parameter brace expansion that requires us to return the + length of a parameter. */ +static intmax_t +parameter_brace_expand_length (name) + char *name; +{ + char *t, *newname; + intmax_t number, arg_index; + WORD_LIST *list; + SHELL_VAR *var; + + var = (SHELL_VAR *)NULL; + + if (name[1] == '\0') /* ${#} */ + number = number_of_args (); + else if (DOLLAR_AT_STAR (name[1]) && name[2] == '\0') /* ${#@}, ${#*} */ + number = number_of_args (); + else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') + { + /* Take the lengths of some of the shell's special parameters. */ + switch (name[1]) + { + case '-': + t = which_set_flags (); + break; + case '?': + t = itos (last_command_exit_value); + break; + case '$': + t = itos (dollar_dollar_pid); + break; + case '!': + if (last_asynchronous_pid == NO_PID) + t = (char *)NULL; /* XXX - error if set -u set? */ + else + t = itos (last_asynchronous_pid); + break; + case '#': + t = itos (number_of_args ()); + break; + } + number = STRLEN (t); + FREE (t); + } +#if defined (ARRAY_VARS) + else if (valid_array_reference (name + 1, 0)) + number = array_length_reference (name + 1); +#endif /* ARRAY_VARS */ + else + { + number = 0; + + if (legal_number (name + 1, &arg_index)) /* ${#1} */ + { + t = get_dollar_var_value (arg_index); + if (t == 0 && unbound_vars_is_error) + return INTMAX_MIN; + number = MB_STRLEN (t); + FREE (t); + } +#if defined (ARRAY_VARS) + else if ((var = find_variable (name + 1)) && (invisible_p (var) == 0) && (array_p (var) || assoc_p (var))) + { + if (assoc_p (var)) + t = assoc_reference (assoc_cell (var), "0"); + else + t = array_reference (array_cell (var), 0); + if (t == 0 && unbound_vars_is_error) + return INTMAX_MIN; + number = MB_STRLEN (t); + } +#endif + /* Fast path for the common case of taking the length of a non-dynamic + scalar variable value. */ + else if ((var || (var = find_variable (name + 1))) && + invisible_p (var) == 0 && + array_p (var) == 0 && assoc_p (var) == 0 && + var->dynamic_value == 0) + number = value_cell (var) ? MB_STRLEN (value_cell (var)) : 0; + else if (var == 0 && unbound_vars_is_error == 0) + number = 0; + else /* ${#PS1} */ + { + newname = savestring (name); + newname[0] = '$'; + list = expand_string (newname, Q_DOUBLE_QUOTES); + t = list ? string_list (list) : (char *)NULL; + free (newname); + if (list) + dispose_words (list); + + number = t ? MB_STRLEN (t) : 0; + FREE (t); + } + } + + return (number); +} + +/* Skip characters in SUBSTR until DELIM. SUBSTR is an arithmetic expression, + so we do some ad-hoc parsing of an arithmetic expression to find + the first DELIM, instead of using strchr(3). Two rules: + 1. If the substring contains a `(', read until closing `)'. + 2. If the substring contains a `?', read past one `:' for each `?'. + The SD_ARITHEXP flag to skip_to_delim takes care of doing this. +*/ + +static char * +skiparith (substr, delim) + char *substr; + int delim; +{ + int i; + char delims[2]; + + delims[0] = delim; + delims[1] = '\0'; + + i = skip_to_delim (substr, 0, delims, SD_ARITHEXP); + return (substr + i); +} + +/* Verify and limit the start and end of the desired substring. If + VTYPE == 0, a regular shell variable is being used; if it is 1, + then the positional parameters are being used; if it is 2, then + VALUE is really a pointer to an array variable that should be used. + Return value is 1 if both values were OK, 0 if there was a problem + with an invalid expression, or -1 if the values were out of range. */ +static int +verify_substring_values (v, value, substr, vtype, e1p, e2p) + SHELL_VAR *v; + char *value, *substr; + int vtype; + intmax_t *e1p, *e2p; +{ + char *t, *temp1, *temp2; + arrayind_t len; + int expok, eflag; +#if defined (ARRAY_VARS) + ARRAY *a; + HASH_TABLE *h; +#endif + + /* duplicate behavior of strchr(3) */ + t = skiparith (substr, ':'); + if (*t && *t == ':') + *t = '\0'; + else + t = (char *)0; + + temp1 = expand_arith_string (substr, Q_DOUBLE_QUOTES|Q_ARITH); + eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED; + + *e1p = evalexp (temp1, eflag, &expok); + free (temp1); + if (expok == 0) + return (0); + + len = -1; /* paranoia */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + len = MB_STRLEN (value); + break; + case VT_POSPARMS: + len = number_of_args () + 1; + if (*e1p == 0) + len++; /* add one arg if counting from $0 */ + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + /* For arrays, the first value deals with array indices. Negative + offsets count from one past the array's maximum index. Associative + arrays treat the number of elements as the maximum index. */ + if (assoc_p (v)) + { + h = assoc_cell (v); + len = assoc_num_elements (h) + (*e1p < 0); + } + else + { + a = (ARRAY *)value; + len = array_max_index (a) + (*e1p < 0); /* arrays index from 0 to n - 1 */ + } + break; +#endif + } + + if (len == -1) /* paranoia */ + return -1; + + if (*e1p < 0) /* negative offsets count from end */ + *e1p += len; + + if (*e1p > len || *e1p < 0) + return (-1); + +#if defined (ARRAY_VARS) + /* For arrays, the second offset deals with the number of elements. */ + if (vtype == VT_ARRAYVAR) + len = assoc_p (v) ? assoc_num_elements (h) : array_num_elements (a); +#endif + + if (t) + { + t++; + temp2 = savestring (t); + temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES|Q_ARITH); + free (temp2); + t[-1] = ':'; + *e2p = evalexp (temp1, eflag, &expok); + free (temp1); + if (expok == 0) + return (0); + + /* Should we allow positional parameter length < 0 to count backwards + from end of positional parameters? */ +#if 1 + if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0) +#else /* XXX - postponed; this isn't really a valuable feature */ + if (vtype == VT_ARRAYVAR && *e2p < 0) +#endif + { + internal_error (_("%s: substring expression < 0"), t); + return (0); + } +#if defined (ARRAY_VARS) + /* In order to deal with sparse arrays, push the intelligence about how + to deal with the number of elements desired down to the array- + specific functions. */ + if (vtype != VT_ARRAYVAR) +#endif + { + if (*e2p < 0) + { + *e2p += len; + if (*e2p < 0 || *e2p < *e1p) + { + internal_error (_("%s: substring expression < 0"), t); + return (0); + } + } + else + *e2p += *e1p; /* want E2 chars starting at E1 */ + if (*e2p > len) + *e2p = len; + } + } + else + *e2p = len; + + return (1); +} + +/* Return the type of variable specified by VARNAME (simple variable, + positional param, or array variable). Also return the value specified + by VARNAME (value of a variable or a reference to an array element). + QUOTED is the standard description of quoting state, using Q_* defines. + FLAGS is currently a set of flags to pass to array_value. If IND is + not INTMAX_MIN, and FLAGS includes AV_USEIND, IND is + passed to array_value so the array index is not computed again. + If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL + characters in the value are quoted with CTLESC and takes appropriate + steps. For convenience, *VALP is set to the dequoted VALUE. */ +static int +get_var_and_type (varname, value, estatep, quoted, flags, varp, valp) + char *varname, *value; + array_eltstate_t *estatep; + int quoted, flags; + SHELL_VAR **varp; + char **valp; +{ + int vtype, want_indir; + char *temp, *vname; + SHELL_VAR *v; + + want_indir = *varname == '!' && + (legal_variable_starter ((unsigned char)varname[1]) || DIGIT (varname[1]) + || VALID_INDIR_PARAM (varname[1])); + if (want_indir) + vname = parameter_brace_find_indir (varname+1, SPECIAL_VAR (varname, 1), quoted, 1); + /* XXX - what if vname == 0 || *vname == 0 ? */ + else + vname = varname; + + if (vname == 0) + { + vtype = VT_VARIABLE; + *varp = (SHELL_VAR *)NULL; + *valp = (char *)NULL; + return (vtype); + } + + /* This sets vtype to VT_VARIABLE or VT_POSPARMS */ + vtype = STR_DOLLAR_AT_STAR (vname); + if (vtype == VT_POSPARMS && vname[0] == '*') + vtype |= VT_STARSUB; + *varp = (SHELL_VAR *)NULL; + +#if defined (ARRAY_VARS) + if (valid_array_reference (vname, 0)) + { + v = array_variable_part (vname, 0, &temp, (int *)0); + /* If we want to signal array_value to use an already-computed index, + the caller will set ESTATEP->IND to that index and pass AV_USEIND in + FLAGS. */ + if (estatep && (flags & AV_USEIND) == 0) + estatep->ind = INTMAX_MIN; + + if (v && invisible_p (v)) + { + vtype = VT_ARRAYMEMBER; + *varp = (SHELL_VAR *)NULL; + *valp = (char *)NULL; + } + if (v && (array_p (v) || assoc_p (v))) + { + if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == RBRACK) + { + /* Callers have to differentiate between indexed and associative */ + vtype = VT_ARRAYVAR; + if (temp[0] == '*') + vtype |= VT_STARSUB; + *valp = array_p (v) ? (char *)array_cell (v) : (char *)assoc_cell (v); + } + else + { + vtype = VT_ARRAYMEMBER; + *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, estatep); + } + *varp = v; + } + else if (v && (ALL_ELEMENT_SUB (temp[0]) && temp[1] == RBRACK)) + { + vtype = VT_VARIABLE; + *varp = v; + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + *valp = value ? dequote_string (value) : (char *)NULL; + else + *valp = value ? dequote_escapes (value) : (char *)NULL; + } + else + { + vtype = VT_ARRAYMEMBER; + *varp = v; + *valp = array_value (vname, Q_DOUBLE_QUOTES, flags, estatep); + } + } + else if ((v = find_variable (vname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v))) + { + vtype = VT_ARRAYMEMBER; + *varp = v; + *valp = assoc_p (v) ? assoc_reference (assoc_cell (v), "0") : array_reference (array_cell (v), 0); + } + else +#endif + { + if (value && vtype == VT_VARIABLE) + { + *varp = find_variable (vname); + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + *valp = dequote_string (value); + else + *valp = dequote_escapes (value); + } + else + *valp = value; + } + + if (want_indir) + free (vname); + + return vtype; +} + +/***********************************************************/ +/* */ +/* Functions to perform transformations on variable values */ +/* */ +/***********************************************************/ + +static char * +string_var_assignment (v, s) + SHELL_VAR *v; + char *s; +{ + char flags[MAX_ATTRIBUTES], *ret, *val; + int i; + + val = (v && (invisible_p (v) || var_isset (v) == 0)) ? (char *)NULL : sh_quote_reusable (s, 0); + i = var_attribute_string (v, 0, flags); + if (i == 0 && val == 0) + return (char *)NULL; + + ret = (char *)xmalloc (i + STRLEN (val) + strlen (v->name) + 16 + MAX_ATTRIBUTES); + if (i > 0 && val == 0) + sprintf (ret, "declare -%s %s", flags, v->name); + else if (i > 0) + sprintf (ret, "declare -%s %s=%s", flags, v->name, val); + else + sprintf (ret, "%s=%s", v->name, val); + free (val); + return ret; +} + +#if defined (ARRAY_VARS) +static char * +array_var_assignment (v, itype, quoted, atype) + SHELL_VAR *v; + int itype, quoted, atype; +{ + char *ret, *val, flags[MAX_ATTRIBUTES]; + int i; + + if (v == 0) + return (char *)NULL; + if (atype == 2) + val = array_p (v) ? array_to_kvpair (array_cell (v), 0) + : assoc_to_kvpair (assoc_cell (v), 0); + else + val = array_p (v) ? array_to_assign (array_cell (v), 0) + : assoc_to_assign (assoc_cell (v), 0); + + if (val == 0 && (invisible_p (v) || var_isset (v) == 0)) + ; /* placeholder */ + else if (val == 0) + { + val = (char *)xmalloc (3); + val[0] = LPAREN; + val[1] = RPAREN; + val[2] = 0; + } + else + { + ret = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) ? quote_string (val) : quote_escapes (val); + free (val); + val = ret; + } + + if (atype == 2) + return val; + + i = var_attribute_string (v, 0, flags); + ret = (char *)xmalloc (i + STRLEN (val) + strlen (v->name) + 16); + if (val) + sprintf (ret, "declare -%s %s=%s", flags, v->name, val); + else + sprintf (ret, "declare -%s %s", flags, v->name); + free (val); + return ret; +} +#endif + +static char * +pos_params_assignment (list, itype, quoted) + WORD_LIST *list; + int itype; + int quoted; +{ + char *temp, *ret; + + /* first, we transform the list to quote each word. */ + temp = list_transform ('Q', (SHELL_VAR *)0, list, itype, quoted); + ret = (char *)xmalloc (strlen (temp) + 8); + strcpy (ret, "set -- "); + strcpy (ret + 7, temp); + free (temp); + return ret; +} + +static char * +string_transform (xc, v, s) + int xc; + SHELL_VAR *v; + char *s; +{ + char *ret, flags[MAX_ATTRIBUTES], *t; + int i; + + if (((xc == 'A' || xc == 'a') && v == 0)) + return (char *)NULL; + else if (xc != 'a' && xc != 'A' && s == 0) + return (char *)NULL; + + switch (xc) + { + /* Transformations that interrogate the variable */ + case 'a': + i = var_attribute_string (v, 0, flags); + ret = (i > 0) ? savestring (flags) : (char *)NULL; + break; + case 'A': + ret = string_var_assignment (v, s); + break; + case 'K': + case 'k': + ret = sh_quote_reusable (s, 0); + break; + /* Transformations that modify the variable's value */ + case 'E': + t = ansiexpand (s, 0, strlen (s), (int *)0); + ret = dequote_escapes (t); + free (t); + break; + case 'P': + ret = decode_prompt_string (s); + break; + case 'Q': + ret = sh_quote_reusable (s, 0); + break; + case 'U': + ret = sh_modcase (s, 0, CASE_UPPER); + break; + case 'u': + ret = sh_modcase (s, 0, CASE_UPFIRST); /* capitalize */ + break; + case 'L': + ret = sh_modcase (s, 0, CASE_LOWER); + break; + default: + ret = (char *)NULL; + break; + } + return ret; +} + +static char * +list_transform (xc, v, list, itype, quoted) + int xc; + SHELL_VAR *v; + WORD_LIST *list; + int itype, quoted; +{ + WORD_LIST *new, *l; + WORD_DESC *w; + char *tword; + int qflags; + + for (new = (WORD_LIST *)NULL, l = list; l; l = l->next) + { + tword = string_transform (xc, v, l->word->word); + w = alloc_word_desc (); + w->word = tword ? tword : savestring (""); /* XXX */ + new = make_word_list (w, new); + } + l = REVERSE_LIST (new, WORD_LIST *); + + qflags = quoted; + /* If we are expanding in a context where word splitting will not be + performed, treat as quoted. This changes how $* will be expanded. */ + if (itype == '*' && expand_no_split_dollar_star && ifs_is_null) + qflags |= Q_DOUBLE_QUOTES; /* Posix interp 888 */ + + tword = string_list_pos_params (itype, l, qflags, 0); + dispose_words (l); + + return (tword); +} + +static char * +parameter_list_transform (xc, itype, quoted) + int xc; + int itype; + int quoted; +{ + char *ret; + WORD_LIST *list; + + list = list_rest_of_args (); + if (list == 0) + return ((char *)NULL); + if (xc == 'A') + ret = pos_params_assignment (list, itype, quoted); + else + ret = list_transform (xc, (SHELL_VAR *)0, list, itype, quoted); + dispose_words (list); + return (ret); +} + +#if defined (ARRAY_VARS) +static char * +array_transform (xc, var, starsub, quoted) + int xc; + SHELL_VAR *var; + int starsub; /* so we can figure out how it's indexed */ + int quoted; +{ + ARRAY *a; + HASH_TABLE *h; + int itype, qflags; + char *ret; + WORD_LIST *list; + SHELL_VAR *v; + + v = var; /* XXX - for now */ + + itype = starsub ? '*' : '@'; + + if (xc == 'A') + return (array_var_assignment (v, itype, quoted, 1)); + else if (xc == 'K') + return (array_var_assignment (v, itype, quoted, 2)); + + /* special case for unset arrays and attributes */ + if (xc == 'a' && (invisible_p (v) || var_isset (v) == 0)) + { + char flags[MAX_ATTRIBUTES]; + int i; + + i = var_attribute_string (v, 0, flags); + return ((i > 0) ? savestring (flags) : (char *)NULL); + } + + a = (v && array_p (v)) ? array_cell (v) : 0; + h = (v && assoc_p (v)) ? assoc_cell (v) : 0; + + /* XXX - for now */ + if (xc == 'k') + { + if (v == 0) + return ((char *)NULL); + list = array_p (v) ? array_to_kvpair_list (a) : assoc_to_kvpair_list (h); + qflags = quoted; + /* If we are expanding in a context where word splitting will not be + performed, treat as quoted. This changes how $* will be expanded. */ + if (itype == '*' && expand_no_split_dollar_star && ifs_is_null) + qflags |= Q_DOUBLE_QUOTES; /* Posix interp 888 */ + + ret = string_list_pos_params (itype, list, qflags, 0); + dispose_words (list); + return ret; + } + + list = a ? array_to_word_list (a) : (h ? assoc_to_word_list (h) : 0); + if (list == 0) + return ((char *)NULL); + ret = list_transform (xc, v, list, itype, quoted); + dispose_words (list); + + return ret; +} +#endif /* ARRAY_VARS */ + +static int +valid_parameter_transform (xform) + char *xform; +{ + if (xform[1]) + return 0; + + /* check for valid values of xform[0] */ + switch (xform[0]) + { + case 'a': /* expand to a string with just attributes */ + case 'A': /* expand as an assignment statement with attributes */ + case 'K': /* expand assoc array to list of key/value pairs */ + case 'k': /* XXX - for now */ + case 'E': /* expand like $'...' */ + case 'P': /* expand like prompt string */ + case 'Q': /* quote reusably */ + case 'U': /* transform to uppercase */ + case 'u': /* transform by capitalizing */ + case 'L': /* transform to lowercase */ + return 1; + default: + return 0; + } +} + +static char * +parameter_brace_transform (varname, value, estatep, xform, rtype, quoted, pflags, flags) + char *varname, *value; + array_eltstate_t *estatep; + char *xform; + int rtype, quoted, pflags, flags; +{ + int vtype, xc, starsub; + char *temp1, *val, *oname; + SHELL_VAR *v; + + xc = xform[0]; + if (value == 0 && xc != 'A' && xc != 'a') + return ((char *)NULL); + + oname = this_command_name; + this_command_name = varname; + + vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val); + if (vtype == -1) + { + this_command_name = oname; + return ((char *)NULL); + } + + if (xform[0] == 0 || valid_parameter_transform (xform) == 0) + { + this_command_name = oname; + if (vtype == VT_VARIABLE) + FREE (val); + return (interactive_shell ? &expand_param_error : &expand_param_fatal); + } + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + /* If we are asked to display the attributes of an unset variable, V will + be NULL after the call to get_var_and_type. Double-check here. */ + if ((xc == 'a' || xc == 'A') && vtype == VT_VARIABLE && varname && v == 0) + v = find_variable (varname); + + temp1 = (char *)NULL; /* shut up gcc */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp1 = string_transform (xc, v, val); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp1) + { + val = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + ? quote_string (temp1) + : quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + temp1 = array_transform (xc, v, starsub, quoted); + if (temp1 && quoted == 0 && ifs_is_null) + { + /* Posix interp 888 */ + } + else if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; +#endif + case VT_POSPARMS: + temp1 = parameter_list_transform (xc, varname[0], quoted); + if (temp1 && quoted == 0 && ifs_is_null) + { + /* Posix interp 888 */ + } + else if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) + { + val = quote_escapes (temp1); + free (temp1); + temp1 = val; + } + break; + } + + this_command_name = oname; + return temp1; +} + +/******************************************************/ +/* */ +/* Functions to extract substrings of variable values */ +/* */ +/******************************************************/ + +#if defined (HANDLE_MULTIBYTE) +/* Character-oriented rather than strictly byte-oriented substrings. S and + E, rather being strict indices into STRING, indicate character (possibly + multibyte character) positions that require calculation. + Used by the ${param:offset[:length]} expansion. */ +static char * +mb_substring (string, s, e) + char *string; + int s, e; +{ + char *tt; + int start, stop, i; + size_t slen; + DECLARE_MBSTATE; + + start = 0; + /* Don't need string length in ADVANCE_CHAR unless multibyte chars possible. */ + slen = (MB_CUR_MAX > 1) ? STRLEN (string) : 0; + + i = s; + while (string[start] && i--) + ADVANCE_CHAR (string, slen, start); + stop = start; + i = e - s; + while (string[stop] && i--) + ADVANCE_CHAR (string, slen, stop); + tt = substring (string, start, stop); + return tt; +} +#endif + +/* Process a variable substring expansion: ${name:e1[:e2]}. If VARNAME + is `@', use the positional parameters; otherwise, use the value of + VARNAME. If VARNAME is an array variable, use the array elements. */ + +static char * +parameter_brace_substring (varname, value, estatep, substr, quoted, pflags, flags) + char *varname, *value; + array_eltstate_t *estatep; + char *substr; + int quoted, pflags, flags; +{ + intmax_t e1, e2; + int vtype, r, starsub; + char *temp, *val, *tt, *oname; + SHELL_VAR *v; + + if (value == 0 && ((varname[0] != '@' && varname[0] != '*') || varname[1])) + return ((char *)NULL); + + oname = this_command_name; + this_command_name = varname; + + vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val); + if (vtype == -1) + { + this_command_name = oname; + return ((char *)NULL); + } + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + r = verify_substring_values (v, val, substr, vtype, &e1, &e2); + this_command_name = oname; + if (r <= 0) + { + if (vtype == VT_VARIABLE) + FREE (val); + return ((r == 0) ? &expand_param_error : (char *)NULL); + } + + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: +#if defined (HANDLE_MULTIBYTE) + if (MB_CUR_MAX > 1) + tt = mb_substring (val, e1, e2); + else +#endif + tt = substring (val, e1, e2); + + if (vtype == VT_VARIABLE) + FREE (val); + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + temp = quote_string (tt); + else + temp = tt ? quote_escapes (tt) : (char *)NULL; + FREE (tt); + break; + case VT_POSPARMS: + case VT_ARRAYVAR: + if (vtype == VT_POSPARMS) + tt = pos_params (varname, e1, e2, quoted, pflags); +#if defined (ARRAY_VARS) + /* assoc_subrange and array_subrange both call string_list_pos_params, + so we can treat this case just like VT_POSPARAMS. */ + else if (assoc_p (v)) + /* we convert to list and take first e2 elements starting at e1th + element -- officially undefined for now */ + tt = assoc_subrange (assoc_cell (v), e1, e2, starsub, quoted, pflags); + else + /* We want E2 to be the number of elements desired (arrays can be + sparse, so verify_substring_values just returns the numbers + specified and we rely on array_subrange to understand how to + deal with them). */ + tt = array_subrange (array_cell (v), e1, e2, starsub, quoted, pflags); +#endif + /* We want to leave this alone in every case where pos_params/ + string_list_pos_params quotes the list members */ + if (tt && quoted == 0 && ifs_is_null) + { + temp = tt; /* Posix interp 888 */ + } + else if (tt && quoted == 0 && (pflags & PF_ASSIGNRHS)) + { + temp = tt; /* Posix interp 888 */ + } + else if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) + { + temp = tt ? quote_escapes (tt) : (char *)NULL; + FREE (tt); + } + else + temp = tt; + break; + + default: + temp = (char *)NULL; + } + + return temp; +} + +/****************************************************************/ +/* */ +/* Functions to perform pattern substitution on variable values */ +/* */ +/****************************************************************/ + +static int +shouldexp_replacement (s) + char *s; +{ + size_t slen; + int sindex, c; + DECLARE_MBSTATE; + + sindex = 0; + slen = STRLEN (s); + while (c = s[sindex]) + { + if (c == '\\') + { + sindex++; + if (s[sindex] == 0) + return 0; + /* We want to remove this backslash because we treat it as special + in this context. THIS ASSUMES THE STRING IS PROCESSED BY + strcreplace() OR EQUIVALENT that handles removing backslashes + preceding the special character. */ + if (s[sindex] == '&') + return 1; + if (s[sindex] == '\\') + return 1; + } + else if (c == '&') + return 1; + ADVANCE_CHAR (s, slen, sindex); + } + return 0; +} + +char * +pat_subst (string, pat, rep, mflags) + char *string, *pat, *rep; + int mflags; +{ + char *ret, *s, *e, *str, *rstr, *mstr, *send; + int rptr, mtype, rxpand, mlen; + size_t rsize, l, replen, rslen; + DECLARE_MBSTATE; + + if (string == 0) + return (savestring ("")); + + mtype = mflags & MATCH_TYPEMASK; + rxpand = mflags & MATCH_EXPREP; + + /* Special cases: + * 1. A null pattern with mtype == MATCH_BEG means to prefix STRING + * with REP and return the result. + * 2. A null pattern with mtype == MATCH_END means to append REP to + * STRING and return the result. + * 3. A null STRING with a matching pattern means to append REP to + * STRING and return the result. + * + * These process `&' in the replacement string, like `sed' does when + * presented with a BRE of `^' or `$'. + */ + if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END)) + { + rstr = (mflags & MATCH_EXPREP) ? strcreplace (rep, '&', "", 2) : rep; + rslen = STRLEN (rstr); + l = STRLEN (string); + ret = (char *)xmalloc (rslen + l + 2); + if (rslen == 0) + strcpy (ret, string); + else if (mtype == MATCH_BEG) + { + strcpy (ret, rstr); + strcpy (ret + rslen, string); + } + else + { + strcpy (ret, string); + strcpy (ret + l, rstr); + } + if (rstr != rep) + free (rstr); + return (ret); + } + else if (*string == 0 && (match_pattern (string, pat, mtype, &s, &e) != 0)) + return ((mflags & MATCH_EXPREP) ? strcreplace (rep, '&', "", 2) : savestring (rep)); + + ret = (char *)xmalloc (rsize = 64); + ret[0] = '\0'; + send = string + strlen (string); + + for (replen = STRLEN (rep), rptr = 0, str = string; *str;) + { + if (match_pattern (str, pat, mtype, &s, &e) == 0) + break; + l = s - str; + + if (rep && rxpand) + { + int x; + mlen = e - s; + mstr = xmalloc (mlen + 1); + for (x = 0; x < mlen; x++) + mstr[x] = s[x]; + mstr[mlen] = '\0'; + rstr = strcreplace (rep, '&', mstr, 2); + free (mstr); + rslen = strlen (rstr); + } + else + { + rstr = rep; + rslen = replen; + } + + RESIZE_MALLOCED_BUFFER (ret, rptr, (l + rslen), rsize, 64); + + /* OK, now copy the leading unmatched portion of the string (from + str to s) to ret starting at rptr (the current offset). Then copy + the replacement string at ret + rptr + (s - str). Increment + rptr (if necessary) and str and go on. */ + if (l) + { + strncpy (ret + rptr, str, l); + rptr += l; + } + if (replen) + { + strncpy (ret + rptr, rstr, rslen); + rptr += rslen; + } + str = e; /* e == end of match */ + + if (rstr != rep) + free (rstr); + + if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY) + break; + + if (s == e) + { + /* On a zero-length match, make sure we copy one character, since + we increment one character to avoid infinite recursion. */ + char *p, *origp, *origs; + size_t clen; + + RESIZE_MALLOCED_BUFFER (ret, rptr, locale_mb_cur_max, rsize, 64); +#if defined (HANDLE_MULTIBYTE) + p = origp = ret + rptr; + origs = str; + COPY_CHAR_P (p, str, send); + rptr += p - origp; + e += str - origs; +#else + ret[rptr++] = *str++; + e++; /* avoid infinite recursion on zero-length match */ +#endif + } + } + + /* Now copy the unmatched portion of the input string */ + if (str && *str) + { + l = send - str + 1; + RESIZE_MALLOCED_BUFFER (ret, rptr, l, rsize, 64); + strcpy (ret + rptr, str); + } + else + ret[rptr] = '\0'; + + return ret; +} + +/* Do pattern match and replacement on the positional parameters. */ +static char * +pos_params_pat_subst (string, pat, rep, mflags) + char *string, *pat, *rep; + int mflags; +{ + WORD_LIST *save, *params; + WORD_DESC *w; + char *ret; + int pchar, qflags, pflags; + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + for ( ; params; params = params->next) + { + ret = pat_subst (params->word->word, pat, rep, mflags); + w = alloc_word_desc (); + w->word = ret ? ret : savestring (""); + dispose_word (params->word); + params->word = w; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + pflags = (mflags & MATCH_ASSIGNRHS) == MATCH_ASSIGNRHS ? PF_ASSIGNRHS : 0; + + /* If we are expanding in a context where word splitting will not be + performed, treat as quoted. This changes how $* will be expanded. */ + if (pchar == '*' && (mflags & MATCH_ASSIGNRHS) && expand_no_split_dollar_star && ifs_is_null) + qflags |= Q_DOUBLE_QUOTES; /* Posix interp 888 */ + + ret = string_list_pos_params (pchar, save, qflags, pflags); + dispose_words (save); + + return (ret); +} + +/* Perform pattern substitution on VALUE, which is the expansion of + VARNAME. PATSUB is an expression supplying the pattern to match + and the string to substitute. QUOTED is a flags word containing + the type of quoting currently in effect. */ +static char * +parameter_brace_patsub (varname, value, estatep, patsub, quoted, pflags, flags) + char *varname, *value; + array_eltstate_t *estatep; + char *patsub; + int quoted, pflags, flags; +{ + int vtype, mflags, starsub, delim; + char *val, *temp, *pat, *rep, *p, *lpatsub, *tt, *oname; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + oname = this_command_name; + this_command_name = varname; /* error messages */ + + vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val); + if (vtype == -1) + { + this_command_name = oname; + return ((char *)NULL); + } + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + mflags = 0; + /* PATSUB is never NULL when this is called. */ + if (*patsub == '/') + { + mflags |= MATCH_GLOBREP; + patsub++; + } + + /* Malloc this because expand_string_if_necessary or one of the expansion + functions in its call chain may free it on a substitution error. */ + lpatsub = savestring (patsub); + + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + mflags |= MATCH_QUOTED; + + if (starsub) + mflags |= MATCH_STARSUB; + + if (pflags & PF_ASSIGNRHS) + mflags |= MATCH_ASSIGNRHS; + + /* If the pattern starts with a `/', make sure we skip over it when looking + for the replacement delimiter. */ + delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0); + if (lpatsub[delim] == '/') + { + lpatsub[delim] = 0; + rep = lpatsub + delim + 1; + } + else + rep = (char *)NULL; + + if (rep && *rep == '\0') + rep = (char *)NULL; + + /* Perform the same expansions on the pattern as performed by the + pattern removal expansions. */ + pat = getpattern (lpatsub, quoted, 1); + + if (rep) + { + /* We want to perform quote removal on the expanded replacement even if + the entire expansion is double-quoted because the parser and string + extraction functions treated quotes in the replacement string as + special. THIS IS NOT BACKWARDS COMPATIBLE WITH BASH-4.2. */ + if (shell_compatibility_level > 42 && patsub_replacement == 0) + rep = expand_string_if_necessary (rep, quoted & ~(Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT), expand_string_unsplit); + else if (shell_compatibility_level > 42 && patsub_replacement) + rep = expand_string_for_patsub (rep, quoted & ~(Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)); + /* This is the bash-4.2 code. */ + else if ((mflags & MATCH_QUOTED) == 0) + rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit); + else + rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit); + + /* Check whether or not to replace `&' in the replacement string after + expanding it, since we want to treat backslashes quoting the `&' + consistently. */ + if (patsub_replacement && rep && *rep && shouldexp_replacement (rep)) + mflags |= MATCH_EXPREP; + + } + + /* ksh93 doesn't allow the match specifier to be a part of the expanded + pattern. This is an extension. Make sure we don't anchor the pattern + at the beginning or end of the string if we're doing global replacement, + though. */ + p = pat; + if (mflags & MATCH_GLOBREP) + mflags |= MATCH_ANY; + else if (pat && pat[0] == '#') + { + mflags |= MATCH_BEG; + p++; + } + else if (pat && pat[0] == '%') + { + mflags |= MATCH_END; + p++; + } + else + mflags |= MATCH_ANY; + + /* OK, we now want to substitute REP for PAT in VAL. If + flags & MATCH_GLOBREP is non-zero, the substitution is done + everywhere, otherwise only the first occurrence of PAT is + replaced. The pattern matching code doesn't understand + CTLESC quoting CTLESC and CTLNUL so we use the dequoted variable + values passed in (VT_VARIABLE) so the pattern substitution + code works right. We need to requote special chars after + we're done for VT_VARIABLE and VT_ARRAYMEMBER, and for the + other cases if QUOTED == 0, since the posparams and arrays + indexed by * or @ do special things when QUOTED != 0. */ + + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp = pat_subst (val, p, rep, mflags); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp) + { + tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp); + free (temp); + temp = tt; + } + break; + case VT_POSPARMS: + /* This does the right thing for the case where we are not performing + word splitting. MATCH_STARSUB restricts it to ${* /foo/bar}, and + pos_params_pat_subst/string_list_pos_params will do the right thing + in turn for the case where ifs_is_null. Posix interp 888 */ + if ((pflags & PF_NOSPLIT2) && (mflags & MATCH_STARSUB)) + mflags |= MATCH_ASSIGNRHS; + temp = pos_params_pat_subst (val, p, rep, mflags); + if (temp && quoted == 0 && ifs_is_null) + { + /* Posix interp 888 */ + } + else if (temp && quoted == 0 && (pflags & PF_ASSIGNRHS)) + { + /* Posix interp 888 */ + } + else if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + /* If we are expanding in a context where word splitting will not be + performed, treat as quoted. This changes how ${A[*]} will be + expanded to make it identical to $*. */ + if ((mflags & MATCH_STARSUB) && (mflags & MATCH_ASSIGNRHS) && ifs_is_null) + mflags |= MATCH_QUOTED; /* Posix interp 888 */ + + /* these eventually call string_list_pos_params */ + if (assoc_p (v)) + temp = assoc_patsub (assoc_cell (v), p, rep, mflags); + else + temp = array_patsub (array_cell (v), p, rep, mflags); + + if (temp && quoted == 0 && ifs_is_null) + { + /* Posix interp 888 */ + } + else if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; +#endif + } + + FREE (pat); + FREE (rep); + free (lpatsub); + + this_command_name = oname; + + return temp; +} + +/****************************************************************/ +/* */ +/* Functions to perform case modification on variable values */ +/* */ +/****************************************************************/ + +/* Do case modification on the positional parameters. */ + +static char * +pos_params_modcase (string, pat, modop, mflags) + char *string, *pat; + int modop; + int mflags; +{ + WORD_LIST *save, *params; + WORD_DESC *w; + char *ret; + int pchar, qflags, pflags; + + save = params = list_rest_of_args (); + if (save == 0) + return ((char *)NULL); + + for ( ; params; params = params->next) + { + ret = sh_modcase (params->word->word, pat, modop); + w = alloc_word_desc (); + w->word = ret ? ret : savestring (""); + dispose_word (params->word); + params->word = w; + } + + pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@'; + qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0; + pflags = (mflags & MATCH_ASSIGNRHS) == MATCH_ASSIGNRHS ? PF_ASSIGNRHS : 0; + + /* If we are expanding in a context where word splitting will not be + performed, treat as quoted. This changes how $* will be expanded. */ + if (pchar == '*' && (mflags & MATCH_ASSIGNRHS) && ifs_is_null) + qflags |= Q_DOUBLE_QUOTES; /* Posix interp 888 */ + + ret = string_list_pos_params (pchar, save, qflags, pflags); + dispose_words (save); + + return (ret); +} + +/* Perform case modification on VALUE, which is the expansion of + VARNAME. MODSPEC is an expression supplying the type of modification + to perform. QUOTED is a flags word containing the type of quoting + currently in effect. */ +static char * +parameter_brace_casemod (varname, value, estatep, modspec, patspec, quoted, pflags, flags) + char *varname, *value; + array_eltstate_t *estatep; + int modspec; + char *patspec; + int quoted, pflags, flags; +{ + int vtype, starsub, modop, mflags, x; + char *val, *temp, *pat, *p, *lpat, *tt, *oname; + SHELL_VAR *v; + + if (value == 0) + return ((char *)NULL); + + oname = this_command_name; + this_command_name = varname; + + vtype = get_var_and_type (varname, value, estatep, quoted, flags, &v, &val); + if (vtype == -1) + { + this_command_name = oname; + return ((char *)NULL); + } + + starsub = vtype & VT_STARSUB; + vtype &= ~VT_STARSUB; + + modop = 0; + mflags = 0; + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + mflags |= MATCH_QUOTED; + if (starsub) + mflags |= MATCH_STARSUB; + if (pflags & PF_ASSIGNRHS) + mflags |= MATCH_ASSIGNRHS; + + p = patspec; + if (modspec == '^') + { + x = p && p[0] == modspec; + modop = x ? CASE_UPPER : CASE_UPFIRST; + p += x; + } + else if (modspec == ',') + { + x = p && p[0] == modspec; + modop = x ? CASE_LOWER : CASE_LOWFIRST; + p += x; + } + else if (modspec == '~') + { + x = p && p[0] == modspec; + modop = x ? CASE_TOGGLEALL : CASE_TOGGLE; + p += x; + } + + lpat = p ? savestring (p) : 0; + /* Perform the same expansions on the pattern as performed by the + pattern removal expansions. */ + pat = lpat ? getpattern (lpat, quoted, 1) : 0; + + /* OK, now we do the case modification. */ + switch (vtype) + { + case VT_VARIABLE: + case VT_ARRAYMEMBER: + temp = sh_modcase (val, pat, modop); + if (vtype == VT_VARIABLE) + FREE (val); + if (temp) + { + tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp); + free (temp); + temp = tt; + } + break; + + case VT_POSPARMS: + temp = pos_params_modcase (val, pat, modop, mflags); + if (temp && quoted == 0 && ifs_is_null) + { + /* Posix interp 888 */ + } + else if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + break; + +#if defined (ARRAY_VARS) + case VT_ARRAYVAR: + /* If we are expanding in a context where word splitting will not be + performed, treat as quoted. This changes how ${A[*]} will be + expanded to make it identical to $*. */ + if ((mflags & MATCH_STARSUB) && (mflags & MATCH_ASSIGNRHS) && ifs_is_null) + mflags |= MATCH_QUOTED; /* Posix interp 888 */ + + temp = assoc_p (v) ? assoc_modcase (assoc_cell (v), pat, modop, mflags) + : array_modcase (array_cell (v), pat, modop, mflags); + + if (temp && quoted == 0 && ifs_is_null) + { + /* Posix interp 888 */ + } + else if (temp && (mflags & MATCH_QUOTED) == 0) + { + tt = quote_escapes (temp); + free (temp); + temp = tt; + } + + break; +#endif + } + + FREE (pat); + free (lpat); + + this_command_name = oname; + + return temp; +} + +/* Check for unbalanced parens in S, which is the contents of $(( ... )). If + any occur, this must be a nested command substitution, so return 0. + Otherwise, return 1. A valid arithmetic expression must always have a + ( before a matching ), so any cases where there are more right parens + means that this must not be an arithmetic expression, though the parser + will not accept it without a balanced total number of parens. */ +static int +chk_arithsub (s, len) + const char *s; + int len; +{ + int i, count; + DECLARE_MBSTATE; + + i = count = 0; + while (i < len) + { + if (s[i] == LPAREN) + count++; + else if (s[i] == RPAREN) + { + count--; + if (count < 0) + return 0; + } + + switch (s[i]) + { + default: + ADVANCE_CHAR (s, len, i); + break; + + case '\\': + i++; + if (s[i]) + ADVANCE_CHAR (s, len, i); + break; + + case '\'': + i = skip_single_quoted (s, len, ++i, 0); + break; + + case '"': + i = skip_double_quoted ((char *)s, len, ++i, 0); + break; + } + } + + return (count == 0); +} + +/****************************************************************/ +/* */ +/* Functions to perform parameter expansion on a string */ +/* */ +/****************************************************************/ + +/* ${[#][!]name[[:][^[^]][,[,]]#[#]%[%]-=?+[word][:e1[:e2]]]} */ +static WORD_DESC * +parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, contains_dollar_at) + char *string; + int *indexp, quoted, pflags, *quoted_dollar_atp, *contains_dollar_at; +{ + int check_nullness, var_is_set, var_is_null, var_is_special; + int want_substring, want_indir, want_patsub, want_casemod, want_attributes; + char *name, *value, *temp, *temp1; + WORD_DESC *tdesc, *ret; + int t_index, sindex, c, tflag, modspec, local_pflags, all_element_arrayref; + intmax_t number; + array_eltstate_t es; + + temp = temp1 = value = (char *)NULL; + var_is_set = var_is_null = var_is_special = check_nullness = 0; + want_substring = want_indir = want_patsub = want_casemod = want_attributes = 0; + + local_pflags = 0; + all_element_arrayref = 0; + + sindex = *indexp; + t_index = ++sindex; + /* ${#var} doesn't have any of the other parameter expansions on it. */ + if (string[t_index] == '#' && legal_variable_starter (string[t_index+1])) /* {{ */ + name = string_extract (string, &t_index, "}", SX_VARNAME); + else +#if defined (CASEMOD_EXPANSIONS) + /* To enable case-toggling expansions using the `~' operator character + define CASEMOD_TOGGLECASE in config-top.h */ +# if defined (CASEMOD_TOGGLECASE) + name = string_extract (string, &t_index, "#%^,~:-=?+/@}", SX_VARNAME); +# else + name = string_extract (string, &t_index, "#%^,:-=?+/@}", SX_VARNAME); +# endif /* CASEMOD_TOGGLECASE */ +#else + name = string_extract (string, &t_index, "#%:-=?+/@}", SX_VARNAME); +#endif /* CASEMOD_EXPANSIONS */ + + /* Handle ${@[stuff]} now that @ is a word expansion operator. Not exactly + the cleanest code ever. */ + if (*name == 0 && sindex == t_index && string[sindex] == '@') + { + name = (char *)xrealloc (name, 2); + name[0] = '@'; + name[1] = '\0'; + t_index++; + } + else if (*name == '!' && t_index > sindex && string[t_index] == '@' && string[t_index+1] == RBRACE) + { + name = (char *)xrealloc (name, t_index - sindex + 2); + name[t_index - sindex] = '@'; + name[t_index - sindex + 1] = '\0'; + t_index++; + } + + ret = 0; + tflag = 0; + +#if defined (ARRAY_VARS) + init_eltstate (&es); +#endif + es.ind = INTMAX_MIN; /* XXX */ + + /* If the name really consists of a special variable, then make sure + that we have the entire name. We don't allow indirect references + to special variables except `#', `?', `@' and `*'. This clause is + designed to handle ${#SPECIAL} and ${!SPECIAL}, not anything more + general. */ + if ((sindex == t_index && VALID_SPECIAL_LENGTH_PARAM (string[t_index])) || + (sindex == t_index && string[sindex] == '#' && VALID_SPECIAL_LENGTH_PARAM (string[sindex + 1])) || + (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index]))) + { + t_index++; + temp1 = string_extract (string, &t_index, "#%:-=?+/@}", 0); + name = (char *)xrealloc (name, 3 + (strlen (temp1))); + *name = string[sindex]; + if (string[sindex] == '!') + { + /* indirect reference of $#, $?, $@, or $* */ + name[1] = string[sindex + 1]; + strcpy (name + 2, temp1); + } + else + strcpy (name + 1, temp1); + free (temp1); + } + sindex = t_index; + + /* Find out what character ended the variable name. Then + do the appropriate thing. */ + if (c = string[sindex]) + sindex++; + + /* If c is followed by one of the valid parameter expansion + characters, move past it as normal. If not, assume that + a substring specification is being given, and do not move + past it. */ + if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex])) + { + check_nullness++; + if (c = string[sindex]) + sindex++; + } + else if (c == ':' && string[sindex] != RBRACE) + want_substring = 1; + else if (c == '/' /* && string[sindex] != RBRACE */) /* XXX */ + want_patsub = 1; +#if defined (CASEMOD_EXPANSIONS) + else if (c == '^' || c == ',' || c == '~') + { + modspec = c; + want_casemod = 1; + } +#endif + else if (c == '@' && (string[sindex] == 'a' || string[sindex] == 'A') && string[sindex+1] == RBRACE) + { + /* special case because we do not want to shortcut foo as foo[0] here */ + want_attributes = 1; + local_pflags |= PF_ALLINDS; + } + + /* Catch the valid and invalid brace expressions that made it through the + tests above. */ + /* ${#-} is a valid expansion and means to take the length of $-. + Similarly for ${#?} and ${##}... */ + if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && + VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE) + { + name = (char *)xrealloc (name, 3); + name[1] = c; + name[2] = '\0'; + c = string[sindex++]; + } + + /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */ + if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && + member (c, "%:=+/") && string[sindex] == RBRACE) + { + temp = (char *)NULL; + goto bad_substitution; /* XXX - substitution error */ + } + + /* Indirect expansion begins with a `!'. A valid indirect expansion is + either a variable name, one of the positional parameters or a special + variable that expands to one of the positional parameters. */ + want_indir = *name == '!' && + (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1]) + || VALID_INDIR_PARAM (name[1])); + + /* Determine the value of this variable whose name is NAME. */ + + /* Check for special variables, directly referenced. */ + if (SPECIAL_VAR (name, want_indir)) + var_is_special++; + + /* Check for special expansion things, like the length of a parameter */ + if (*name == '#' && name[1]) + { + /* If we are not pointing at the character just after the + closing brace, then we haven't gotten all of the name. + Since it begins with a special character, this is a bad + substitution. Also check NAME for validity before trying + to go on. */ + if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0)) + { + temp = (char *)NULL; + goto bad_substitution; /* substitution error */ + } + + number = parameter_brace_expand_length (name); + if (number == INTMAX_MIN && unbound_vars_is_error) + { + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (name+1); + free (name); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + free (name); + + *indexp = sindex; + if (number < 0) + return (&expand_wdesc_error); + else + { + ret = alloc_word_desc (); + ret->word = itos (number); + return ret; + } + } + + /* ${@} is identical to $@. */ + if (name[0] == '@' && name[1] == '\0') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + + if (contains_dollar_at) + *contains_dollar_at = 1; + + tflag |= W_DOLLARAT; + } + + /* Process ${!PREFIX*} expansion. */ + if (want_indir && string[sindex - 1] == RBRACE && + (string[sindex - 2] == '*' || string[sindex - 2] == '@') && + legal_variable_starter ((unsigned char) name[1])) + { + char **x; + WORD_LIST *xlist; + + temp1 = savestring (name + 1); + number = strlen (temp1); + temp1[number - 1] = '\0'; + x = all_variables_matching_prefix (temp1); + xlist = strvec_to_word_list (x, 0, 0); + if (string[sindex - 2] == '*') + temp = string_list_dollar_star (xlist, quoted, 0); + else + { + temp = string_list_dollar_at (xlist, quoted, 0); + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + + tflag |= W_DOLLARAT; + } + free (x); + dispose_words (xlist); + free (temp1); + *indexp = sindex; + + free (name); + + ret = alloc_word_desc (); + ret->word = temp; + ret->flags = tflag; /* XXX */ + return ret; + } + +#if defined (ARRAY_VARS) + /* Process ${!ARRAY[@]} and ${!ARRAY[*]} expansion. */ + if (want_indir && string[sindex - 1] == RBRACE && + string[sindex - 2] == RBRACK && valid_array_reference (name+1, 0)) + { + char *x, *x1; + + temp1 = savestring (name + 1); + x = array_variable_name (temp1, 0, &x1, (int *)0); + FREE (x); + if (ALL_ELEMENT_SUB (x1[0]) && x1[1] == RBRACK) + { + temp = array_keys (temp1, quoted, pflags); /* handles assoc vars too */ + if (x1[0] == '@') + { + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + + tflag |= W_DOLLARAT; + } + + free (name); + free (temp1); + *indexp = sindex; + + ret = alloc_word_desc (); + ret->word = temp; + ret->flags = tflag; /* XXX */ + return ret; + } + + free (temp1); + } +#endif /* ARRAY_VARS */ + + /* Make sure that NAME is valid before trying to go on. */ + if (valid_brace_expansion_word (want_indir ? name + 1 : name, + var_is_special) == 0) + { + temp = (char *)NULL; + goto bad_substitution; /* substitution error */ + } + + if (want_indir) + { + tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, pflags|local_pflags, quoted_dollar_atp, contains_dollar_at); + if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal) + { + temp = (char *)NULL; + goto bad_substitution; + } + + /* Turn off the W_ARRAYIND flag because there is no way for this function + to return the index we're supposed to be using. */ + if (tdesc && tdesc->flags) + tdesc->flags &= ~W_ARRAYIND; + + /* If the indir expansion contains $@/$*, extend the special treatment + of the case of no positional parameters and `set -u' to it. */ + if (contains_dollar_at && *contains_dollar_at) + all_element_arrayref = 1; + } + else + { + local_pflags |= PF_IGNUNBOUND|(pflags&(PF_NOSPLIT2|PF_ASSIGNRHS)); + tdesc = parameter_brace_expand_word (name, var_is_special, quoted, local_pflags, &es); + } + + if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal) + { + tflag = 0; + tdesc = 0; + } + + if (tdesc) + { + temp = tdesc->word; + tflag = tdesc->flags; + dispose_word_desc (tdesc); + } + else + temp = (char *)0; + + if (temp == &expand_param_error || temp == &expand_param_fatal) + { + FREE (name); + FREE (value); + return (temp == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal); + } + +#if defined (ARRAY_VARS) + if (valid_array_reference (name, 0)) + { + int qflags; + char *t; + + qflags = quoted; + /* If in a context where word splitting will not take place, treat as + if double-quoted. Has effects with $* and ${array[*]} */ + + if (pflags & PF_ASSIGNRHS) + qflags |= Q_DOUBLE_QUOTES; + /* We duplicate a little code here */ + t = mbschr (name, LBRACK); + if (t && ALL_ELEMENT_SUB (t[1]) && t[2] == RBRACK) + { + all_element_arrayref = 1; + if (expand_no_split_dollar_star && t[1] == '*') /* XXX */ + qflags |= Q_DOUBLE_QUOTES; + } + chk_atstar (name, qflags, pflags, quoted_dollar_atp, contains_dollar_at); + } +#endif + + var_is_set = temp != (char *)0; + var_is_null = check_nullness && (var_is_set == 0 || *temp == 0); + /* XXX - this may not need to be restricted to special variables */ + if (check_nullness) + var_is_null |= var_is_set && var_is_special && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && QUOTED_NULL (temp); +#if defined (ARRAY_VARS) + if (check_nullness) + var_is_null |= var_is_set && + (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && + QUOTED_NULL (temp) && + valid_array_reference (name, 0) && + chk_atstar (name, 0, 0, (int *)0, (int *)0); +#endif + + /* Get the rest of the stuff inside the braces. */ + if (c && c != RBRACE) + { + /* Extract the contents of the ${ ... } expansion + according to the Posix.2 rules. */ + value = extract_dollar_brace_string (string, &sindex, quoted, (c == '%' || c == '#' || c =='/' || c == '^' || c == ',' || c ==':') ? SX_POSIXEXP|SX_WORD : SX_WORD); + if (string[sindex] == RBRACE) + sindex++; + else + goto bad_substitution; /* substitution error */ + } + else + value = (char *)NULL; + + *indexp = sindex; + + /* All the cases where an expansion can possibly generate an unbound + variable error. */ + if (want_substring || want_patsub || want_casemod || c == '@' || c == '#' || c == '%' || c == RBRACE) + { + if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]) && all_element_arrayref == 0) + { + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (name); + FREE (value); + FREE (temp); + free (name); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + } + + /* If this is a substring spec, process it and add the result. */ + if (want_substring) + { + temp1 = parameter_brace_substring (name, temp, &es, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + FREE (value); + FREE (temp); +#if defined (ARRAY_VARS) + flush_eltstate (&es); +#endif + + if (temp1 == &expand_param_error || temp1 == &expand_param_fatal) + { + FREE (name); + return (temp1 == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal); + } + + ret = alloc_word_desc (); + ret->word = temp1; + /* We test quoted_dollar_atp because we want variants with double-quoted + "$@" to take a different code path. In fact, we make sure at the end + of expand_word_internal that we're only looking at these flags if + quoted_dollar_at == 0. */ + if (temp1 && + (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) && + QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + else if (temp1 && (name[0] == '*' && name[1] == 0) && quoted == 0 && + (pflags & PF_ASSIGNRHS)) + ret->flags |= W_SPLITSPACE; /* Posix interp 888 */ + /* Special handling for $* when unquoted and $IFS is null. Posix interp 888 */ + else if (temp1 && (name[0] == '*' && name[1] == 0) && quoted == 0 && ifs_is_null) + ret->flags |= W_SPLITSPACE; /* Posix interp 888 */ + + FREE (name); + return ret; + } + else if (want_patsub) + { + temp1 = parameter_brace_patsub (name, temp, &es, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + FREE (value); + FREE (temp); +#if defined (ARRAY_VARS) + flush_eltstate (&es); +#endif + + if (temp1 == &expand_param_error || temp1 == &expand_param_fatal) + { + FREE (name); + return (temp1 == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal); + } + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && + (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) && + QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + /* Special handling for $* when unquoted and $IFS is null. Posix interp 888 */ + else if (temp1 && (name[0] == '*' && name[1] == 0) && quoted == 0 && ifs_is_null) + ret->flags |= W_SPLITSPACE; /* Posix interp 888 */ + + FREE (name); + return ret; + } +#if defined (CASEMOD_EXPANSIONS) + else if (want_casemod) + { + temp1 = parameter_brace_casemod (name, temp, &es, modspec, value, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + FREE (value); + FREE (temp); +#if defined (ARRAY_VARS) + flush_eltstate (&es); +#endif + + if (temp1 == &expand_param_error || temp1 == &expand_param_fatal) + { + FREE (name); + return (temp1 == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal); + } + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && + (quoted_dollar_atp == 0 || *quoted_dollar_atp == 0) && + QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + /* Special handling for $* when unquoted and $IFS is null. Posix interp 888 */ + else if (temp1 && (name[0] == '*' && name[1] == 0) && quoted == 0 && ifs_is_null) + ret->flags |= W_SPLITSPACE; /* Posix interp 888 */ + + FREE (name); + return ret; + } +#endif + + /* Do the right thing based on which character ended the variable name. */ + switch (c) + { + default: + case '\0': +bad_substitution: + set_exit_status (EXECUTION_FAILURE); + report_error (_("%s: bad substitution"), string ? string : "??"); + FREE (value); + FREE (temp); + free (name); +#if defined (ARRAY_VARS) + flush_eltstate (&es); +#endif + if (shell_compatibility_level <= 43) + return &expand_wdesc_error; + else + return ((posixly_correct && interactive_shell == 0) ? &expand_wdesc_fatal : &expand_wdesc_error); + + case RBRACE: + break; + + case '@': + temp1 = parameter_brace_transform (name, temp, &es, value, c, quoted, pflags, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + free (temp); + free (value); +#if defined (ARRAY_VARS) + flush_eltstate (&es); +#endif + + if (temp1 == &expand_param_error || temp1 == &expand_param_fatal) + { + free (name); + set_exit_status (EXECUTION_FAILURE); + report_error (_("%s: bad substitution"), string ? string : "??"); + return (temp1 == &expand_param_error ? &expand_wdesc_error : &expand_wdesc_fatal); + } + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + /* Special handling for $* when unquoted and $IFS is null. Posix interp 888 */ + else if (temp1 && (name[0] == '*' && name[1] == 0) && quoted == 0 && ifs_is_null) + ret->flags |= W_SPLITSPACE; /* Posix interp 888 */ + + free (name); + return ret; + + case '#': /* ${param#[#]pattern} */ + case '%': /* ${param%[%]pattern} */ + if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0') + { + FREE (value); + break; + } + temp1 = parameter_brace_remove_pattern (name, temp, &es, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0); + free (temp); + free (value); +#if defined (ARRAY_VARS) + flush_eltstate (&es); +#endif + + ret = alloc_word_desc (); + ret->word = temp1; + if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ret->flags |= W_QUOTED|W_HASQUOTEDNULL; + /* Special handling for $* when unquoted and $IFS is null. Posix interp 888 */ + else if (temp1 && (name[0] == '*' && name[1] == 0) && quoted == 0 && ifs_is_null) + ret->flags |= W_SPLITSPACE; /* Posix interp 888 */ + + free (name); + return ret; + + case '-': + case '=': + case '?': + case '+': + if (var_is_set && var_is_null == 0) + { + /* If the operator is `+', we don't want the value of the named + variable for anything, just the value of the right hand side. */ + if (c == '+') + { + /* XXX -- if we're double-quoted and the named variable is "$@", + we want to turn off any special handling of "$@" -- + we're not using it, so whatever is on the rhs applies. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + + FREE (temp); + if (value) + { + /* From Posix discussion on austin-group list. Issue 221 + requires that backslashes escaping `}' inside + double-quoted ${...} be removed. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + quoted |= Q_DOLBRACE; + ret = parameter_brace_expand_rhs (name, value, c, + quoted, + pflags, + quoted_dollar_atp, + contains_dollar_at); + /* XXX - fix up later, esp. noting presence of + W_HASQUOTEDNULL in ret->flags */ + free (value); + } + else + temp = (char *)NULL; + } + else + { + FREE (value); + } + /* Otherwise do nothing; just use the value in TEMP. */ + } + else /* VAR not set or VAR is NULL. */ + { + /* If we're freeing a quoted null here, we need to remember we saw + it so we can restore it later if needed, or the caller can note it. + The check against `+' doesn't really matter, since the other cases + don't use or return TFLAG, but it's good for clarity. */ + if (c == '+' && temp && QUOTED_NULL (temp) && + (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + tflag |= W_HASQUOTEDNULL; + + FREE (temp); + temp = (char *)NULL; + if (c == '=' && var_is_special) + { + set_exit_status (EXECUTION_FAILURE); + report_error (_("$%s: cannot assign in this way"), name); + free (name); + free (value); +#if defined (ARRAY_VARS) + flush_eltstate (&es); +#endif + return &expand_wdesc_error; + } + else if (c == '?') + { + parameter_brace_expand_error (name, value, check_nullness); +#if defined (ARRAY_VARS) + flush_eltstate (&es); +#endif + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + else if (c != '+') + { + /* XXX -- if we're double-quoted and the named variable is "$@", + we want to turn off any special handling of "$@" -- + we're not using it, so whatever is on the rhs applies. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) + *quoted_dollar_atp = 0; + if (contains_dollar_at) + *contains_dollar_at = 0; + + /* From Posix discussion on austin-group list. Issue 221 requires + that backslashes escaping `}' inside double-quoted ${...} be + removed. */ + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + quoted |= Q_DOLBRACE; + ret = parameter_brace_expand_rhs (name, value, c, quoted, pflags, + quoted_dollar_atp, + contains_dollar_at); + /* XXX - fix up later, esp. noting presence of + W_HASQUOTEDNULL in tdesc->flags */ + } + free (value); + } + + break; + } + free (name); +#if defined (ARRAY_VARS) + flush_eltstate (&es); +#endif + + if (ret == 0) + { + ret = alloc_word_desc (); + ret->flags = tflag; + ret->word = temp; + } + return (ret); +} + +/* Expand a single ${xxx} expansion. The braces are optional. When + the braces are used, parameter_brace_expand() does the work, + possibly calling param_expand recursively. */ +static WORD_DESC * +param_expand (string, sindex, quoted, expanded_something, + contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p, + pflags) + char *string; + int *sindex, quoted, *expanded_something, *contains_dollar_at; + int *quoted_dollar_at_p, *had_quoted_null_p, pflags; +{ + char *temp, *temp1, uerror[3], *savecmd; + int zindex, t_index, expok, eflag; + unsigned char c; + intmax_t number; + SHELL_VAR *var; + WORD_LIST *list, *l; + WORD_DESC *tdesc, *ret; + int tflag, nullarg; + +/*itrace("param_expand: `%s' pflags = %d", string+*sindex, pflags);*/ + zindex = *sindex; + c = string[++zindex]; + + temp = (char *)NULL; + ret = tdesc = (WORD_DESC *)NULL; + tflag = 0; + + /* Do simple cases first. Switch on what follows '$'. */ + switch (c) + { + /* $0 .. $9? */ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + temp1 = dollar_vars[TODIGIT (c)]; + /* This doesn't get called when (pflags&PF_IGNUNBOUND) != 0 */ + if (unbound_vars_is_error && temp1 == (char *)NULL) + { + uerror[0] = '$'; + uerror[1] = c; + uerror[2] = '\0'; + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + if (temp1) + temp = (*temp1 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp1) + : quote_escapes (temp1); + else + temp = (char *)NULL; + + break; + + /* $$ -- pid of the invoking shell. */ + case '$': + temp = itos (dollar_dollar_pid); + break; + + /* $# -- number of positional parameters. */ + case '#': + temp = itos (number_of_args ()); + break; + + /* $? -- return value of the last synchronous command. */ + case '?': + temp = itos (last_command_exit_value); + break; + + /* $- -- flags supplied to the shell on invocation or by `set'. */ + case '-': + temp = which_set_flags (); + break; + + /* $! -- Pid of the last asynchronous command. */ + case '!': + /* If no asynchronous pids have been created, expand to nothing. + If `set -u' has been executed, and no async processes have + been created, this is an expansion error. */ + if (last_asynchronous_pid == NO_PID) + { + if (expanded_something) + *expanded_something = 0; + temp = (char *)NULL; + if (unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0) + { + uerror[0] = '$'; + uerror[1] = c; + uerror[2] = '\0'; + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } + } + else + temp = itos (last_asynchronous_pid); + break; + + /* The only difference between this and $@ is when the arg is quoted. */ + case '*': /* `$*' */ + list = list_rest_of_args (); + +#if 0 + /* According to austin-group posix proposal by Geoff Clare in + <20090505091501.GA10097@squonk.masqnet> of 5 May 2009: + + "The shell shall write a message to standard error and + immediately exit when it tries to expand an unset parameter + other than the '@' and '*' special parameters." + */ + + if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0) + { + uerror[0] = '$'; + uerror[1] = '*'; + uerror[2] = '\0'; + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } +#endif + + /* If there are no command-line arguments, this should just + disappear if there are other characters in the expansion, + even if it's quoted. */ + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0) + temp = (char *)NULL; + else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE)) + { + /* If we have "$*" we want to make a string of the positional + parameters, separated by the first character of $IFS, and + quote the whole string, including the separators. If IFS + is unset, the parameters are separated by ' '; if $IFS is + null, the parameters are concatenated. */ + temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list, quoted, 0) : string_list (list); + if (temp) + { + temp1 = (quoted & Q_DOUBLE_QUOTES) ? quote_string (temp) : temp; + if (*temp == 0) + tflag |= W_HASQUOTEDNULL; + if (temp != temp1) + free (temp); + temp = temp1; + } + } + else + { + /* We check whether or not we're eventually going to split $* here, + for example when IFS is empty and we are processing the rhs of + an assignment statement. In that case, we don't separate the + arguments at all. Otherwise, if the $* is not quoted it is + identical to $@ */ + if (expand_no_split_dollar_star && quoted == 0 && ifs_is_set == 0 && (pflags & PF_ASSIGNRHS)) + { + /* Posix interp 888: RHS of assignment, IFS unset: no splitting, + separate with space */ + temp1 = string_list_dollar_star (list, quoted, pflags); + temp = temp1 ? quote_string (temp1) : temp1; + /* XXX - tentative - note that we saw a quoted null here */ + if (temp1 && *temp1 == 0 && QUOTED_NULL (temp)) + tflag |= W_SAWQUOTEDNULL; + FREE (temp1); + } + else if (expand_no_split_dollar_star && quoted == 0 && ifs_is_null && (pflags & PF_ASSIGNRHS)) + { + /* Posix interp 888: RHS of assignment, IFS set to '' */ + temp1 = string_list_dollar_star (list, quoted, pflags); + temp = temp1 ? quote_escapes (temp1) : temp1; + FREE (temp1); + } + else if (expand_no_split_dollar_star && quoted == 0 && ifs_is_set && ifs_is_null == 0 && (pflags & PF_ASSIGNRHS)) + { + /* Posix interp 888: RHS of assignment, IFS set to non-null value */ + temp1 = string_list_dollar_star (list, quoted, pflags); + temp = temp1 ? quote_string (temp1) : temp1; + + /* XXX - tentative - note that we saw a quoted null here */ + if (temp1 && *temp1 == 0 && QUOTED_NULL (temp)) + tflag |= W_SAWQUOTEDNULL; + FREE (temp1); + } + /* XXX - should we check ifs_is_set here as well? */ +# if defined (HANDLE_MULTIBYTE) + else if (expand_no_split_dollar_star && ifs_firstc[0] == 0) +# else + else if (expand_no_split_dollar_star && ifs_firstc == 0) +# endif + /* Posix interp 888: not RHS, no splitting, IFS set to '' */ + temp = string_list_dollar_star (list, quoted, 0); + else + { + temp = string_list_dollar_at (list, quoted, 0); + /* Set W_SPLITSPACE to make sure the individual positional + parameters are split into separate arguments */ +#if 0 + if (quoted == 0 && (ifs_is_set == 0 || ifs_is_null)) +#else /* change with bash-5.0 */ + if (quoted == 0 && ifs_is_null) +#endif + tflag |= W_SPLITSPACE; + /* If we're not quoted but we still don't want word splitting, make + we quote the IFS characters to protect them from splitting (e.g., + when $@ is in the string as well). */ + else if (temp && quoted == 0 && ifs_is_set && (pflags & PF_ASSIGNRHS)) + { + temp1 = quote_string (temp); + free (temp); + temp = temp1; + } + } + + if (expand_no_split_dollar_star == 0 && contains_dollar_at) + *contains_dollar_at = 1; + } + + dispose_words (list); + break; + + /* When we have "$@" what we want is "$1" "$2" "$3" ... This + means that we have to turn quoting off after we split into + the individually quoted arguments so that the final split + on the first character of $IFS is still done. */ + case '@': /* `$@' */ + list = list_rest_of_args (); + +#if 0 + /* According to austin-group posix proposal by Geoff Clare in + <20090505091501.GA10097@squonk.masqnet> of 5 May 2009: + + "The shell shall write a message to standard error and + immediately exit when it tries to expand an unset parameter + other than the '@' and '*' special parameters." + */ + + if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0) + { + uerror[0] = '$'; + uerror[1] = '@'; + uerror[2] = '\0'; + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (uerror); + return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal); + } +#endif + + for (nullarg = 0, l = list; l; l = l->next) + { + if (l->word && (l->word->word == 0 || l->word->word[0] == 0)) + nullarg = 1; + } + + /* We want to flag the fact that we saw this. We can't turn + off quoting entirely, because other characters in the + string might need it (consider "\"$@\""), but we need some + way to signal that the final split on the first character + of $IFS should be done, even though QUOTED is 1. */ + /* XXX - should this test include Q_PATQUOTE? */ + if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + *quoted_dollar_at_p = 1; + if (contains_dollar_at) + *contains_dollar_at = 1; + + /* We want to separate the positional parameters with the first + character of $IFS in case $IFS is something other than a space. + We also want to make sure that splitting is done no matter what -- + according to POSIX.2, this expands to a list of the positional + parameters no matter what IFS is set to. */ + /* XXX - what to do when in a context where word splitting is not + performed? Even when IFS is not the default, posix seems to imply + that we have to expand $@ to all the positional parameters and + separate them with spaces, which are preserved because word splitting + doesn't take place. See below for how we use PF_NOSPLIT2 here. */ + + /* These are the cases where word splitting will not be performed. */ + if (pflags & PF_ASSIGNRHS) + { + temp = string_list_dollar_at (list, (quoted|Q_DOUBLE_QUOTES), pflags); + if (nullarg) + tflag |= W_HASQUOTEDNULL; /* we know quoting produces quoted nulls */ + } + + /* This needs to match what expand_word_internal does with non-quoted $@ + does with separating with spaces. Passing Q_DOUBLE_QUOTES means that + the characters in LIST will be quoted, and PF_ASSIGNRHS ensures that + they will separated by spaces. After doing this, we need the special + handling for PF_NOSPLIT2 in expand_word_internal to remove the CTLESC + quotes. */ + else if (pflags & PF_NOSPLIT2) + { +#if defined (HANDLE_MULTIBYTE) + if (quoted == 0 && ifs_is_set && ifs_is_null == 0 && ifs_firstc[0] != ' ') +#else + if (quoted == 0 && ifs_is_set && ifs_is_null == 0 && ifs_firstc != ' ') +#endif + /* Posix interp 888 */ + temp = string_list_dollar_at (list, Q_DOUBLE_QUOTES, pflags); + else + temp = string_list_dollar_at (list, quoted, pflags); + } + else + temp = string_list_dollar_at (list, quoted, pflags); + + tflag |= W_DOLLARAT; + dispose_words (list); + break; + + case LBRACE: + tdesc = parameter_brace_expand (string, &zindex, quoted, pflags, + quoted_dollar_at_p, + contains_dollar_at); + + if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal) + return (tdesc); + temp = tdesc ? tdesc->word : (char *)0; + + /* XXX */ + /* Quoted nulls should be removed if there is anything else + in the string. */ + /* Note that we saw the quoted null so we can add one back at + the end of this function if there are no other characters + in the string, discard TEMP, and go on. The exception to + this is when we have "${@}" and $1 is '', since $@ needs + special handling. */ + if (tdesc && tdesc->word && (tdesc->flags & W_HASQUOTEDNULL) && QUOTED_NULL (temp)) + { + if (had_quoted_null_p) + *had_quoted_null_p = 1; + if (*quoted_dollar_at_p == 0) + { + free (temp); + tdesc->word = temp = (char *)NULL; + } + + } + + ret = tdesc; + goto return0; + + /* Do command or arithmetic substitution. */ + case LPAREN: + /* We have to extract the contents of this paren substitution. */ + t_index = zindex + 1; + /* XXX - might want to check for string[t_index+2] == LPAREN and parse + as arithmetic substitution immediately. */ + temp = extract_command_subst (string, &t_index, (pflags&PF_COMPLETE) ? SX_COMPLETE : 0); + zindex = t_index; + + /* For Posix.2-style `$(( ))' arithmetic substitution, + extract the expression and pass it to the evaluator. */ + if (temp && *temp == LPAREN) + { + char *temp2; + temp1 = temp + 1; + temp2 = savestring (temp1); + t_index = strlen (temp2) - 1; + + if (temp2[t_index] != RPAREN) + { + free (temp2); + goto comsub; + } + + /* Cut off ending `)' */ + temp2[t_index] = '\0'; + + if (chk_arithsub (temp2, t_index) == 0) + { + free (temp2); +#if 0 + internal_warning (_("future versions of the shell will force evaluation as an arithmetic substitution")); +#endif + goto comsub; + } + + /* Expand variables found inside the expression. */ + temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES|Q_ARITH); + free (temp2); + +arithsub: + /* No error messages. */ + savecmd = this_command_name; + this_command_name = (char *)NULL; + + eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED; + number = evalexp (temp1, eflag, &expok); + this_command_name = savecmd; + free (temp); + free (temp1); + if (expok == 0) + { + if (interactive_shell == 0 && posixly_correct) + { + set_exit_status (EXECUTION_FAILURE); + return (&expand_wdesc_fatal); + } + else + return (&expand_wdesc_error); + } + temp = itos (number); + break; + } + +comsub: + if (pflags & PF_NOCOMSUB) + /* we need zindex+1 because string[zindex] == RPAREN */ + temp1 = substring (string, *sindex, zindex+1); + else + { + tdesc = command_substitute (temp, quoted, pflags&PF_ASSIGNRHS); + temp1 = tdesc ? tdesc->word : (char *)NULL; + if (tdesc) + dispose_word_desc (tdesc); + } + FREE (temp); + temp = temp1; + break; + + /* Do POSIX.2d9-style arithmetic substitution. This will probably go + away in a future bash release. */ + case '[': /*]*/ + /* Extract the contents of this arithmetic substitution. */ + t_index = zindex + 1; + temp = extract_arithmetic_subst (string, &t_index); + zindex = t_index; + if (temp == 0) + { + temp = savestring (string); + if (expanded_something) + *expanded_something = 0; + goto return0; + } + + /* Do initial variable expansion. */ + temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES|Q_ARITH); + + goto arithsub; + + default: + /* Find the variable in VARIABLE_LIST. */ + temp = (char *)NULL; + + for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++) + ; + temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL; + + /* If this isn't a variable name, then just output the `$'. */ + if (temp1 == 0 || *temp1 == '\0') + { + FREE (temp1); + temp = (char *)xmalloc (2); + temp[0] = '$'; + temp[1] = '\0'; + if (expanded_something) + *expanded_something = 0; + goto return0; + } + + /* If the variable exists, return its value cell. */ + var = find_variable (temp1); + + if (var && invisible_p (var) == 0 && var_isset (var)) + { +#if defined (ARRAY_VARS) + if (assoc_p (var) || array_p (var)) + { + temp = array_p (var) ? array_reference (array_cell (var), 0) + : assoc_reference (assoc_cell (var), "0"); + if (temp) + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : quote_escapes (temp); + else if (unbound_vars_is_error) + goto unbound_variable; + } + else +#endif + { + temp = value_cell (var); + + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : ((pflags & PF_ASSIGNRHS) ? quote_rhs (temp) + : quote_escapes (temp)); + } + + free (temp1); + + goto return0; + } + else if (var && (invisible_p (var) || var_isset (var) == 0)) + temp = (char *)NULL; + else if ((var = find_variable_last_nameref (temp1, 0)) && var_isset (var) && invisible_p (var) == 0) + { + temp = nameref_cell (var); +#if defined (ARRAY_VARS) + if (temp && *temp && valid_array_reference (temp, 0)) + { + chk_atstar (temp, quoted, pflags, quoted_dollar_at_p, contains_dollar_at); + tdesc = parameter_brace_expand_word (temp, SPECIAL_VAR (temp, 0), quoted, pflags, 0); + if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal) + return (tdesc); + ret = tdesc; + goto return0; + } + else +#endif + /* y=2 ; typeset -n x=y; echo $x is not the same as echo $2 in ksh */ + if (temp && *temp && legal_identifier (temp) == 0) + { + set_exit_status (EXECUTION_FAILURE); + report_error (_("%s: invalid variable name for name reference"), temp); + return (&expand_wdesc_error); /* XXX */ + } + else + temp = (char *)NULL; + } + + temp = (char *)NULL; + +unbound_variable: + if (unbound_vars_is_error) + { + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (temp1); + } + else + { + free (temp1); + goto return0; + } + + free (temp1); + set_exit_status (EXECUTION_FAILURE); + return ((unbound_vars_is_error && interactive_shell == 0) + ? &expand_wdesc_fatal + : &expand_wdesc_error); + } + + if (string[zindex]) + zindex++; + +return0: + *sindex = zindex; + + if (ret == 0) + { + ret = alloc_word_desc (); + ret->flags = tflag; /* XXX */ + ret->word = temp; + } + return ret; +} + +#if defined (ARRAY_VARS) +/* Characters that need to be backslash-quoted after expanding array subscripts */ +static char abstab[256] = { '\1' }; + +/* Run an array subscript through the appropriate word expansions. */ +char * +expand_subscript_string (string, quoted) + char *string; + int quoted; +{ + WORD_DESC td; + WORD_LIST *tlist; + int oe; + char *ret; + + if (string == 0 || *string == 0) + return (char *)NULL; + + oe = expand_no_split_dollar_star; + ret = (char *)NULL; + + td.flags = W_NOPROCSUB|W_NOTILDE|W_NOSPLIT2; /* XXX - W_NOCOMSUB? */ + td.word = savestring (string); /* in case it's freed on error */ + + expand_no_split_dollar_star = 1; + tlist = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); + expand_no_split_dollar_star = oe; + + if (tlist) + { + if (tlist->word) + { + remove_quoted_nulls (tlist->word->word); + tlist->word->flags &= ~W_HASQUOTEDNULL; + } + dequote_list (tlist); + ret = string_list (tlist); + dispose_words (tlist); + } + + free (td.word); + return (ret); +} + +/* Expand the subscript in STRING, which is an array reference. To ensure we + only expand it once, we quote the characters that would start another + expansion and the bracket characters that are special to array subscripts. */ +static char * +expand_array_subscript (string, sindex, quoted, flags) + char *string; + int *sindex; + int quoted, flags; +{ + char *ret, *exp, *t; + size_t slen; + int si, ni; + + si = *sindex; + slen = STRLEN (string); + + if (abstab[0] == '\1') + { + /* These are basically the characters that start shell expansions plus + the characters that delimit subscripts. */ + memset (abstab, '\0', sizeof (abstab)); + abstab[LBRACK] = abstab[RBRACK] = 1; + abstab['$'] = abstab['`'] = abstab['~'] = 1; + abstab['\\'] = abstab['\''] = 1; + abstab['"'] = 1; /* XXX */ + /* We don't quote `@' or `*' in the subscript at all. */ + } + + /* string[si] == LBRACK */ + ni = skipsubscript (string, si, 0); + /* These checks mirror the ones in valid_array_reference. The check for + (ni - si) == 1 checks for empty subscripts. We don't check that the + subscript is a separate word if we're parsing an arithmetic expression. */ + if (ni >= slen || string[ni] != RBRACK || (ni - si) == 1 || + (string[ni+1] != '\0' && (quoted & Q_ARITH) == 0)) + { + /* let's check and see what fails this check */ + INTERNAL_DEBUG (("expand_array_subscript: bad subscript string: `%s'", string+si)); + ret = (char *)xmalloc (2); /* badly-formed subscript */ + ret[0] = string[si]; + ret[1] = '\0'; + *sindex = si + 1; + return ret; + } + + /* STRING[ni] == RBRACK */ + exp = substring (string, si+1, ni); + t = expand_subscript_string (exp, quoted & ~(Q_ARITH|Q_DOUBLE_QUOTES)); + free (exp); + exp = sh_backslash_quote (t, abstab, 0); + free (t); + + slen = STRLEN (exp); + ret = xmalloc (slen + 2 + 1); + ret[0] ='['; + strcpy (ret + 1, exp); + ret[slen + 1] = ']'; + ret[slen + 2] = '\0'; + + free (exp); + *sindex = ni + 1; + + return ret; +} +#endif + +void +invalidate_cached_quoted_dollar_at () +{ + dispose_words (cached_quoted_dollar_at); + cached_quoted_dollar_at = 0; +} + +/* Make a word list which is the result of parameter and variable + expansion, command substitution, arithmetic substitution, and + quote removal of WORD. Return a pointer to a WORD_LIST which is + the result of the expansion. If WORD contains a null word, the + word list returned is also null. + + QUOTED contains flag values defined in shell.h. + + ISEXP is used to tell expand_word_internal that the word should be + treated as the result of an expansion. This has implications for + how IFS characters in the word are treated. + + CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null + they point to an integer value which receives information about expansion. + CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero. + EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions, + else zero. + + This only does word splitting in the case of $@ expansion. In that + case, we split on ' '. */ + +/* Values for the local variable quoted_state. */ +#define UNQUOTED 0 +#define PARTIALLY_QUOTED 1 +#define WHOLLY_QUOTED 2 + +static WORD_LIST * +expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something) + WORD_DESC *word; + int quoted, isexp; + int *contains_dollar_at; + int *expanded_something; +{ + WORD_LIST *list; + WORD_DESC *tword; + + /* The intermediate string that we build while expanding. */ + char *istring; + + /* The current size of the above object. */ + size_t istring_size; + + /* Index into ISTRING. */ + size_t istring_index; + + /* Temporary string storage. */ + char *temp, *temp1; + + /* The text of WORD. */ + register char *string; + + /* The size of STRING. */ + size_t string_size; + + /* The index into STRING. */ + int sindex; + + /* This gets 1 if we see a $@ while quoted. */ + int quoted_dollar_at; + + /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on + whether WORD contains no quoting characters, a partially quoted + string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */ + int quoted_state; + + /* State flags */ + int had_quoted_null; + int has_quoted_ifs; /* did we add a quoted $IFS character here? */ + int has_dollar_at, temp_has_dollar_at; + int internal_tilde; + int split_on_spaces; + int local_expanded; + int tflag; + int pflags; /* flags passed to param_expand */ + int mb_cur_max; + + int assignoff; /* If assignment, offset of `=' */ + + register unsigned char c; /* Current character. */ + int t_index; /* For calls to string_extract_xxx. */ + + char twochars[2]; + + DECLARE_MBSTATE; + + /* OK, let's see if we can optimize a common idiom: "$@". This needs to make sure + that all of the flags callers care about (e.g., W_HASQUOTEDNULL) are set in + list->flags. */ + if (STREQ (word->word, "\"$@\"") && + (word->flags == (W_HASDOLLAR|W_QUOTED)) && + dollar_vars[1]) /* XXX - check IFS here as well? */ + { + if (contains_dollar_at) + *contains_dollar_at = 1; + if (expanded_something) + *expanded_something = 1; + if (cached_quoted_dollar_at) + return (copy_word_list (cached_quoted_dollar_at)); + list = list_rest_of_args (); + list = quote_list (list); + cached_quoted_dollar_at = copy_word_list (list); + return (list); + } + + istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE); + istring[istring_index = 0] = '\0'; + quoted_dollar_at = had_quoted_null = has_dollar_at = 0; + has_quoted_ifs = 0; + split_on_spaces = 0; + internal_tilde = 0; /* expanding =~ or :~ */ + quoted_state = UNQUOTED; + + string = word->word; + if (string == 0) + goto finished_with_string; + mb_cur_max = MB_CUR_MAX; + + /* Don't need the string length for the SADD... and COPY_ macros unless + multibyte characters are possible, but do need it for bounds checking. */ + string_size = (mb_cur_max > 1) ? strlen (string) : 1; + + if (contains_dollar_at) + *contains_dollar_at = 0; + + assignoff = -1; + + /* Begin the expansion. */ + + for (sindex = 0; ;) + { + c = string[sindex]; + + /* Case on top-level character. */ + switch (c) + { + case '\0': + goto finished_with_string; + + case CTLESC: + sindex++; +#if HANDLE_MULTIBYTE + if (mb_cur_max > 1 && string[sindex]) + { + SADD_MBQCHAR_BODY(temp, string, sindex, string_size); + } + else +#endif + { + temp = (char *)xmalloc (3); + temp[0] = CTLESC; + temp[1] = c = string[sindex]; + temp[2] = '\0'; + } + +dollar_add_string: + if (string[sindex]) + sindex++; + +add_string: + if (temp) + { + istring = sub_append_string (temp, istring, &istring_index, &istring_size); + temp = (char *)0; + } + + break; + +#if defined (PROCESS_SUBSTITUTION) + /* Process substitution. */ + case '<': + case '>': + { + /* XXX - technically this should only be expanded at the start + of a word */ + if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (word->flags & W_NOPROCSUB)) + { + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + t_index = sindex + 1; /* skip past both '<' and LPAREN */ + + temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index, 0); /*))*/ + sindex = t_index; + + /* If the process substitution specification is `<()', we want to + open the pipe for writing in the child and produce output; if + it is `>()', we want to open the pipe for reading in the child + and consume input. */ + temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0; + + FREE (temp1); + + goto dollar_add_string; + } +#endif /* PROCESS_SUBSTITUTION */ + +#if defined (ARRAY_VARS) + case '[': /*]*/ + if ((quoted & Q_ARITH) == 0 || shell_compatibility_level <= 51) + { + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) + goto add_ifs_character; + else + goto add_character; + } + else + { + temp = expand_array_subscript (string, &sindex, quoted, word->flags); + goto add_string; + } +#endif + + case '=': + /* Posix.2 section 3.6.1 says that tildes following `=' in words + which are not assignment statements are not expanded. If the + shell isn't in posix mode, though, we perform tilde expansion + on `likely candidate' unquoted assignment statements (flags + include W_ASSIGNMENT but not W_QUOTED). A likely candidate + contains an unquoted :~ or =~. Something to think about: we + now have a flag that says to perform tilde expansion on arguments + to `assignment builtins' like declare and export that look like + assignment statements. We now do tilde expansion on such words + even in POSIX mode. */ + if (word->flags & (W_ASSIGNRHS|W_NOTILDE)) + { + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + } + /* If we're not in posix mode or forcing assignment-statement tilde + expansion, note where the first `=' appears in the word and prepare + to do tilde expansion following the first `='. We have to keep + track of the first `=' (using assignoff) to avoid being confused + by an `=' in the rhs of the assignment statement. */ + if ((word->flags & W_ASSIGNMENT) && + (posixly_correct == 0 || (word->flags & W_TILDEEXP)) && + assignoff == -1 && sindex > 0) + assignoff = sindex; + if (sindex == assignoff && string[sindex+1] == '~') /* XXX */ + internal_tilde = 1; + + if (word->flags & W_ASSIGNARG) + word->flags |= W_ASSIGNRHS; /* affects $@ */ + + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + { + has_quoted_ifs++; + goto add_ifs_character; + } + else + goto add_character; + + case ':': + if (word->flags & (W_NOTILDE|W_NOASSNTILDE)) + { + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + } + + if ((word->flags & (W_ASSIGNMENT|W_ASSIGNRHS)) && + (posixly_correct == 0 || (word->flags & W_TILDEEXP)) && + string[sindex+1] == '~') + internal_tilde = 1; + + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c)) + goto add_ifs_character; + else + goto add_character; + + case '~': + /* If the word isn't supposed to be tilde expanded, or we're not + at the start of a word or after an unquoted : or = in an + assignment statement, we don't do tilde expansion. We don't + do tilde expansion if quoted or in an arithmetic context. */ + + if ((word->flags & W_NOTILDE) || + (sindex > 0 && (internal_tilde == 0)) || + (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + { + internal_tilde = 0; + if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0) + goto add_ifs_character; + else + goto add_character; + } + + if (word->flags & W_ASSIGNRHS) + tflag = 2; + else if (word->flags & (W_ASSIGNMENT|W_TILDEEXP)) + tflag = 1; + else + tflag = 0; + + temp = bash_tilde_find_word (string + sindex, tflag, &t_index); + + internal_tilde = 0; + + if (temp && *temp && t_index > 0) + { + temp1 = bash_tilde_expand (temp, tflag); + if (temp1 && *temp1 == '~' && STREQ (temp, temp1)) + { + FREE (temp); + FREE (temp1); + goto add_character; /* tilde expansion failed */ + } + free (temp); + temp = temp1; + sindex += t_index; + goto add_quoted_string; /* XXX was add_string */ + } + else + { + FREE (temp); + goto add_character; + } + + case '$': + if (expanded_something) + *expanded_something = 1; + local_expanded = 1; + + temp_has_dollar_at = 0; + pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0; + if (word->flags & W_NOSPLIT2) + pflags |= PF_NOSPLIT2; + if (word->flags & W_ASSIGNRHS) + pflags |= PF_ASSIGNRHS; + if (word->flags & W_COMPLETE) + pflags |= PF_COMPLETE; + + tword = param_expand (string, &sindex, quoted, expanded_something, + &temp_has_dollar_at, "ed_dollar_at, + &had_quoted_null, pflags); + has_dollar_at += temp_has_dollar_at; + split_on_spaces += (tword->flags & W_SPLITSPACE); + + if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal) + { + free (string); + free (istring); + return ((tword == &expand_wdesc_error) ? &expand_word_error + : &expand_word_fatal); + } + if (contains_dollar_at && has_dollar_at) + *contains_dollar_at = 1; + + if (tword && (tword->flags & W_HASQUOTEDNULL)) + had_quoted_null = 1; /* note for later */ + if (tword && (tword->flags & W_SAWQUOTEDNULL)) + had_quoted_null = 1; /* XXX */ + + temp = tword ? tword->word : (char *)NULL; + dispose_word_desc (tword); + + /* Kill quoted nulls; we will add them back at the end of + expand_word_internal if nothing else in the string */ + if (had_quoted_null && temp && QUOTED_NULL (temp)) + { + FREE (temp); + temp = (char *)NULL; + } + + goto add_string; + break; + + case '`': /* Backquoted command substitution. */ + { + t_index = sindex++; + + temp = string_extract (string, &sindex, "`", (word->flags & W_COMPLETE) ? SX_COMPLETE : SX_REQMATCH); + /* The test of sindex against t_index is to allow bare instances of + ` to pass through, for backwards compatibility. */ + if (temp == &extract_string_error || temp == &extract_string_fatal) + { + if (sindex - 1 == t_index) + { + sindex = t_index; + goto add_character; + } + set_exit_status (EXECUTION_FAILURE); + report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index); + free (string); + free (istring); + return ((temp == &extract_string_error) ? &expand_word_error + : &expand_word_fatal); + } + + if (expanded_something) + *expanded_something = 1; + local_expanded = 1; + + if (word->flags & W_NOCOMSUB) + /* sindex + 1 because string[sindex] == '`' */ + temp1 = substring (string, t_index, sindex + 1); + else + { + de_backslash (temp); + tword = command_substitute (temp, quoted, 0); + temp1 = tword ? tword->word : (char *)NULL; + if (tword) + dispose_word_desc (tword); + } + FREE (temp); + temp = temp1; + goto dollar_add_string; + } + + case '\\': + if (string[sindex + 1] == '\n') + { + sindex += 2; + continue; + } + + c = string[++sindex]; + + /* "However, the double-quote character ( '"' ) shall not be treated + specially within a here-document, except when the double-quote + appears within "$()", "``", or "${}"." */ + if ((quoted & Q_HERE_DOCUMENT) && (quoted & Q_DOLBRACE) && c == '"') + tflag = CBSDQUOTE; /* special case */ + else if (quoted & Q_HERE_DOCUMENT) + tflag = CBSHDOC; + else if (quoted & Q_DOUBLE_QUOTES) + tflag = CBSDQUOTE; + else + tflag = 0; + + /* From Posix discussion on austin-group list: Backslash escaping + a } in ${...} is removed. Issue 0000221 */ + if ((quoted & Q_DOLBRACE) && c == RBRACE) + { + SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size); + } + /* This is the fix for " $@\ " */ + else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0) && isexp == 0 && isifs (c)) + { + RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = CTLESC; + istring[istring_index++] = '\\'; + istring[istring_index] = '\0'; + + SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size); + } + else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && c == 0) + { + RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = CTLESC; + istring[istring_index++] = '\\'; + istring[istring_index] = '\0'; + break; + } + else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0)) + { + SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size); + } + else if (c == 0) + { + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + { + SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size); + } + + sindex++; +add_twochars: + /* BEFORE jumping here, we need to increment sindex if appropriate */ + RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = twochars[0]; + istring[istring_index++] = twochars[1]; + istring[istring_index] = '\0'; + + break; + + case '"': + /* XXX - revisit this */ + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) && ((quoted & Q_ARITH) == 0)) + goto add_character; + + t_index = ++sindex; + temp = string_extract_double_quoted (string, &sindex, (word->flags & W_COMPLETE) ? SX_COMPLETE : 0); + + /* If the quotes surrounded the entire string, then the + whole word was quoted. */ + quoted_state = (t_index == 1 && string[sindex] == '\0') + ? WHOLLY_QUOTED + : PARTIALLY_QUOTED; + + if (temp && *temp) + { + tword = alloc_word_desc (); + tword->word = temp; + + if (word->flags & W_ASSIGNARG) + tword->flags |= word->flags & (W_ASSIGNARG|W_ASSIGNRHS); /* affects $@ */ + if (word->flags & W_COMPLETE) + tword->flags |= W_COMPLETE; /* for command substitutions */ + if (word->flags & W_NOCOMSUB) + tword->flags |= W_NOCOMSUB; + if (word->flags & W_NOPROCSUB) + tword->flags |= W_NOPROCSUB; + + if (word->flags & W_ASSIGNRHS) + tword->flags |= W_ASSIGNRHS; + + temp = (char *)NULL; + + temp_has_dollar_at = 0; /* does this quoted (sub)string include $@? */ + /* Need to get W_HASQUOTEDNULL flag through this function. */ + /* XXX - preserve Q_ARITH here? */ + list = expand_word_internal (tword, Q_DOUBLE_QUOTES|(quoted&Q_ARITH), 0, &temp_has_dollar_at, (int *)NULL); + has_dollar_at += temp_has_dollar_at; + + if (list == &expand_word_error || list == &expand_word_fatal) + { + free (istring); + free (string); + /* expand_word_internal has already freed temp_word->word + for us because of the way it prints error messages. */ + tword->word = (char *)NULL; + dispose_word (tword); + return list; + } + + dispose_word (tword); + + /* "$@" (a double-quoted dollar-at) expands into nothing, + not even a NULL word, when there are no positional + parameters. Posix interp 888 says that other parts of the + word that expand to quoted nulls result in quoted nulls, so + we can't just throw the entire word away if we have "$@" + anywhere in it. We use had_quoted_null to keep track */ + if (list == 0 && temp_has_dollar_at) /* XXX - was has_dollar_at */ + { + quoted_dollar_at++; + break; + } + + /* If this list comes back with a quoted null from expansion, + we have either "$x" or "$@" with $1 == ''. In either case, + we need to make sure we add a quoted null argument and + disable the special handling that "$@" gets. */ + if (list && list->word && list->next == 0 && (list->word->flags & W_HASQUOTEDNULL)) + { + if (had_quoted_null && temp_has_dollar_at) + quoted_dollar_at++; + had_quoted_null = 1; /* XXX */ + } + + /* If we get "$@", we know we have expanded something, so we + need to remember it for the final split on $IFS. This is + a special case; it's the only case where a quoted string + can expand into more than one word. It's going to come back + from the above call to expand_word_internal as a list with + multiple words. */ + if (list) + dequote_list (list); + + if (temp_has_dollar_at) /* XXX - was has_dollar_at */ + { + quoted_dollar_at++; + if (contains_dollar_at) + *contains_dollar_at = 1; + if (expanded_something) + *expanded_something = 1; + local_expanded = 1; + } + } + else + { + /* What we have is "". This is a minor optimization. */ + FREE (temp); + list = (WORD_LIST *)NULL; + had_quoted_null = 1; /* note for later */ + } + + /* The code above *might* return a list (consider the case of "$@", + where it returns "$1", "$2", etc.). We can't throw away the + rest of the list, and we have to make sure each word gets added + as quoted. We test on tresult->next: if it is non-NULL, we + quote the whole list, save it to a string with string_list, and + add that string. We don't need to quote the results of this + (and it would be wrong, since that would quote the separators + as well), so we go directly to add_string. */ + if (list) + { + if (list->next) + { + /* Testing quoted_dollar_at makes sure that "$@" is + split correctly when $IFS does not contain a space. */ + temp = quoted_dollar_at + ? string_list_dollar_at (list, Q_DOUBLE_QUOTES, 0) + : string_list (quote_list (list)); + dispose_words (list); + goto add_string; + } + else + { + temp = savestring (list->word->word); + tflag = list->word->flags; + dispose_words (list); + + /* If the string is not a quoted null string, we want + to remove any embedded unquoted CTLNUL characters. + We do not want to turn quoted null strings back into + the empty string, though. We do this because we + want to remove any quoted nulls from expansions that + contain other characters. For example, if we have + x"$*"y or "x$*y" and there are no positional parameters, + the $* should expand into nothing. */ + /* We use the W_HASQUOTEDNULL flag to differentiate the + cases: a quoted null character as above and when + CTLNUL is contained in the (non-null) expansion + of some variable. We use the had_quoted_null flag to + pass the value through this function to its caller. */ + if ((tflag & W_HASQUOTEDNULL) && QUOTED_NULL (temp) == 0) + remove_quoted_nulls (temp); /* XXX */ + } + } + else + temp = (char *)NULL; + + if (temp == 0 && quoted_state == PARTIALLY_QUOTED) + had_quoted_null = 1; /* note for later */ + + /* We do not want to add quoted nulls to strings that are only + partially quoted; we can throw them away. The exception to + this is when we are going to be performing word splitting, + since we have to preserve a null argument if the next character + will cause word splitting. */ + if (temp == 0 && quoted_state == PARTIALLY_QUOTED && quoted == 0 && (word->flags & (W_NOSPLIT|W_EXPANDRHS|W_ASSIGNRHS)) == W_EXPANDRHS) + { + c = CTLNUL; + sindex--; + had_quoted_null = 1; + goto add_character; + } + if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2))) + continue; + + add_quoted_string: + + if (temp) + { + temp1 = temp; + temp = quote_string (temp); + free (temp1); + goto add_string; + } + else + { + /* Add NULL arg. */ + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + had_quoted_null = 1; /* note for later */ + goto add_character; + } + + /* break; */ + + case '\'': + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + goto add_character; + + t_index = ++sindex; + temp = string_extract_single_quoted (string, &sindex, 0); + + /* If the entire STRING was surrounded by single quotes, + then the string is wholly quoted. */ + quoted_state = (t_index == 1 && string[sindex] == '\0') + ? WHOLLY_QUOTED + : PARTIALLY_QUOTED; + + /* If all we had was '', it is a null expansion. */ + if (*temp == '\0') + { + free (temp); + temp = (char *)NULL; + } + else + remove_quoted_escapes (temp); /* ??? */ + + if (temp == 0 && quoted_state == PARTIALLY_QUOTED) + had_quoted_null = 1; /* note for later */ + + /* We do not want to add quoted nulls to strings that are only + partially quoted; such nulls are discarded. See above for the + exception, which is when the string is going to be split. + Posix interp 888/1129 */ + if (temp == 0 && quoted_state == PARTIALLY_QUOTED && quoted == 0 && (word->flags & (W_NOSPLIT|W_EXPANDRHS|W_ASSIGNRHS)) == W_EXPANDRHS) + { + c = CTLNUL; + sindex--; + goto add_character; + } + + if (temp == 0 && (quoted_state == PARTIALLY_QUOTED) && (word->flags & (W_NOSPLIT|W_NOSPLIT2))) + continue; + + /* If we have a quoted null expansion, add a quoted NULL to istring. */ + if (temp == 0) + { + c = CTLNUL; + sindex--; /* add_character: label increments sindex */ + goto add_character; + } + else + goto add_quoted_string; + + /* break; */ + + case ' ': + /* If we are in a context where the word is not going to be split, but + we need to account for $@ and $* producing one word for each + positional parameter, add quoted spaces so the spaces in the + expansion of "$@", if any, behave correctly. We still may need to + split if we are expanding the rhs of a word expansion. */ + if (ifs_is_null || split_on_spaces || ((word->flags & (W_NOSPLIT|W_NOSPLIT2|W_ASSIGNRHS)) && (word->flags & W_EXPANDRHS) == 0)) + { + if (string[sindex]) + sindex++; + twochars[0] = CTLESC; + twochars[1] = c; + goto add_twochars; + } + /* FALLTHROUGH */ + + default: + /* This is the fix for " $@ " */ +add_ifs_character: + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c) && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0)) + { + if ((quoted&(Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0) + has_quoted_ifs++; +add_quoted_character: + if (string[sindex]) /* from old goto dollar_add_string */ + sindex++; + if (c == 0) + { + c = CTLNUL; + goto add_character; + } + else + { +#if HANDLE_MULTIBYTE + /* XXX - should make sure that c is actually multibyte, + otherwise we can use the twochars branch */ + if (mb_cur_max > 1) + sindex--; + + if (mb_cur_max > 1) + { + SADD_MBQCHAR_BODY(temp, string, sindex, string_size); + } + else +#endif + { + twochars[0] = CTLESC; + twochars[1] = c; + goto add_twochars; + } + } + } + + SADD_MBCHAR (temp, string, sindex, string_size); + +add_character: + RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size, + DEFAULT_ARRAY_SIZE); + istring[istring_index++] = c; + istring[istring_index] = '\0'; + + /* Next character. */ + sindex++; + } + } + +finished_with_string: + /* OK, we're ready to return. If we have a quoted string, and + quoted_dollar_at is not set, we do no splitting at all; otherwise + we split on ' '. The routines that call this will handle what to + do if nothing has been expanded. */ + + /* Partially and wholly quoted strings which expand to the empty + string are retained as an empty arguments. Unquoted strings + which expand to the empty string are discarded. The single + exception is the case of expanding "$@" when there are no + positional parameters. In that case, we discard the expansion. */ + + /* Because of how the code that handles "" and '' in partially + quoted strings works, we need to make ISTRING into a QUOTED_NULL + if we saw quoting characters, but the expansion was empty. + "" and '' are tossed away before we get to this point when + processing partially quoted strings. This makes "" and $xxx"" + equivalent when xxx is unset. We also look to see whether we + saw a quoted null from a ${} expansion and add one back if we + need to. */ + + /* If we expand to nothing and there were no single or double quotes + in the word, we throw it away. Otherwise, we return a NULL word. + The single exception is for $@ surrounded by double quotes when + there are no positional parameters. In that case, we also throw + the word away. */ + + if (*istring == '\0') + { +#if 0 + if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED)) +#else + if (had_quoted_null || (quoted_dollar_at == 0 && quoted_state == PARTIALLY_QUOTED)) +#endif + { + istring[0] = CTLNUL; + istring[1] = '\0'; + tword = alloc_word_desc (); + tword->word = istring; + istring = 0; /* avoid later free() */ + tword->flags |= W_HASQUOTEDNULL; /* XXX */ + list = make_word_list (tword, (WORD_LIST *)NULL); + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + } + /* According to sh, ksh, and Posix.2, if a word expands into nothing + and a double-quoted "$@" appears anywhere in it, then the entire + word is removed. */ + /* XXX - exception appears to be that quoted null strings result in + null arguments */ + else if (quoted_state == UNQUOTED || quoted_dollar_at) + list = (WORD_LIST *)NULL; + else + list = (WORD_LIST *)NULL; + } + else if (word->flags & W_NOSPLIT) + { + tword = alloc_word_desc (); + tword->word = istring; + if (had_quoted_null && QUOTED_NULL (istring)) + tword->flags |= W_HASQUOTEDNULL; + istring = 0; /* avoid later free() */ + if (word->flags & W_ASSIGNMENT) + tword->flags |= W_ASSIGNMENT; /* XXX */ + if (word->flags & W_COMPASSIGN) + tword->flags |= W_COMPASSIGN; /* XXX */ + if (word->flags & W_NOGLOB) + tword->flags |= W_NOGLOB; /* XXX */ + if (word->flags & W_NOBRACE) + tword->flags |= W_NOBRACE; /* XXX */ + if (word->flags & W_ARRAYREF) + tword->flags |= W_ARRAYREF; + if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) + tword->flags |= W_QUOTED; + list = make_word_list (tword, (WORD_LIST *)NULL); + } + else if (word->flags & W_ASSIGNRHS) + { + list = list_string (istring, "", quoted); + tword = list->word; + if (had_quoted_null && QUOTED_NULL (istring)) + tword->flags |= W_HASQUOTEDNULL; + free (list); + free (istring); + istring = 0; /* avoid later free() */ + goto set_word_flags; + } + else + { + char *ifs_chars; + + ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL; + + /* If we have $@, we need to split the results no matter what. If + IFS is unset or NULL, string_list_dollar_at has separated the + positional parameters with a space, so we split on space (we have + set ifs_chars to " \t\n" above if ifs is unset). If IFS is set, + string_list_dollar_at has separated the positional parameters + with the first character of $IFS, so we split on $IFS. If + SPLIT_ON_SPACES is set, we expanded $* (unquoted) with IFS either + unset or null, and we want to make sure that we split on spaces + regardless of what else has happened to IFS since the expansion, + or we expanded "$@" with IFS null and we need to split the positional + parameters into separate words. */ + if (split_on_spaces) + { + /* If IFS is not set, and the word is not quoted, we want to split + the individual words on $' \t\n'. We rely on previous steps to + quote the portions of the word that should not be split */ + if (ifs_is_set == 0) + list = list_string (istring, " \t\n", 1); /* XXX quoted == 1? */ + else + list = list_string (istring, " ", 1); /* XXX quoted == 1? */ + } + + /* If we have $@ (has_dollar_at != 0) and we are in a context where we + don't want to split the result (W_NOSPLIT2), and we are not quoted, + we have already separated the arguments with the first character of + $IFS. In this case, we want to return a list with a single word + with the separator possibly replaced with a space (it's what other + shells seem to do). + quoted_dollar_at is internal to this function and is set if we are + passed an argument that is unquoted (quoted == 0) but we encounter a + double-quoted $@ while expanding it. */ + else if (has_dollar_at && quoted_dollar_at == 0 && ifs_chars && quoted == 0 && (word->flags & W_NOSPLIT2)) + { + tword = alloc_word_desc (); + /* Only split and rejoin if we have to */ + if (*ifs_chars && *ifs_chars != ' ') + { + /* list_string dequotes CTLESCs in the string it's passed, so we + need it to get the space separation right if space isn't the + first character in IFS (but is present) and to remove the + quoting we added back in param_expand(). */ + list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1); + /* This isn't exactly right in the case where we're expanding + the RHS of an expansion like ${var-$@} where IFS=: (for + example). The W_NOSPLIT2 means we do the separation with :; + the list_string removes the quotes and breaks the string into + a list, and the string_list rejoins it on spaces. When we + return, we expect to be able to split the results, but the + space separation means the right split doesn't happen. */ + tword->word = string_list (list); + } + else + tword->word = istring; + if (had_quoted_null && QUOTED_NULL (istring)) + tword->flags |= W_HASQUOTEDNULL; /* XXX */ + if (tword->word != istring) + free (istring); + istring = 0; /* avoid later free() */ + goto set_word_flags; + } + else if (has_dollar_at && ifs_chars) + list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1); + else + { + tword = alloc_word_desc (); + if (expanded_something && *expanded_something == 0 && has_quoted_ifs) + tword->word = remove_quoted_ifs (istring); + else + tword->word = istring; + if (had_quoted_null && QUOTED_NULL (istring)) /* should check for more than one */ + tword->flags |= W_HASQUOTEDNULL; /* XXX */ + else if (had_quoted_null) + tword->flags |= W_SAWQUOTEDNULL; /* XXX */ + if (tword->word != istring) + free (istring); + istring = 0; /* avoid later free() */ +set_word_flags: + if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED)) + tword->flags |= W_QUOTED; + if (word->flags & W_ASSIGNMENT) + tword->flags |= W_ASSIGNMENT; + if (word->flags & W_COMPASSIGN) + tword->flags |= W_COMPASSIGN; + if (word->flags & W_NOGLOB) + tword->flags |= W_NOGLOB; + if (word->flags & W_NOBRACE) + tword->flags |= W_NOBRACE; + if (word->flags & W_ARRAYREF) + tword->flags |= W_ARRAYREF; + list = make_word_list (tword, (WORD_LIST *)NULL); + } + } + + free (istring); + return (list); +} + +/* **************************************************************** */ +/* */ +/* Functions for Quote Removal */ +/* */ +/* **************************************************************** */ + +/* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the + backslash quoting rules for within double quotes or a here document. */ +char * +string_quote_removal (string, quoted) + char *string; + int quoted; +{ + size_t slen; + char *r, *result_string, *temp, *send; + int sindex, tindex, dquote; + unsigned char c; + DECLARE_MBSTATE; + + /* The result can be no longer than the original string. */ + slen = strlen (string); + send = string + slen; + + r = result_string = (char *)xmalloc (slen + 1); + + for (dquote = sindex = 0; c = string[sindex];) + { + switch (c) + { + case '\\': + c = string[++sindex]; + if (c == 0) + { + *r++ = '\\'; + break; + } + if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0) + *r++ = '\\'; + /* FALLTHROUGH */ + + default: + SCOPY_CHAR_M (r, string, send, sindex); + break; + + case '\'': + if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) + { + *r++ = c; + sindex++; + break; + } + tindex = sindex + 1; + temp = string_extract_single_quoted (string, &tindex, 0); + if (temp) + { + strcpy (r, temp); + r += strlen (r); + free (temp); + } + sindex = tindex; + break; + + case '"': + dquote = 1 - dquote; + sindex++; + break; + } + } + *r = '\0'; + return (result_string); +} + +#if 0 +/* UNUSED */ +/* Perform quote removal on word WORD. This allocates and returns a new + WORD_DESC *. */ +WORD_DESC * +word_quote_removal (word, quoted) + WORD_DESC *word; + int quoted; +{ + WORD_DESC *w; + char *t; + + t = string_quote_removal (word->word, quoted); + w = alloc_word_desc (); + w->word = t ? t : savestring (""); + return (w); +} + +/* Perform quote removal on all words in LIST. If QUOTED is non-zero, + the members of the list are treated as if they are surrounded by + double quotes. Return a new list, or NULL if LIST is NULL. */ +WORD_LIST * +word_list_quote_removal (list, quoted) + WORD_LIST *list; + int quoted; +{ + WORD_LIST *result, *t, *tresult, *e; + + for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) + { + tresult = make_word_list (word_quote_removal (t->word, quoted), (WORD_LIST *)NULL); +#if 0 + result = (WORD_LIST *) list_append (result, tresult); +#else + if (result == 0) + result = e = tresult; + else + { + e->next = tresult; + while (e->next) + e = e->next; + } +#endif + } + return (result); +} +#endif + +/******************************************* + * * + * Functions to perform word splitting * + * * + *******************************************/ + +void +setifs (v) + SHELL_VAR *v; +{ + char *t; + unsigned char uc; + + ifs_var = v; + ifs_value = (v && value_cell (v)) ? value_cell (v) : " \t\n"; + + ifs_is_set = ifs_var != 0; + ifs_is_null = ifs_is_set && (*ifs_value == 0); + + /* Should really merge ifs_cmap with sh_syntaxtab. XXX - doesn't yet + handle multibyte chars in IFS */ + memset (ifs_cmap, '\0', sizeof (ifs_cmap)); + for (t = ifs_value ; t && *t; t++) + { + uc = *t; + ifs_cmap[uc] = 1; + } + +#if defined (HANDLE_MULTIBYTE) + if (ifs_value == 0) + { + ifs_firstc[0] = '\0'; /* XXX - ? */ + ifs_firstc_len = 1; + } + else + { + if (locale_utf8locale && UTF8_SINGLEBYTE (*ifs_value)) + ifs_firstc_len = (*ifs_value != 0) ? 1 : 0; + else + { + size_t ifs_len; + ifs_len = strnlen (ifs_value, MB_CUR_MAX); + ifs_firstc_len = MBLEN (ifs_value, ifs_len); + } + if (ifs_firstc_len == 1 || ifs_firstc_len == 0 || MB_INVALIDCH (ifs_firstc_len)) + { + ifs_firstc[0] = ifs_value[0]; + ifs_firstc[1] = '\0'; + ifs_firstc_len = 1; + } + else + memcpy (ifs_firstc, ifs_value, ifs_firstc_len); + } +#else + ifs_firstc = ifs_value ? *ifs_value : 0; +#endif +} + +char * +getifs () +{ + return ifs_value; +} + +/* This splits a single word into a WORD LIST on $IFS, but only if the word + is not quoted. list_string () performs quote removal for us, even if we + don't do any splitting. */ +WORD_LIST * +word_split (w, ifs_chars) + WORD_DESC *w; + char *ifs_chars; +{ + WORD_LIST *result; + + if (w) + { + char *xifs; + + xifs = ((w->flags & W_QUOTED) || ifs_chars == 0) ? "" : ifs_chars; + result = list_string (w->word, xifs, w->flags & W_QUOTED); + } + else + result = (WORD_LIST *)NULL; + + return (result); +} + +/* Perform word splitting on LIST and return the RESULT. It is possible + to return (WORD_LIST *)NULL. */ +static WORD_LIST * +word_list_split (list) + WORD_LIST *list; +{ + WORD_LIST *result, *t, *tresult, *e; + WORD_DESC *w; + + for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) + { + tresult = word_split (t->word, ifs_value); + /* POSIX 2.6: "If the complete expansion appropriate for a word results + in an empty field, that empty field shall be deleted from the list + of fields that form the completely expanded command, unless the + original word contained single-quote or double-quote characters." + This is where we handle these words that contain quoted null strings + and other characters that expand to nothing after word splitting. */ + if (tresult == 0 && t->word && (t->word->flags & W_SAWQUOTEDNULL)) /* XXX */ + { + w = alloc_word_desc (); + w->word = (char *)xmalloc (1); + w->word[0] = '\0'; + tresult = make_word_list (w, (WORD_LIST *)NULL); + } +#if defined (ARRAY_VARS) + /* pass W_ARRAYREF through for words that are not split and are + identical to the original word. */ + if (tresult && tresult->next == 0 && t->next == 0 && (t->word->flags & W_ARRAYREF) && STREQ (t->word->word, tresult->word->word)) + tresult->word->flags |= W_ARRAYREF; +#endif + if (result == 0) + result = e = tresult; + else + { + e->next = tresult; + while (e->next) + e = e->next; + } + } + return (result); +} + +/************************************************** + * * + * Functions to expand an entire WORD_LIST * + * * + **************************************************/ + +/* Do any word-expansion-specific cleanup and jump to top_level */ +static void +exp_jump_to_top_level (v) + int v; +{ + set_pipestatus_from_exit (last_command_exit_value); + + /* Cleanup code goes here. */ + expand_no_split_dollar_star = 0; /* XXX */ + if (expanding_redir) + undo_partial_redirects (); + expanding_redir = 0; + assigning_in_environment = 0; + + if (parse_and_execute_level == 0) + top_level_cleanup (); /* from sig.c */ + + jump_to_top_level (v); +} + +/* Put NLIST (which is a WORD_LIST * of only one element) at the front of + ELIST, and set ELIST to the new list. */ +#define PREPEND_LIST(nlist, elist) \ + do { nlist->next = elist; elist = nlist; } while (0) + +/* Separate out any initial variable assignments from TLIST. If set -k has + been executed, remove all assignment statements from TLIST. Initial + variable assignments and other environment assignments are placed + on SUBST_ASSIGN_VARLIST. */ +static WORD_LIST * +separate_out_assignments (tlist) + WORD_LIST *tlist; +{ + register WORD_LIST *vp, *lp; + + if (tlist == 0) + return ((WORD_LIST *)NULL); + + if (subst_assign_varlist) + dispose_words (subst_assign_varlist); /* Clean up after previous error */ + + subst_assign_varlist = (WORD_LIST *)NULL; + vp = lp = tlist; + + /* Separate out variable assignments at the start of the command. + Loop invariant: vp->next == lp + Loop postcondition: + lp = list of words left after assignment statements skipped + tlist = original list of words + */ + while (lp && (lp->word->flags & W_ASSIGNMENT)) + { + vp = lp; + lp = lp->next; + } + + /* If lp != tlist, we have some initial assignment statements. + We make SUBST_ASSIGN_VARLIST point to the list of assignment + words and TLIST point to the remaining words. */ + if (lp != tlist) + { + subst_assign_varlist = tlist; + /* ASSERT(vp->next == lp); */ + vp->next = (WORD_LIST *)NULL; /* terminate variable list */ + tlist = lp; /* remainder of word list */ + } + + /* vp == end of variable list */ + /* tlist == remainder of original word list without variable assignments */ + if (!tlist) + /* All the words in tlist were assignment statements */ + return ((WORD_LIST *)NULL); + + /* ASSERT(tlist != NULL); */ + /* ASSERT((tlist->word->flags & W_ASSIGNMENT) == 0); */ + + /* If the -k option is in effect, we need to go through the remaining + words, separate out the assignment words, and place them on + SUBST_ASSIGN_VARLIST. */ + if (place_keywords_in_env) + { + WORD_LIST *tp; /* tp == running pointer into tlist */ + + tp = tlist; + lp = tlist->next; + + /* Loop Invariant: tp->next == lp */ + /* Loop postcondition: tlist == word list without assignment statements */ + while (lp) + { + if (lp->word->flags & W_ASSIGNMENT) + { + /* Found an assignment statement, add this word to end of + subst_assign_varlist (vp). */ + if (!subst_assign_varlist) + subst_assign_varlist = vp = lp; + else + { + vp->next = lp; + vp = lp; + } + + /* Remove the word pointed to by LP from TLIST. */ + tp->next = lp->next; + /* ASSERT(vp == lp); */ + lp->next = (WORD_LIST *)NULL; + lp = tp->next; + } + else + { + tp = lp; + lp = lp->next; + } + } + } + return (tlist); +} + +#define WEXP_VARASSIGN 0x001 +#define WEXP_BRACEEXP 0x002 +#define WEXP_TILDEEXP 0x004 +#define WEXP_PARAMEXP 0x008 +#define WEXP_PATHEXP 0x010 + +/* All of the expansions, including variable assignments at the start of + the list. */ +#define WEXP_ALL (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) + +/* All of the expansions except variable assignments at the start of + the list. */ +#define WEXP_NOVARS (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) + +/* All of the `shell expansions': brace expansion, tilde expansion, parameter + expansion, command substitution, arithmetic expansion, word splitting, and + quote removal. */ +#define WEXP_SHELLEXP (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP) + +/* Take the list of words in LIST and do the various substitutions. Return + a new list of words which is the expanded list, and without things like + variable assignments. */ + +WORD_LIST * +expand_words (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_ALL)); +} + +/* Same as expand_words (), but doesn't hack variable or environment + variables. */ +WORD_LIST * +expand_words_no_vars (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_NOVARS)); +} + +WORD_LIST * +expand_words_shellexp (list) + WORD_LIST *list; +{ + return (expand_word_list_internal (list, WEXP_SHELLEXP)); +} + +static WORD_LIST * +glob_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + char **glob_array, *temp_string; + register int glob_index; + WORD_LIST *glob_list, *output_list, *disposables, *next; + WORD_DESC *tword; + int x; + + output_list = disposables = (WORD_LIST *)NULL; + glob_array = (char **)NULL; + while (tlist) + { + /* For each word, either globbing is attempted or the word is + added to orig_list. If globbing succeeds, the results are + added to orig_list and the word (tlist) is added to the list + of disposable words. If globbing fails and failed glob + expansions are left unchanged (the shell default), the + original word is added to orig_list. If globbing fails and + failed glob expansions are removed, the original word is + added to the list of disposable words. orig_list ends up + in reverse order and requires a call to REVERSE_LIST to + be set right. After all words are examined, the disposable + words are freed. */ + next = tlist->next; + + /* If the word isn't an assignment and contains an unquoted + pattern matching character, then glob it. */ + if ((tlist->word->flags & W_NOGLOB) == 0 && + unquoted_glob_pattern_p (tlist->word->word)) + { + glob_array = shell_glob_filename (tlist->word->word, QGLOB_CTLESC); /* XXX */ + + /* Handle error cases. + I don't think we should report errors like "No such file + or directory". However, I would like to report errors + like "Read failed". */ + + if (glob_array == 0 || GLOB_FAILED (glob_array)) + { + glob_array = (char **)xmalloc (sizeof (char *)); + glob_array[0] = (char *)NULL; + } + + /* Dequote the current word in case we have to use it. */ + if (glob_array[0] == NULL) + { + temp_string = dequote_string (tlist->word->word); + free (tlist->word->word); + tlist->word->word = temp_string; + } + + /* Make the array into a word list. */ + glob_list = (WORD_LIST *)NULL; + for (glob_index = 0; glob_array[glob_index]; glob_index++) + { + tword = make_bare_word (glob_array[glob_index]); + glob_list = make_word_list (tword, glob_list); + } + + if (glob_list) + { + output_list = (WORD_LIST *)list_append (glob_list, output_list); + PREPEND_LIST (tlist, disposables); + } + else if (fail_glob_expansion != 0) + { + last_command_exit_value = EXECUTION_FAILURE; + report_error (_("no match: %s"), tlist->word->word); + exp_jump_to_top_level (DISCARD); + } + else if (allow_null_glob_expansion == 0) + { + /* Failed glob expressions are left unchanged. */ + PREPEND_LIST (tlist, output_list); + } + else + { + /* Failed glob expressions are removed. */ + PREPEND_LIST (tlist, disposables); + } + } + else + { + /* Dequote the string. */ + temp_string = dequote_string (tlist->word->word); + free (tlist->word->word); + tlist->word->word = temp_string; + PREPEND_LIST (tlist, output_list); + } + + strvec_dispose (glob_array); + glob_array = (char **)NULL; + + tlist = next; + } + + if (disposables) + dispose_words (disposables); + + if (output_list) + output_list = REVERSE_LIST (output_list, WORD_LIST *); + + return (output_list); +} + +#if defined (BRACE_EXPANSION) +static WORD_LIST * +brace_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + register char **expansions; + char *temp_string; + WORD_LIST *disposables, *output_list, *next; + WORD_DESC *w; + int eindex; + + for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next) + { + next = tlist->next; + + if (tlist->word->flags & W_NOBRACE) + { +/*itrace("brace_expand_word_list: %s: W_NOBRACE", tlist->word->word);*/ + PREPEND_LIST (tlist, output_list); + continue; + } + + if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG)) + { +/*itrace("brace_expand_word_list: %s: W_COMPASSIGN|W_ASSIGNARG", tlist->word->word);*/ + PREPEND_LIST (tlist, output_list); + continue; + } + + /* Only do brace expansion if the word has a brace character. If + not, just add the word list element to BRACES and continue. In + the common case, at least when running shell scripts, this will + degenerate to a bunch of calls to `mbschr', and then what is + basically a reversal of TLIST into BRACES, which is corrected + by a call to REVERSE_LIST () on BRACES when the end of TLIST + is reached. */ + if (mbschr (tlist->word->word, LBRACE)) + { + expansions = brace_expand (tlist->word->word); + + for (eindex = 0; temp_string = expansions[eindex]; eindex++) + { + w = alloc_word_desc (); + w->word = temp_string; + + /* If brace expansion didn't change the word, preserve + the flags. We may want to preserve the flags + unconditionally someday -- XXX */ + if (STREQ (temp_string, tlist->word->word)) + w->flags = tlist->word->flags; + else + w = make_word_flags (w, temp_string); + + output_list = make_word_list (w, output_list); + } + free (expansions); + + /* Add TLIST to the list of words to be freed after brace + expansion has been performed. */ + PREPEND_LIST (tlist, disposables); + } + else + PREPEND_LIST (tlist, output_list); + } + + if (disposables) + dispose_words (disposables); + + if (output_list) + output_list = REVERSE_LIST (output_list, WORD_LIST *); + + return (output_list); +} +#endif + +#if defined (ARRAY_VARS) +/* Take WORD, a compound array assignment, and internally run (for example), + 'declare -A w', where W is the variable name portion of WORD. OPTION is + the list of options to supply to `declare'. CMD is the declaration command + we are expanding right now; it's unused currently. */ +static int +make_internal_declare (word, option, cmd) + char *word; + char *option; + char *cmd; +{ + int t, r; + WORD_LIST *wl; + WORD_DESC *w; + + w = make_word (word); + + t = assignment (w->word, 0); + if (w->word[t] == '=') + { + w->word[t] = '\0'; + if (w->word[t - 1] == '+') /* cut off any append op */ + w->word[t - 1] = '\0'; + } + + wl = make_word_list (w, (WORD_LIST *)NULL); + wl = make_word_list (make_word (option), wl); + + r = declare_builtin (wl); + + dispose_words (wl); + return r; +} + +/* Expand VALUE in NAME[+]=( VALUE ) to a list of words. FLAGS is 1 if NAME + is an associative array. + + If we are processing an indexed array, expand_compound_array_assignment + will expand all the individual words and quote_compound_array_list will + single-quote them. If we are processing an associative array, we use + parse_string_to_word_list to split VALUE into a list of words instead of + faking up a shell variable and calling expand_compound_array_assignment. + expand_and_quote_assoc_word expands and single-quotes each word in VALUE + together so we don't have problems finding the end of the subscript when + quoting it. + + Words in VALUE can be individual words, which are expanded and single-quoted, + or words of the form [IND]=VALUE, which end up as explained below, as + ['expanded-ind']='expanded-value'. */ + +static WORD_LIST * +expand_oneword (value, flags) + char *value; + int flags; +{ + WORD_LIST *l, *nl; + char *t; + int kvpair; + + if (flags == 0) + { + /* Indexed array */ + l = expand_compound_array_assignment ((SHELL_VAR *)NULL, value, flags); + /* Now we quote the results of the expansion above to prevent double + expansion. */ + quote_compound_array_list (l, flags); + return l; + } + else + { + /* Associative array */ + l = parse_string_to_word_list (value, 1, "array assign"); +#if ASSOC_KVPAIR_ASSIGNMENT + kvpair = kvpair_assignment_p (l); +#endif + + /* For associative arrays, with their arbitrary subscripts, we have to + expand and quote in one step so we don't have to search for the + closing right bracket more than once. */ + for (nl = l; nl; nl = nl->next) + { +#if ASSOC_KVPAIR_ASSIGNMENT + if (kvpair) + /* keys and values undergo the same set of expansions */ + t = expand_and_quote_kvpair_word (nl->word->word); + else +#endif + if ((nl->word->flags & W_ASSIGNMENT) == 0) + t = sh_single_quote (nl->word->word ? nl->word->word : ""); + else + t = expand_and_quote_assoc_word (nl->word->word, flags); + free (nl->word->word); + nl->word->word = t; + } + return l; + } +} + +/* Expand a single compound assignment argument to a declaration builtin. + This word takes the form NAME[+]=( VALUE ). The NAME[+]= is passed through + unchanged. The VALUE is expanded and each word in the result is single- + quoted. Words of the form [key]=value end up as + ['expanded-key']='expanded-value'. Associative arrays have special + handling, see expand_oneword() above. The return value is + NAME[+]=( expanded-and-quoted-VALUE ). */ +static void +expand_compound_assignment_word (tlist, flags) + WORD_LIST *tlist; + int flags; +{ + WORD_LIST *l; + int wlen, oind, t; + char *value, *temp; + +/*itrace("expand_compound_assignment_word: original word = -%s-", tlist->word->word);*/ + t = assignment (tlist->word->word, 0); + + /* value doesn't have the open and close parens */ + oind = 1; + value = extract_array_assignment_list (tlist->word->word + t + 1, &oind); + /* This performs one round of expansion on the index/key and value and + single-quotes each word in the result. */ + l = expand_oneword (value, flags); + free (value); + + value = string_list (l); + dispose_words (l); + + wlen = STRLEN (value); + + /* Now, let's rebuild the string */ + temp = xmalloc (t + 3 + wlen + 1); /* name[+]=(value) */ + memcpy (temp, tlist->word->word, ++t); + temp[t++] = '('; + if (value) + memcpy (temp + t, value, wlen); + t += wlen; + temp[t++] = ')'; + temp[t] = '\0'; +/*itrace("expand_compound_assignment_word: reconstructed word = -%s-", temp);*/ + + free (tlist->word->word); + tlist->word->word = temp; + + free (value); +} + +/* Expand and process an argument to a declaration command. We have already + set flags in TLIST->word->flags depending on the declaration command + (declare, local, etc.) and the options supplied to it (-a, -A, etc.). + TLIST->word->word is of the form NAME[+]=( VALUE ). + + This does several things, all using pieces of other functions to get the + evaluation sequence right. It's called for compound array assignments with + the W_ASSIGNMENT flag set (basically, valid identifier names on the lhs). + It parses out which flags need to be set for declare to create the variable + correctly, then calls declare internally (make_internal_declare) to make + sure the variable exists with the correct attributes. Before the variable + is created, it calls expand_compound_assignment_word to expand VALUE to a + list of words, appropriately quoted for further evaluation. This preserves + the semantics of word-expansion-before-calling-builtins. Finally, it calls + do_word_assignment to perform the expansion and assignment with the same + expansion semantics as a standalone assignment statement (no word splitting, + etc.) even though the word is single-quoted so all that needs to happen is + quote removal. */ +static WORD_LIST * +expand_declaration_argument (tlist, wcmd) + WORD_LIST *tlist, *wcmd; +{ + char opts[16], omap[128]; + int t, opti, oind, skip, inheriting; + WORD_LIST *l; + + inheriting = localvar_inherit; + opti = 0; + if (tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL|W_CHKLOCAL|W_ASSIGNARRAY)) + opts[opti++] = '-'; + + if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSNGLOBAL)) == (W_ASSIGNASSOC|W_ASSNGLOBAL)) + { + opts[opti++] = 'g'; + opts[opti++] = 'A'; + } + else if (tlist->word->flags & W_ASSIGNASSOC) + { + opts[opti++] = 'A'; + } + else if ((tlist->word->flags & (W_ASSIGNARRAY|W_ASSNGLOBAL)) == (W_ASSIGNARRAY|W_ASSNGLOBAL)) + { + opts[opti++] = 'g'; + opts[opti++] = 'a'; + } + else if (tlist->word->flags & W_ASSIGNARRAY) + { + opts[opti++] = 'a'; + } + else if (tlist->word->flags & W_ASSNGLOBAL) + opts[opti++] = 'g'; + + if (tlist->word->flags & W_CHKLOCAL) + opts[opti++] = 'G'; + + /* If we have special handling note the integer attribute and others + that transform the value upon assignment. What we do is take all + of the option arguments and scan through them looking for options + that cause such transformations, and add them to the `opts' array. */ + + memset (omap, '\0', sizeof (omap)); + for (l = wcmd->next; l != tlist; l = l->next) + { + int optchar; + + if (l->word->word[0] != '-' && l->word->word[0] != '+') + break; /* non-option argument */ + if (l->word->word[0] == '-' && l->word->word[1] == '-' && l->word->word[2] == 0) + break; /* -- signals end of options */ + optchar = l->word->word[0]; + for (oind = 1; l->word->word[oind]; oind++) + switch (l->word->word[oind]) + { + case 'I': + inheriting = 1; + case 'i': + case 'l': + case 'u': + case 'c': + omap[l->word->word[oind]] = 1; + if (opti == 0) + opts[opti++] = optchar; + break; + default: + break; + } + } + + for (oind = 0; oind < sizeof (omap); oind++) + if (omap[oind]) + opts[opti++] = oind; + + /* If there are no -a/-A options, but we have a compound assignment, + we have a choice: we can set opts[0]='-', opts[1]='a', since the + default is to create an indexed array, and call + make_internal_declare with that, or we can just skip the -a and let + declare_builtin deal with it. Once we're here, we're better set + up for the latter, since we don't want to deal with looking up + any existing variable here -- better to let declare_builtin do it. + We need the variable created, though, especially if it's local, so + we get the scoping right before we call do_word_assignment. + To ensure that make_local_declare gets called, we add `--' if there + aren't any options. */ + if ((tlist->word->flags & (W_ASSIGNASSOC|W_ASSIGNARRAY)) == 0) + { + if (opti == 0) + { + opts[opti++] = '-'; + opts[opti++] = '-'; + } + } + opts[opti] = '\0'; + + /* This isn't perfect, but it's a start. Improvements later. We expand + tlist->word->word and single-quote the results to avoid multiple + expansions by, say, do_assignment_internal(). We have to weigh the + cost of reconstructing the compound assignment string with its single + quoting and letting the declare builtin handle it. The single quotes + will prevent any unwanted additional expansion or word splitting. */ + expand_compound_assignment_word (tlist, (tlist->word->flags & W_ASSIGNASSOC) ? 1 : 0); + + skip = 0; + if (opti > 0) + { + t = make_internal_declare (tlist->word->word, opts, wcmd ? wcmd->word->word : (char *)0); + if (t != EXECUTION_SUCCESS) + { + last_command_exit_value = t; + if (tlist->word->flags & W_FORCELOCAL) /* non-fatal error */ + skip = 1; + else + exp_jump_to_top_level (DISCARD); + } + } + + if (skip == 0) + { + t = do_word_assignment (tlist->word, 0); + if (t == 0) + { + last_command_exit_value = EXECUTION_FAILURE; + exp_jump_to_top_level (DISCARD); + } + } + + /* Now transform the word as ksh93 appears to do and go on */ + t = assignment (tlist->word->word, 0); + tlist->word->word[t] = '\0'; + if (tlist->word->word[t - 1] == '+') + tlist->word->word[t - 1] = '\0'; /* cut off append op */ + tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC|W_ASSIGNARRAY); + + return (tlist); +} +#endif /* ARRAY_VARS */ + +static WORD_LIST * +shell_expand_word_list (tlist, eflags) + WORD_LIST *tlist; + int eflags; +{ + WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list, *wcmd; + int expanded_something, has_dollar_at; + + /* We do tilde expansion all the time. This is what 1003.2 says. */ + wcmd = new_list = (WORD_LIST *)NULL; + + for (orig_list = tlist; tlist; tlist = next) + { + if (wcmd == 0 && (tlist->word->flags & W_ASSNBLTIN)) + wcmd = tlist; + + next = tlist->next; + +#if defined (ARRAY_VARS) + /* If this is a compound array assignment to a builtin that accepts + such assignments (e.g., `declare'), take the assignment and perform + it separately, handling the semantics of declarations inside shell + functions. This avoids the double-evaluation of such arguments, + because `declare' does some evaluation of compound assignments on + its own. */ + if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG)) + expand_declaration_argument (tlist, wcmd); +#endif + + expanded_something = 0; + expanded = expand_word_internal + (tlist->word, 0, 0, &has_dollar_at, &expanded_something); + + if (expanded == &expand_word_error || expanded == &expand_word_fatal) + { + /* By convention, each time this error is returned, + tlist->word->word has already been freed. */ + tlist->word->word = (char *)NULL; + + /* Dispose our copy of the original list. */ + dispose_words (orig_list); + /* Dispose the new list we're building. */ + dispose_words (new_list); + + last_command_exit_value = EXECUTION_FAILURE; + if (expanded == &expand_word_error) + exp_jump_to_top_level (DISCARD); + else + exp_jump_to_top_level (FORCE_EOF); + } + + /* Don't split words marked W_NOSPLIT. */ + if (expanded_something && (tlist->word->flags & W_NOSPLIT) == 0) + { + temp_list = word_list_split (expanded); + dispose_words (expanded); + } + else + { + /* If no parameter expansion, command substitution, process + substitution, or arithmetic substitution took place, then + do not do word splitting. We still have to remove quoted + null characters from the result. */ + word_list_remove_quoted_nulls (expanded); + temp_list = expanded; + } + + expanded = REVERSE_LIST (temp_list, WORD_LIST *); + new_list = (WORD_LIST *)list_append (expanded, new_list); + } + + if (orig_list) + dispose_words (orig_list); + + if (new_list) + new_list = REVERSE_LIST (new_list, WORD_LIST *); + + return (new_list); +} + +/* Perform assignment statements optionally preceding a command name COMMAND. + If COMMAND == NULL, is_nullcmd usually == 1. Follow the POSIX rules for + variable assignment errors. */ +static int +do_assignment_statements (varlist, command, is_nullcmd) + WORD_LIST *varlist; + char *command; + int is_nullcmd; +{ + WORD_LIST *temp_list; + char *savecmd; + sh_wassign_func_t *assign_func; + int is_special_builtin, is_builtin_or_func, tint; + + /* If the remainder of the words expand to nothing, Posix.2 requires + that the variable and environment assignments affect the shell's + environment (do_word_assignment). */ + assign_func = is_nullcmd ? do_word_assignment : assign_in_env; + tempenv_assign_error = 0; + + is_builtin_or_func = command && (find_shell_builtin (command) || find_function (command)); + /* Posix says that special builtins exit if a variable assignment error + occurs in an assignment preceding it. (XXX - this is old -- current Posix + says that any variable assignment error causes a non-interactive shell + to exit. See the STRICT_POSIX checks below. */ + is_special_builtin = posixly_correct && command && find_special_builtin (command); + + savecmd = this_command_name; + for (temp_list = varlist; temp_list; temp_list = temp_list->next) + { + this_command_name = (char *)NULL; + assigning_in_environment = is_nullcmd == 0; + tint = (*assign_func) (temp_list->word, is_builtin_or_func); + assigning_in_environment = 0; + this_command_name = savecmd; + + /* Variable assignment errors in non-interactive shells running + in posix mode cause the shell to exit. */ + if (tint == 0) + { + if (is_nullcmd) /* assignment statement */ + { + last_command_exit_value = EXECUTION_FAILURE; +#if defined (STRICT_POSIX) + if (posixly_correct && interactive_shell == 0) +#else + if (posixly_correct && interactive_shell == 0 && executing_command_builtin == 0) +#endif + exp_jump_to_top_level (FORCE_EOF); + else + exp_jump_to_top_level (DISCARD); + } + /* In posix mode, assignment errors in the temporary environment + cause a non-interactive shell executing a special builtin to + exit and a non-interactive shell to otherwise jump back to the + top level. This is what POSIX says to do for variable assignment + errors, and POSIX says errors in assigning to the temporary + environment are treated as variable assignment errors. + (XXX - this is not what current POSIX says - look at the + STRICT_POSIX defines. */ + else if (posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; +#if defined (STRICT_POSIX) + exp_jump_to_top_level ((interactive_shell == 0) ? FORCE_EOF : DISCARD); +#else + if (interactive_shell == 0 && is_special_builtin) + exp_jump_to_top_level (FORCE_EOF); + else if (interactive_shell == 0) + exp_jump_to_top_level (DISCARD); /* XXX - maybe change later */ + else + exp_jump_to_top_level (DISCARD); +#endif + } + else + tempenv_assign_error++; + } + } + return (tempenv_assign_error); +} + +/* The workhorse for expand_words () and expand_words_no_vars (). + First arg is LIST, a WORD_LIST of words. + Second arg EFLAGS is a flags word controlling which expansions are + performed. + + This does all of the substitutions: brace expansion, tilde expansion, + parameter expansion, command substitution, arithmetic expansion, + process substitution, word splitting, and pathname expansion, according + to the bits set in EFLAGS. Words with the W_QUOTED or W_NOSPLIT bits + set, or for which no expansion is done, do not undergo word splitting. + Words with the W_NOGLOB bit set do not undergo pathname expansion; words + with W_NOBRACE set do not undergo brace expansion (see + brace_expand_word_list above). */ +static WORD_LIST * +expand_word_list_internal (list, eflags) + WORD_LIST *list; + int eflags; +{ + WORD_LIST *new_list, *temp_list; + + tempenv_assign_error = 0; + if (list == 0) + return ((WORD_LIST *)NULL); + + garglist = new_list = copy_word_list (list); + if (eflags & WEXP_VARASSIGN) + { + garglist = new_list = separate_out_assignments (new_list); + if (new_list == 0) + { + if (subst_assign_varlist) + do_assignment_statements (subst_assign_varlist, (char *)NULL, 1); + + dispose_words (subst_assign_varlist); + subst_assign_varlist = (WORD_LIST *)NULL; + + return ((WORD_LIST *)NULL); + } + } + + /* Begin expanding the words that remain. The expansions take place on + things that aren't really variable assignments. */ + +#if defined (BRACE_EXPANSION) + /* Do brace expansion on this word if there are any brace characters + in the string. */ + if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list) + new_list = brace_expand_word_list (new_list, eflags); +#endif /* BRACE_EXPANSION */ + + /* Perform the `normal' shell expansions: tilde expansion, parameter and + variable substitution, command substitution, arithmetic expansion, + and word splitting. */ + new_list = shell_expand_word_list (new_list, eflags); + + /* Okay, we're almost done. Now let's just do some filename + globbing. */ + if (new_list) + { + if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0) + /* Glob expand the word list unless globbing has been disabled. */ + new_list = glob_expand_word_list (new_list, eflags); + else + /* Dequote the words, because we're not performing globbing. */ + new_list = dequote_list (new_list); + } + + if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist) + { + do_assignment_statements (subst_assign_varlist, (new_list && new_list->word) ? new_list->word->word : (char *)NULL, new_list == 0); + + dispose_words (subst_assign_varlist); + subst_assign_varlist = (WORD_LIST *)NULL; + } + + return (new_list); +} diff --git a/third_party/bash/subst.h b/third_party/bash/subst.h new file mode 100644 index 000000000..28cc92031 --- /dev/null +++ b/third_party/bash/subst.h @@ -0,0 +1,362 @@ +/* subst.h -- Names of externally visible functions in subst.c. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_SUBST_H_) +#define _SUBST_H_ + +#include "stdc.h" + +/* Constants which specify how to handle backslashes and quoting in + expand_word_internal (). Q_DOUBLE_QUOTES means to use the function + slashify_in_quotes () to decide whether the backslash should be + retained. Q_HERE_DOCUMENT means slashify_in_here_document () to + decide whether to retain the backslash. Q_KEEP_BACKSLASH means + to unconditionally retain the backslash. Q_PATQUOTE means that we're + expanding a pattern ${var%#[#%]pattern} in an expansion surrounded + by double quotes. Q_DOLBRACE means we are expanding a ${...} word, so + backslashes should also escape { and } and be removed. */ +#define Q_DOUBLE_QUOTES 0x001 +#define Q_HERE_DOCUMENT 0x002 +#define Q_KEEP_BACKSLASH 0x004 +#define Q_PATQUOTE 0x008 +#define Q_QUOTED 0x010 +#define Q_ADDEDQUOTES 0x020 +#define Q_QUOTEDNULL 0x040 +#define Q_DOLBRACE 0x080 +#define Q_ARITH 0x100 /* expanding string for arithmetic evaluation */ +#define Q_ARRAYSUB 0x200 /* expanding indexed array subscript */ + +/* Flag values controlling how assignment statements are treated. */ +#define ASS_APPEND 0x0001 +#define ASS_MKLOCAL 0x0002 +#define ASS_MKASSOC 0x0004 +#define ASS_MKGLOBAL 0x0008 /* force global assignment */ +#define ASS_NAMEREF 0x0010 /* assigning to nameref variable */ +#define ASS_FORCE 0x0020 /* force assignment even to readonly variable */ +#define ASS_CHKLOCAL 0x0040 /* check local variable before assignment */ +#define ASS_NOEXPAND 0x0080 /* don't expand associative array subscripts */ +#define ASS_NOEVAL 0x0100 /* don't evaluate value as expression */ +#define ASS_NOLONGJMP 0x0200 /* don't longjmp on fatal assignment error */ +#define ASS_NOINVIS 0x0400 /* don't resolve local invisible variables */ +#define ASS_ALLOWALLSUB 0x0800 /* allow * and @ as associative array keys */ +#define ASS_ONEWORD 0x1000 /* don't check array subscripts, assume higher level has done that */ + +/* Flags for the string extraction functions. */ +#define SX_NOALLOC 0x0001 /* just skip; don't return substring */ +#define SX_VARNAME 0x0002 /* variable name; for string_extract () */ +#define SX_REQMATCH 0x0004 /* closing/matching delimiter required */ +#define SX_COMMAND 0x0008 /* extracting a shell script/command */ +#define SX_NOCTLESC 0x0010 /* don't honor CTLESC quoting */ +#define SX_NOESCCTLNUL 0x0020 /* don't let CTLESC quote CTLNUL */ +#define SX_NOLONGJMP 0x0040 /* don't longjmp on fatal error */ +#define SX_ARITHSUB 0x0080 /* extracting $(( ... )) (currently unused) */ +#define SX_POSIXEXP 0x0100 /* extracting new Posix pattern removal expansions in extract_dollar_brace_string */ +#define SX_WORD 0x0200 /* extracting word in ${param op word} */ +#define SX_COMPLETE 0x0400 /* extracting word for completion */ +#define SX_STRIPDQ 0x0800 /* strip double quotes when extracting double-quoted string */ +#define SX_NOERROR 0x1000 /* don't print parser error messages */ + +/* Remove backslashes which are quoting backquotes from STRING. Modifies + STRING, and returns a pointer to it. */ +extern char * de_backslash PARAMS((char *)); + +/* Replace instances of \! in a string with !. */ +extern void unquote_bang PARAMS((char *)); + +/* Extract the $( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "$(". + Make (SINDEX) get the position just after the matching ")". + XFLAGS is additional flags to pass to other extraction functions, */ +extern char *extract_command_subst PARAMS((char *, int *, int)); + +/* Extract the $[ construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "$[". + Make (SINDEX) get the position just after the matching "]". */ +extern char *extract_arithmetic_subst PARAMS((char *, int *)); + +#if defined (PROCESS_SUBSTITUTION) +/* Extract the <( or >( construct in STRING, and return a new string. + Start extracting at (SINDEX) as if we had just seen "<(". + Make (SINDEX) get the position just after the matching ")". */ +extern char *extract_process_subst PARAMS((char *, char *, int *, int)); +#endif /* PROCESS_SUBSTITUTION */ + +/* Extract the name of the variable to bind to from the assignment string. */ +extern char *assignment_name PARAMS((char *)); + +/* Return a single string of all the words present in LIST, separating + each word with SEP. */ +extern char *string_list_internal PARAMS((WORD_LIST *, char *)); + +/* Return a single string of all the words present in LIST, separating + each word with a space. */ +extern char *string_list PARAMS((WORD_LIST *)); + +/* Turn $* into a single string, obeying POSIX rules. */ +extern char *string_list_dollar_star PARAMS((WORD_LIST *, int, int)); + +/* Expand $@ into a single string, obeying POSIX rules. */ +extern char *string_list_dollar_at PARAMS((WORD_LIST *, int, int)); + +/* Turn the positional parameters into a string, understanding quoting and + the various subtleties of using the first character of $IFS as the + separator. Calls string_list_dollar_at, string_list_dollar_star, and + string_list as appropriate. */ +extern char *string_list_pos_params PARAMS((int, WORD_LIST *, int, int)); + +/* Perform quoted null character removal on each element of LIST. + This modifies LIST. */ +extern void word_list_remove_quoted_nulls PARAMS((WORD_LIST *)); + +/* This performs word splitting and quoted null character removal on + STRING. */ +extern WORD_LIST *list_string PARAMS((char *, char *, int)); + +extern char *ifs_firstchar PARAMS((int *)); +extern char *get_word_from_string PARAMS((char **, char *, char **)); +extern char *strip_trailing_ifs_whitespace PARAMS((char *, char *, int)); + +/* Given STRING, an assignment string, get the value of the right side + of the `=', and bind it to the left side. If EXPAND is true, then + perform tilde expansion, parameter expansion, command substitution, + and arithmetic expansion on the right-hand side. Do not perform word + splitting on the result of expansion. */ +extern int do_assignment PARAMS((char *)); +extern int do_assignment_no_expand PARAMS((char *)); +extern int do_word_assignment PARAMS((WORD_DESC *, int)); + +/* Append SOURCE to TARGET at INDEX. SIZE is the current amount + of space allocated to TARGET. SOURCE can be NULL, in which + case nothing happens. Gets rid of SOURCE by free ()ing it. + Returns TARGET in case the location has changed. */ +extern char *sub_append_string PARAMS((char *, char *, size_t *, size_t *)); + +/* Append the textual representation of NUMBER to TARGET. + INDEX and SIZE are as in SUB_APPEND_STRING. */ +extern char *sub_append_number PARAMS((intmax_t, char *, int *, int *)); + +/* Return the word list that corresponds to `$*'. */ +extern WORD_LIST *list_rest_of_args PARAMS((void)); + +/* Make a single large string out of the dollar digit variables, + and the rest_of_args. If DOLLAR_STAR is 1, then obey the special + case of "$*" with respect to IFS. */ +extern char *string_rest_of_args PARAMS((int)); + +/* Expand STRING by performing parameter expansion, command substitution, + and arithmetic expansion. Dequote the resulting WORD_LIST before + returning it, but do not perform word splitting. The call to + remove_quoted_nulls () is made here because word splitting normally + takes care of quote removal. */ +extern WORD_LIST *expand_string_unsplit PARAMS((char *, int)); + +/* Expand the rhs of an assignment statement. */ +extern WORD_LIST *expand_string_assignment PARAMS((char *, int)); + +/* Expand a prompt string. */ +extern WORD_LIST *expand_prompt_string PARAMS((char *, int, int)); + +/* Expand STRING just as if you were expanding a word. This also returns + a list of words. Note that filename globbing is *NOT* done for word + or string expansion, just when the shell is expanding a command. This + does parameter expansion, command substitution, arithmetic expansion, + and word splitting. Dequote the resultant WORD_LIST before returning. */ +extern WORD_LIST *expand_string PARAMS((char *, int)); + +/* Convenience functions that expand strings to strings, taking care of + converting the WORD_LIST * returned by the expand_string* functions + to a string and deallocating the WORD_LIST *. */ +extern char *expand_string_to_string PARAMS((char *, int)); +extern char *expand_string_unsplit_to_string PARAMS((char *, int)); +extern char *expand_assignment_string_to_string PARAMS((char *, int)); +extern char *expand_subscript_string PARAMS((char *, int)); + +/* Expand an arithmetic expression string */ +extern char *expand_arith_string PARAMS((char *, int)); + +/* Expand $'...' and $"..." in a string for code paths that do not. */ +extern char *expand_string_dollar_quote PARAMS((char *, int)); + +/* De-quote quoted characters in STRING. */ +extern char *dequote_string PARAMS((char *)); + +/* De-quote CTLESC-escaped CTLESC or CTLNUL characters in STRING. */ +extern char *dequote_escapes PARAMS((const char *)); + +extern WORD_DESC *dequote_word PARAMS((WORD_DESC *)); + +/* De-quote quoted characters in each word in LIST. */ +extern WORD_LIST *dequote_list PARAMS((WORD_LIST *)); + +/* Expand WORD, performing word splitting on the result. This does + parameter expansion, command substitution, arithmetic expansion, + word splitting, and quote removal. */ +extern WORD_LIST *expand_word PARAMS((WORD_DESC *, int)); + +/* Expand WORD, but do not perform word splitting on the result. This + does parameter expansion, command substitution, arithmetic expansion, + and quote removal. */ +extern WORD_LIST *expand_word_unsplit PARAMS((WORD_DESC *, int)); +extern WORD_LIST *expand_word_leave_quoted PARAMS((WORD_DESC *, int)); + +/* Return the value of a positional parameter. This handles values > 10. */ +extern char *get_dollar_var_value PARAMS((intmax_t)); + +/* Quote a string to protect it from word splitting. */ +extern char *quote_string PARAMS((char *)); + +/* Quote escape characters (characters special to internals of expansion) + in a string. */ +extern char *quote_escapes PARAMS((const char *)); + +/* And remove such quoted special characters. */ +extern char *remove_quoted_escapes PARAMS((char *)); + +/* Remove CTLNUL characters from STRING unless they are quoted with CTLESC. */ +extern char *remove_quoted_nulls PARAMS((char *)); + +/* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the + backslash quoting rules for within double quotes. */ +extern char *string_quote_removal PARAMS((char *, int)); + +/* Perform quote removal on word WORD. This allocates and returns a new + WORD_DESC *. */ +extern WORD_DESC *word_quote_removal PARAMS((WORD_DESC *, int)); + +/* Perform quote removal on all words in LIST. If QUOTED is non-zero, + the members of the list are treated as if they are surrounded by + double quotes. Return a new list, or NULL if LIST is NULL. */ +extern WORD_LIST *word_list_quote_removal PARAMS((WORD_LIST *, int)); + +/* Called when IFS is changed to maintain some private variables. */ +extern void setifs PARAMS((SHELL_VAR *)); + +/* Return the value of $IFS, or " \t\n" if IFS is unset. */ +extern char *getifs PARAMS((void)); + +/* This splits a single word into a WORD LIST on $IFS, but only if the word + is not quoted. list_string () performs quote removal for us, even if we + don't do any splitting. */ +extern WORD_LIST *word_split PARAMS((WORD_DESC *, char *)); + +/* Take the list of words in LIST and do the various substitutions. Return + a new list of words which is the expanded list, and without things like + variable assignments. */ +extern WORD_LIST *expand_words PARAMS((WORD_LIST *)); + +/* Same as expand_words (), but doesn't hack variable or environment + variables. */ +extern WORD_LIST *expand_words_no_vars PARAMS((WORD_LIST *)); + +/* Perform the `normal shell expansions' on a WORD_LIST. These are + brace expansion, tilde expansion, parameter and variable substitution, + command substitution, arithmetic expansion, and word splitting. */ +extern WORD_LIST *expand_words_shellexp PARAMS((WORD_LIST *)); + +extern WORD_DESC *command_substitute PARAMS((char *, int, int)); +extern char *pat_subst PARAMS((char *, char *, char *, int)); + +#if defined (PROCESS_SUBSTITUTION) +extern int fifos_pending PARAMS((void)); +extern int num_fifos PARAMS((void)); +extern void unlink_fifo_list PARAMS((void)); +extern void unlink_all_fifos PARAMS((void)); +extern void unlink_fifo PARAMS((int)); + +extern void *copy_fifo_list PARAMS((int *)); +extern void close_new_fifos PARAMS((void *, int)); + +extern void clear_fifo_list PARAMS((void)); + +extern int find_procsub_child PARAMS((pid_t)); +extern void set_procsub_status PARAMS((int, pid_t, int)); + +extern void wait_procsubs PARAMS((void)); +extern void reap_procsubs PARAMS((void)); +#endif + +extern WORD_LIST *list_string_with_quotes PARAMS((char *)); + +#if defined (ARRAY_VARS) +extern char *extract_array_assignment_list PARAMS((char *, int *)); +#endif + +#if defined (COND_COMMAND) +extern char *remove_backslashes PARAMS((char *)); +extern char *cond_expand_word PARAMS((WORD_DESC *, int)); +#endif + +/* Flags for skip_to_delim */ +#define SD_NOJMP 0x001 /* don't longjmp on fatal error. */ +#define SD_INVERT 0x002 /* look for chars NOT in passed set */ +#define SD_NOQUOTEDELIM 0x004 /* don't let single or double quotes act as delimiters */ +#define SD_NOSKIPCMD 0x008 /* don't skip over $(, <(, or >( command/process substitution; parse them as commands */ +#define SD_EXTGLOB 0x010 /* skip over extended globbing patterns if appropriate */ +#define SD_IGNOREQUOTE 0x020 /* single and double quotes are not special */ +#define SD_GLOB 0x040 /* skip over glob patterns like bracket expressions */ +#define SD_NOPROCSUB 0x080 /* don't parse process substitutions as commands */ +#define SD_COMPLETE 0x100 /* skip_to_delim during completion */ +#define SD_HISTEXP 0x200 /* skip_to_delim during history expansion */ +#define SD_ARITHEXP 0x400 /* skip_to_delim during arithmetic expansion */ +#define SD_NOERROR 0x800 /* don't print error messages */ + +extern int skip_to_delim PARAMS((char *, int, char *, int)); + +#if defined (BANG_HISTORY) +extern int skip_to_histexp PARAMS((char *, int, char *, int)); +#endif + +#if defined (READLINE) +extern int char_is_quoted PARAMS((char *, int)); +extern int unclosed_pair PARAMS((char *, int, char *)); +extern WORD_LIST *split_at_delims PARAMS((char *, int, const char *, int, int, int *, int *)); +#endif + +/* Variables used to keep track of the characters in IFS. */ +extern SHELL_VAR *ifs_var; +extern char *ifs_value; +extern unsigned char ifs_cmap[]; +extern int ifs_is_set, ifs_is_null; + +#if defined (HANDLE_MULTIBYTE) +extern unsigned char ifs_firstc[]; +extern size_t ifs_firstc_len; +#else +extern unsigned char ifs_firstc; +#endif + +extern int assigning_in_environment; +extern int expanding_redir; +extern int inherit_errexit; + +extern pid_t last_command_subst_pid; + +/* Evaluates to 1 if C is a character in $IFS. */ +#define isifs(c) (ifs_cmap[(unsigned char)(c)] != 0) + +/* How to determine the quoted state of the character C. */ +#define QUOTED_CHAR(c) ((c) == CTLESC) + +/* Is the first character of STRING a quoted NULL character? */ +#define QUOTED_NULL(string) ((string)[0] == CTLNUL && (string)[1] == '\0') + +extern void invalidate_cached_quoted_dollar_at PARAMS((void)); + +#endif /* !_SUBST_H_ */ diff --git a/third_party/bash/syntax.c b/third_party/bash/syntax.c new file mode 100644 index 000000000..c14e068f3 --- /dev/null +++ b/third_party/bash/syntax.c @@ -0,0 +1,269 @@ +/* + * This file was generated by mksyntax. DO NOT EDIT. + */ + + +#include "config.h" +#include "stdc.h" +#include "syntax.h" + + +int sh_syntabsiz = 256; +int sh_syntaxtab[256] = { + CWORD, /* 0 */ + CSPECL, /* CTLESC */ + CWORD, /* 2 */ + CWORD, /* 3 */ + CWORD, /* 4 */ + CWORD, /* 5 */ + CWORD, /* 6 */ + CWORD, /* \a */ + CWORD, /* \b */ + CSHBRK|CBLANK, /* \t */ + CSHBRK|CBSDQUOTE, /* \n */ + CWORD, /* \v */ + CWORD, /* \f */ + CWORD, /* \r */ + CWORD, /* 14 */ + CWORD, /* 15 */ + CWORD, /* 16 */ + CWORD, /* 17 */ + CWORD, /* 18 */ + CWORD, /* 19 */ + CWORD, /* 20 */ + CWORD, /* 21 */ + CWORD, /* 22 */ + CWORD, /* 23 */ + CWORD, /* 24 */ + CWORD, /* 25 */ + CWORD, /* 26 */ + CWORD, /* ESC */ + CWORD, /* 28 */ + CWORD, /* 29 */ + CWORD, /* 30 */ + CWORD, /* 31 */ + CSHBRK|CBLANK, /* SPC */ + CXGLOB|CSPECVAR, /* ! */ + CQUOTE|CBSDQUOTE|CXQUOTE, /* " */ + CSPECVAR, /* # */ + CEXP|CBSDQUOTE|CBSHDOC|CSPECVAR, /* $ */ + CWORD, /* % */ + CSHMETA|CSHBRK, /* & */ + CQUOTE|CXQUOTE, /* ' */ + CSHMETA|CSHBRK, /* ( */ + CSHMETA|CSHBRK, /* ) */ + CGLOB|CXGLOB|CSPECVAR, /* * */ + CXGLOB|CSUBSTOP, /* + */ + CWORD, /* , */ + CSPECVAR|CSUBSTOP, /* - */ + CWORD, /* . */ + CWORD, /* / */ + CWORD, /* 0 */ + CWORD, /* 1 */ + CWORD, /* 2 */ + CWORD, /* 3 */ + CWORD, /* 4 */ + CWORD, /* 5 */ + CWORD, /* 6 */ + CWORD, /* 7 */ + CWORD, /* 8 */ + CWORD, /* 9 */ + CWORD, /* : */ + CSHMETA|CSHBRK, /* ; */ + CSHMETA|CSHBRK|CEXP, /* < */ + CSUBSTOP, /* = */ + CSHMETA|CSHBRK|CEXP, /* > */ + CGLOB|CXGLOB|CSPECVAR|CSUBSTOP, /* ? */ + CXGLOB|CSPECVAR, /* @ */ + CWORD, /* A */ + CWORD, /* B */ + CWORD, /* C */ + CWORD, /* D */ + CWORD, /* E */ + CWORD, /* F */ + CWORD, /* G */ + CWORD, /* H */ + CWORD, /* I */ + CWORD, /* J */ + CWORD, /* K */ + CWORD, /* L */ + CWORD, /* M */ + CWORD, /* N */ + CWORD, /* O */ + CWORD, /* P */ + CWORD, /* Q */ + CWORD, /* R */ + CWORD, /* S */ + CWORD, /* T */ + CWORD, /* U */ + CWORD, /* V */ + CWORD, /* W */ + CWORD, /* X */ + CWORD, /* Y */ + CWORD, /* Z */ + CGLOB, /* [ */ + CBSDQUOTE|CBSHDOC|CXQUOTE, /* \ */ + CGLOB, /* ] */ + CGLOB, /* ^ */ + CWORD, /* _ */ + CBACKQ|CQUOTE|CBSDQUOTE|CBSHDOC|CXQUOTE, /* ` */ + CWORD, /* a */ + CWORD, /* b */ + CWORD, /* c */ + CWORD, /* d */ + CWORD, /* e */ + CWORD, /* f */ + CWORD, /* g */ + CWORD, /* h */ + CWORD, /* i */ + CWORD, /* j */ + CWORD, /* k */ + CWORD, /* l */ + CWORD, /* m */ + CWORD, /* n */ + CWORD, /* o */ + CWORD, /* p */ + CWORD, /* q */ + CWORD, /* r */ + CWORD, /* s */ + CWORD, /* t */ + CWORD, /* u */ + CWORD, /* v */ + CWORD, /* w */ + CWORD, /* x */ + CWORD, /* y */ + CWORD, /* z */ + CWORD, /* { */ + CSHMETA|CSHBRK, /* | */ + CWORD, /* } */ + CWORD, /* ~ */ + CSPECL, /* CTLNUL */ + CWORD, /* 128 */ + CWORD, /* 129 */ + CWORD, /* 130 */ + CWORD, /* 131 */ + CWORD, /* 132 */ + CWORD, /* 133 */ + CWORD, /* 134 */ + CWORD, /* 135 */ + CWORD, /* 136 */ + CWORD, /* 137 */ + CWORD, /* 138 */ + CWORD, /* 139 */ + CWORD, /* 140 */ + CWORD, /* 141 */ + CWORD, /* 142 */ + CWORD, /* 143 */ + CWORD, /* 144 */ + CWORD, /* 145 */ + CWORD, /* 146 */ + CWORD, /* 147 */ + CWORD, /* 148 */ + CWORD, /* 149 */ + CWORD, /* 150 */ + CWORD, /* 151 */ + CWORD, /* 152 */ + CWORD, /* 153 */ + CWORD, /* 154 */ + CWORD, /* 155 */ + CWORD, /* 156 */ + CWORD, /* 157 */ + CWORD, /* 158 */ + CWORD, /* 159 */ + CWORD, /* 160 */ + CWORD, /* 161 */ + CWORD, /* 162 */ + CWORD, /* 163 */ + CWORD, /* 164 */ + CWORD, /* 165 */ + CWORD, /* 166 */ + CWORD, /* 167 */ + CWORD, /* 168 */ + CWORD, /* 169 */ + CWORD, /* 170 */ + CWORD, /* 171 */ + CWORD, /* 172 */ + CWORD, /* 173 */ + CWORD, /* 174 */ + CWORD, /* 175 */ + CWORD, /* 176 */ + CWORD, /* 177 */ + CWORD, /* 178 */ + CWORD, /* 179 */ + CWORD, /* 180 */ + CWORD, /* 181 */ + CWORD, /* 182 */ + CWORD, /* 183 */ + CWORD, /* 184 */ + CWORD, /* 185 */ + CWORD, /* 186 */ + CWORD, /* 187 */ + CWORD, /* 188 */ + CWORD, /* 189 */ + CWORD, /* 190 */ + CWORD, /* 191 */ + CWORD, /* 192 */ + CWORD, /* 193 */ + CWORD, /* 194 */ + CWORD, /* 195 */ + CWORD, /* 196 */ + CWORD, /* 197 */ + CWORD, /* 198 */ + CWORD, /* 199 */ + CWORD, /* 200 */ + CWORD, /* 201 */ + CWORD, /* 202 */ + CWORD, /* 203 */ + CWORD, /* 204 */ + CWORD, /* 205 */ + CWORD, /* 206 */ + CWORD, /* 207 */ + CWORD, /* 208 */ + CWORD, /* 209 */ + CWORD, /* 210 */ + CWORD, /* 211 */ + CWORD, /* 212 */ + CWORD, /* 213 */ + CWORD, /* 214 */ + CWORD, /* 215 */ + CWORD, /* 216 */ + CWORD, /* 217 */ + CWORD, /* 218 */ + CWORD, /* 219 */ + CWORD, /* 220 */ + CWORD, /* 221 */ + CWORD, /* 222 */ + CWORD, /* 223 */ + CWORD, /* 224 */ + CWORD, /* 225 */ + CWORD, /* 226 */ + CWORD, /* 227 */ + CWORD, /* 228 */ + CWORD, /* 229 */ + CWORD, /* 230 */ + CWORD, /* 231 */ + CWORD, /* 232 */ + CWORD, /* 233 */ + CWORD, /* 234 */ + CWORD, /* 235 */ + CWORD, /* 236 */ + CWORD, /* 237 */ + CWORD, /* 238 */ + CWORD, /* 239 */ + CWORD, /* 240 */ + CWORD, /* 241 */ + CWORD, /* 242 */ + CWORD, /* 243 */ + CWORD, /* 244 */ + CWORD, /* 245 */ + CWORD, /* 246 */ + CWORD, /* 247 */ + CWORD, /* 248 */ + CWORD, /* 249 */ + CWORD, /* 250 */ + CWORD, /* 251 */ + CWORD, /* 252 */ + CWORD, /* 253 */ + CWORD, /* 254 */ + CWORD, /* 255 */ +}; diff --git a/third_party/bash/syntax.h b/third_party/bash/syntax.h new file mode 100644 index 000000000..34f549647 --- /dev/null +++ b/third_party/bash/syntax.h @@ -0,0 +1,106 @@ +/* syntax.h -- Syntax definitions for the shell */ + +/* Copyright (C) 2000, 2001, 2005, 2008, 2009-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _SYNTAX_H_ +#define _SYNTAX_H_ + +/* Defines for use by mksyntax.c */ + +#define slashify_in_quotes "\\`$\"\n" +#define slashify_in_here_document "\\`$" + +#define shell_meta_chars "()<>;&|" +#define shell_break_chars "()<>;&| \t\n" + +#define shell_quote_chars "\"`'" + +#if defined (PROCESS_SUBSTITUTION) +# define shell_exp_chars "$<>" +#else +# define shell_exp_chars "$" +#endif + +#if defined (EXTENDED_GLOB) +# define ext_glob_chars "@*+?!" +#else +# define ext_glob_chars "" +#endif +#define shell_glob_chars "*?[]^" + +/* Defines shared by mksyntax.c and the rest of the shell code. */ + +/* Values for character flags in syntax tables */ + +#define CWORD 0x0000 /* nothing special; an ordinary character */ +#define CSHMETA 0x0001 /* shell meta character */ +#define CSHBRK 0x0002 /* shell break character */ +#define CBACKQ 0x0004 /* back quote */ +#define CQUOTE 0x0008 /* shell quote character */ +#define CSPECL 0x0010 /* special character that needs quoting */ +#define CEXP 0x0020 /* shell expansion character */ +#define CBSDQUOTE 0x0040 /* characters escaped by backslash in double quotes */ +#define CBSHDOC 0x0080 /* characters escaped by backslash in here doc */ +#define CGLOB 0x0100 /* globbing characters */ +#define CXGLOB 0x0200 /* extended globbing characters */ +#define CXQUOTE 0x0400 /* cquote + backslash */ +#define CSPECVAR 0x0800 /* single-character shell variable name */ +#define CSUBSTOP 0x1000 /* values of OP for ${word[:]OPstuff} */ +#define CBLANK 0x2000 /* whitespace (blank) character */ + +/* Defines for use by the rest of the shell. */ +extern int sh_syntaxtab[]; +extern int sh_syntabsiz; + +#define shellmeta(c) (sh_syntaxtab[(unsigned char)(c)] & CSHMETA) +#define shellbreak(c) (sh_syntaxtab[(unsigned char)(c)] & CSHBRK) +#define shellquote(c) (sh_syntaxtab[(unsigned char)(c)] & CQUOTE) +#define shellxquote(c) (sh_syntaxtab[(unsigned char)(c)] & CXQUOTE) + +#define shellblank(c) (sh_syntaxtab[(unsigned char)(c)] & CBLANK) + +#define parserblank(c) ((c) == ' ' || (c) == '\t') + +#define issyntype(c, t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) != 0) +#define notsyntype(c,t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) == 0) + +#if defined (PROCESS_SUBSTITUTION) +# define shellexp(c) ((c) == '$' || (c) == '<' || (c) == '>') +#else +# define shellexp(c) ((c) == '$') +#endif + +#if defined (EXTENDED_GLOB) +# define PATTERN_CHAR(c) \ + ((c) == '@' || (c) == '*' || (c) == '+' || (c) == '?' || (c) == '!') +#else +# define PATTERN_CHAR(c) 0 +#endif + +#define GLOB_CHAR(c) \ + ((c) == '*' || (c) == '?' || (c) == '[' || (c) == ']' || (c) == '^') + +#define CTLESC '\001' +#define CTLNUL '\177' + +#if !defined (HAVE_ISBLANK) && !defined (isblank) +# define isblank(x) ((x) == ' ' || (x) == '\t') +#endif + +#endif /* _SYNTAX_H_ */ diff --git a/third_party/bash/systimes.h b/third_party/bash/systimes.h new file mode 100644 index 000000000..27cdb3e53 --- /dev/null +++ b/third_party/bash/systimes.h @@ -0,0 +1,55 @@ +/* Copyright (C) 1991-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* + * POSIX Standard: 4.5.2 Process Times + */ + +/* + * If we don't have a standard system clock_t type, this must be included + * after config.h + */ + +#ifndef _BASH_SYSTIMES_H +#define _BASH_SYSTIMES_H 1 + +#if defined (HAVE_SYS_TIMES_H) +# include +#else /* !HAVE_SYS_TIMES_H */ + +#include "stdc.h" + +/* Structure describing CPU time used by a process and its children. */ +struct tms + { + clock_t tms_utime; /* User CPU time. */ + clock_t tms_stime; /* System CPU time. */ + + clock_t tms_cutime; /* User CPU time of dead children. */ + clock_t tms_cstime; /* System CPU time of dead children. */ + }; + +/* Store the CPU time used by this process and all its + dead descendants in BUFFER. + Return the elapsed real time from an arbitrary point in the + past (the bash emulation uses the epoch), or (clock_t) -1 for + errors. All times are in CLK_TCKths of a second. */ +extern clock_t times PARAMS((struct tms *buffer)); + +#endif /* !HAVE_SYS_TIMES_H */ +#endif /* _BASH_SYSTIMES_H */ diff --git a/third_party/bash/test.c b/third_party/bash/test.c new file mode 100644 index 000000000..b058922c7 --- /dev/null +++ b/third_party/bash/test.c @@ -0,0 +1,921 @@ +/* test.c - GNU test program (ksb and mjb) */ + +/* Modified to run with the GNU shell Apr 25, 1988 by bfox. */ + +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* Define PATTERN_MATCHING to get the csh-like =~ and !~ pattern-matching + binary operators. */ +/* #define PATTERN_MATCHING */ + +#if defined (HAVE_CONFIG_H) +# include "config.h" +#endif + +#include + +#include "bashtypes.h" + +#if !defined (HAVE_LIMITS_H) && defined (HAVE_SYS_PARAM_H) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#if !defined (_POSIX_VERSION) && defined (HAVE_SYS_FILE_H) +# include +#endif /* !_POSIX_VERSION */ +#include "posixstat.h" +#include "filecntl.h" +#include "stat-time.h" + +#include "bashintl.h" + +#include "shell.h" +#include "pathexp.h" +#include "test.h" +#include "common.h" + +#include "strmatch.h" + +#if !defined (STRLEN) +# define STRLEN(s) ((s)[0] ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0) +#endif + +#if !defined (STREQ) +# define STREQ(a, b) ((a)[0] == (b)[0] && strcmp ((a), (b)) == 0) +#endif /* !STREQ */ +#define STRCOLLEQ(a, b) ((a)[0] == (b)[0] && strcoll ((a), (b)) == 0) + +#if !defined (R_OK) +#define R_OK 4 +#define W_OK 2 +#define X_OK 1 +#define F_OK 0 +#endif /* R_OK */ + +#define EQ 0 +#define NE 1 +#define LT 2 +#define GT 3 +#define LE 4 +#define GE 5 + +#define NT 0 +#define OT 1 +#define EF 2 + +/* The following few defines control the truth and false output of each stage. + TRUE and FALSE are what we use to compute the final output value. + SHELL_BOOLEAN is the form which returns truth or falseness in shell terms. + Default is TRUE = 1, FALSE = 0, SHELL_BOOLEAN = (!value). */ +#define TRUE 1 +#define FALSE 0 +#define SHELL_BOOLEAN(value) (!(value)) + +#define TEST_ERREXIT_STATUS 2 + +static procenv_t test_exit_buf; +static int test_error_return; +#define test_exit(val) \ + do { test_error_return = val; sh_longjmp (test_exit_buf, 1); } while (0) + +extern int sh_stat PARAMS((const char *, struct stat *)); + +static int pos; /* The offset of the current argument in ARGV. */ +static int argc; /* The number of arguments present in ARGV. */ +static char **argv; /* The argument list. */ +static int noeval; + +static void test_syntax_error PARAMS((char *, char *)) __attribute__((__noreturn__)); +static void beyond PARAMS((void)) __attribute__((__noreturn__)); +static void integer_expected_error PARAMS((char *)) __attribute__((__noreturn__)); + +static int unary_operator PARAMS((void)); +static int binary_operator PARAMS((void)); +static int two_arguments PARAMS((void)); +static int three_arguments PARAMS((void)); +static int posixtest PARAMS((void)); + +static int expr PARAMS((void)); +static int term PARAMS((void)); +static int and PARAMS((void)); +static int or PARAMS((void)); + +static int filecomp PARAMS((char *, char *, int)); +static int arithcomp PARAMS((char *, char *, int, int)); +static int patcomp PARAMS((char *, char *, int)); + +static void +test_syntax_error (format, arg) + char *format, *arg; +{ + builtin_error (format, arg); + test_exit (TEST_ERREXIT_STATUS); +} + +/* + * beyond - call when we're beyond the end of the argument list (an + * error condition) + */ +static void +beyond () +{ + test_syntax_error (_("argument expected"), (char *)NULL); +} + +/* Syntax error for when an integer argument was expected, but + something else was found. */ +static void +integer_expected_error (pch) + char *pch; +{ + test_syntax_error (_("%s: integer expression expected"), pch); +} + +/* Increment our position in the argument list. Check that we're not + past the end of the argument list. This check is suppressed if the + argument is FALSE. Made a macro for efficiency. */ +#define advance(f) do { ++pos; if (f && pos >= argc) beyond (); } while (0) +#define unary_advance() do { advance (1); ++pos; } while (0) + +/* + * expr: + * or + */ +static int +expr () +{ + if (pos >= argc) + beyond (); + + return (FALSE ^ or ()); /* Same with this. */ +} + +/* + * or: + * and + * and '-o' or + */ +static int +or () +{ + int value, v2; + + value = and (); + if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'o' && !argv[pos][2]) + { + advance (0); + v2 = or (); + return (value || v2); + } + + return (value); +} + +/* + * and: + * term + * term '-a' and + */ +static int +and () +{ + int value, v2; + + value = term (); + if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'a' && !argv[pos][2]) + { + advance (0); + v2 = and (); + return (value && v2); + } + return (value); +} + +/* + * term - parse a term and return 1 or 0 depending on whether the term + * evaluates to true or false, respectively. + * + * term ::= + * '-'('a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'k'|'p'|'r'|'s'|'u'|'w'|'x') filename + * '-'('G'|'L'|'O'|'S'|'N') filename + * '-t' [int] + * '-'('z'|'n') string + * '-'('v'|'R') varname + * '-o' option + * string + * string ('!='|'='|'==') string + * '-'(eq|ne|le|lt|ge|gt) + * file '-'(nt|ot|ef) file + * '(' ')' + * int ::= + * positive and negative integers + */ +static int +term () +{ + int value; + + if (pos >= argc) + beyond (); + + /* Deal with leading `not's. */ + if (argv[pos][0] == '!' && argv[pos][1] == '\0') + { + value = 0; + while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0') + { + advance (1); + value = 1 - value; + } + + return (value ? !term() : term()); + } + + /* A paren-bracketed argument. */ + if (argv[pos][0] == '(' && argv[pos][1] == '\0') /* ) */ + { + advance (1); + value = expr (); + if (argv[pos] == 0) /* ( */ + test_syntax_error (_("`)' expected"), (char *)NULL); + else if (argv[pos][0] != ')' || argv[pos][1]) /* ( */ + test_syntax_error (_("`)' expected, found %s"), argv[pos]); + advance (0); + return (value); + } + + /* are there enough arguments left that this could be dyadic? */ + if ((pos + 3 <= argc) && test_binop (argv[pos + 1])) + value = binary_operator (); + + /* Might be a switch type argument -- make sure we have enough arguments for + the unary operator and argument */ + else if ((pos + 2) <= argc && test_unop (argv[pos])) + value = unary_operator (); + + else + { + value = argv[pos][0] != '\0'; + advance (0); + } + + return (value); +} + +static int +stat_mtime (fn, st, ts) + char *fn; + struct stat *st; + struct timespec *ts; +{ + int r; + + r = sh_stat (fn, st); + if (r < 0) + return r; + *ts = get_stat_mtime (st); + return 0; +} + +static int +filecomp (s, t, op) + char *s, *t; + int op; +{ + struct stat st1, st2; + struct timespec ts1, ts2; + int r1, r2; + + if ((r1 = stat_mtime (s, &st1, &ts1)) < 0) + { + if (op == EF) + return (FALSE); + } + if ((r2 = stat_mtime (t, &st2, &ts2)) < 0) + { + if (op == EF) + return (FALSE); + } + + switch (op) + { + case OT: return (r1 < r2 || (r2 == 0 && timespec_cmp (ts1, ts2) < 0)); + case NT: return (r1 > r2 || (r1 == 0 && timespec_cmp (ts1, ts2) > 0)); + case EF: return (same_file (s, t, &st1, &st2)); + } + return (FALSE); +} + +static int +arithcomp (s, t, op, flags) + char *s, *t; + int op, flags; +{ + intmax_t l, r; + int expok; + + if (flags & TEST_ARITHEXP) /* conditional command */ + { + int eflag; + + eflag = (shell_compatibility_level > 51) ? 0 : EXP_EXPANDED; + l = evalexp (s, eflag, &expok); + if (expok == 0) + return (FALSE); /* should probably longjmp here */ + r = evalexp (t, eflag, &expok); + if (expok == 0) + return (FALSE); /* ditto */ + } + else + { + if (legal_number (s, &l) == 0) + integer_expected_error (s); + if (legal_number (t, &r) == 0) + integer_expected_error (t); + } + + switch (op) + { + case EQ: return (l == r); + case NE: return (l != r); + case LT: return (l < r); + case GT: return (l > r); + case LE: return (l <= r); + case GE: return (l >= r); + } + + return (FALSE); +} + +static int +patcomp (string, pat, op) + char *string, *pat; + int op; +{ + int m; + + m = strmatch (pat, string, FNMATCH_EXTFLAG|FNMATCH_IGNCASE); + return ((op == EQ) ? (m == 0) : (m != 0)); +} + +int +binary_test (op, arg1, arg2, flags) + char *op, *arg1, *arg2; + int flags; +{ + int patmatch; + + patmatch = (flags & TEST_PATMATCH); + + if (op[0] == '=' && (op[1] == '\0' || (op[1] == '=' && op[2] == '\0'))) + return (patmatch ? patcomp (arg1, arg2, EQ) : STREQ (arg1, arg2)); + else if ((op[0] == '>' || op[0] == '<') && op[1] == '\0') + { +#if defined (HAVE_STRCOLL) + if (shell_compatibility_level > 40 && flags & TEST_LOCALE) + return ((op[0] == '>') ? (strcoll (arg1, arg2) > 0) : (strcoll (arg1, arg2) < 0)); + else +#endif + return ((op[0] == '>') ? (strcmp (arg1, arg2) > 0) : (strcmp (arg1, arg2) < 0)); + } + else if (op[0] == '!' && op[1] == '=' && op[2] == '\0') + return (patmatch ? patcomp (arg1, arg2, NE) : (STREQ (arg1, arg2) == 0)); + + + else if (op[2] == 't') + { + switch (op[1]) + { + case 'n': return (filecomp (arg1, arg2, NT)); /* -nt */ + case 'o': return (filecomp (arg1, arg2, OT)); /* -ot */ + case 'l': return (arithcomp (arg1, arg2, LT, flags)); /* -lt */ + case 'g': return (arithcomp (arg1, arg2, GT, flags)); /* -gt */ + } + } + else if (op[1] == 'e') + { + switch (op[2]) + { + case 'f': return (filecomp (arg1, arg2, EF)); /* -ef */ + case 'q': return (arithcomp (arg1, arg2, EQ, flags)); /* -eq */ + } + } + else if (op[2] == 'e') + { + switch (op[1]) + { + case 'n': return (arithcomp (arg1, arg2, NE, flags)); /* -ne */ + case 'g': return (arithcomp (arg1, arg2, GE, flags)); /* -ge */ + case 'l': return (arithcomp (arg1, arg2, LE, flags)); /* -le */ + } + } + + return (FALSE); /* should never get here */ +} + + +static int +binary_operator () +{ + int value; + char *w; + + w = argv[pos + 1]; + if ((w[0] == '=' && (w[1] == '\0' || (w[1] == '=' && w[2] == '\0'))) || /* =, == */ + ((w[0] == '>' || w[0] == '<') && w[1] == '\0') || /* <, > */ + (w[0] == '!' && w[1] == '=' && w[2] == '\0')) /* != */ + { + value = binary_test (w, argv[pos], argv[pos + 2], 0); + pos += 3; + return (value); + } + +#if defined (PATTERN_MATCHING) + if ((w[0] == '=' || w[0] == '!') && w[1] == '~' && w[2] == '\0') + { + value = patcomp (argv[pos], argv[pos + 2], w[0] == '=' ? EQ : NE); + pos += 3; + return (value); + } +#endif + + if ((w[0] != '-' || w[3] != '\0') || test_binop (w) == 0) + { + test_syntax_error (_("%s: binary operator expected"), w); + /* NOTREACHED */ + return (FALSE); + } + + value = binary_test (w, argv[pos], argv[pos + 2], 0); + pos += 3; + return value; +} + +static int +unary_operator () +{ + char *op; + intmax_t r; + + op = argv[pos]; + if (test_unop (op) == 0) + return (FALSE); + + /* the only tricky case is `-t', which may or may not take an argument. */ + if (op[1] == 't') + { + advance (0); + if (pos < argc) + { + if (legal_number (argv[pos], &r)) + { + advance (0); + return (unary_test (op, argv[pos - 1], 0)); + } + else + return (FALSE); + } + else + return (unary_test (op, "1", 0)); + } + + /* All of the unary operators take an argument, so we first call + unary_advance (), which checks to make sure that there is an + argument, and then advances pos right past it. This means that + pos - 1 is the location of the argument. */ + unary_advance (); + return (unary_test (op, argv[pos - 1], 0)); +} + +int +unary_test (op, arg, flags) + char *op, *arg; + int flags; +{ + intmax_t r; + struct stat stat_buf; + struct timespec mtime, atime; + SHELL_VAR *v; + int aflags; + + switch (op[1]) + { + case 'a': /* file exists in the file system? */ + case 'e': + return (sh_stat (arg, &stat_buf) == 0); + + case 'r': /* file is readable? */ + return (sh_eaccess (arg, R_OK) == 0); + + case 'w': /* File is writeable? */ + return (sh_eaccess (arg, W_OK) == 0); + + case 'x': /* File is executable? */ + return (sh_eaccess (arg, X_OK) == 0); + + case 'O': /* File is owned by you? */ + return (sh_stat (arg, &stat_buf) == 0 && + (uid_t) current_user.euid == (uid_t) stat_buf.st_uid); + + case 'G': /* File is owned by your group? */ + return (sh_stat (arg, &stat_buf) == 0 && + (gid_t) current_user.egid == (gid_t) stat_buf.st_gid); + + case 'N': + if (sh_stat (arg, &stat_buf) < 0) + return (FALSE); + atime = get_stat_atime (&stat_buf); + mtime = get_stat_mtime (&stat_buf); + return (timespec_cmp (mtime, atime) > 0); + + case 'f': /* File is a file? */ + if (sh_stat (arg, &stat_buf) < 0) + return (FALSE); + + /* -f is true if the given file exists and is a regular file. */ +#if defined (S_IFMT) + return (S_ISREG (stat_buf.st_mode) || (stat_buf.st_mode & S_IFMT) == 0); +#else + return (S_ISREG (stat_buf.st_mode)); +#endif /* !S_IFMT */ + + case 'd': /* File is a directory? */ + return (sh_stat (arg, &stat_buf) == 0 && (S_ISDIR (stat_buf.st_mode))); + + case 's': /* File has something in it? */ + return (sh_stat (arg, &stat_buf) == 0 && stat_buf.st_size > (off_t) 0); + + case 'S': /* File is a socket? */ +#if !defined (S_ISSOCK) + return (FALSE); +#else + return (sh_stat (arg, &stat_buf) == 0 && S_ISSOCK (stat_buf.st_mode)); +#endif /* S_ISSOCK */ + + case 'c': /* File is character special? */ + return (sh_stat (arg, &stat_buf) == 0 && S_ISCHR (stat_buf.st_mode)); + + case 'b': /* File is block special? */ + return (sh_stat (arg, &stat_buf) == 0 && S_ISBLK (stat_buf.st_mode)); + + case 'p': /* File is a named pipe? */ +#ifndef S_ISFIFO + return (FALSE); +#else + return (sh_stat (arg, &stat_buf) == 0 && S_ISFIFO (stat_buf.st_mode)); +#endif /* S_ISFIFO */ + + case 'L': /* Same as -h */ + case 'h': /* File is a symbolic link? */ +#if !defined (S_ISLNK) || !defined (HAVE_LSTAT) + return (FALSE); +#else + return ((arg[0] != '\0') && + (lstat (arg, &stat_buf) == 0) && S_ISLNK (stat_buf.st_mode)); +#endif /* S_IFLNK && HAVE_LSTAT */ + + case 'u': /* File is setuid? */ + return (sh_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISUID) != 0); + + case 'g': /* File is setgid? */ + return (sh_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISGID) != 0); + + case 'k': /* File has sticky bit set? */ +#if !defined (S_ISVTX) + /* This is not Posix, and is not defined on some Posix systems. */ + return (FALSE); +#else + return (sh_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISVTX) != 0); +#endif + + case 't': /* File fd is a terminal? */ + if (legal_number (arg, &r) == 0) + return (FALSE); + return ((r == (int)r) && isatty ((int)r)); + + case 'n': /* True if arg has some length. */ + return (arg[0] != '\0'); + + case 'z': /* True if arg has no length. */ + return (arg[0] == '\0'); + + case 'o': /* True if option `arg' is set. */ + return (minus_o_option_value (arg) == 1); + + case 'v': +#if defined (ARRAY_VARS) + aflags = assoc_expand_once ? AV_NOEXPAND : 0; + if (valid_array_reference (arg, aflags)) + { + char *t; + int ret; + array_eltstate_t es; + + /* Let's assume that this has already been expanded once. */ + /* XXX - TAG:bash-5.2 fix with corresponding fix to execute_cmd.c: + execute_cond_node() that passes TEST_ARRAYEXP in FLAGS */ + + if (shell_compatibility_level > 51) + /* Allow associative arrays to use `test -v array[@]' to look for + a key named `@'. */ + aflags |= AV_ATSTARKEYS; /* XXX */ + init_eltstate (&es); + t = get_array_value (arg, aflags|AV_ALLOWALL, &es); + ret = t ? TRUE : FALSE; + if (es.subtype > 0) /* subscript is * or @ */ + free (t); + flush_eltstate (&es); + return ret; + } + else if (legal_number (arg, &r)) /* -v n == is $n set? */ + return ((r >= 0 && r <= number_of_args()) ? TRUE : FALSE); + v = find_variable (arg); + if (v && invisible_p (v) == 0 && array_p (v)) + { + char *t; + /* [[ -v foo ]] == [[ -v foo[0] ]] */ + t = array_reference (array_cell (v), 0); + return (t ? TRUE : FALSE); + } + else if (v && invisible_p (v) == 0 && assoc_p (v)) + { + char *t; + t = assoc_reference (assoc_cell (v), "0"); + return (t ? TRUE : FALSE); + } +#else + v = find_variable (arg); +#endif + return (v && invisible_p (v) == 0 && var_isset (v) ? TRUE : FALSE); + + case 'R': + v = find_variable_noref (arg); + return ((v && invisible_p (v) == 0 && var_isset (v) && nameref_p (v)) ? TRUE : FALSE); + } + + /* We can't actually get here, but this shuts up gcc. */ + return (FALSE); +} + +/* Return TRUE if OP is one of the test command's binary operators. */ +int +test_binop (op) + char *op; +{ + if (op[0] == '=' && op[1] == '\0') + return (1); /* '=' */ + else if ((op[0] == '<' || op[0] == '>') && op[1] == '\0') /* string <, > */ + return (1); + else if ((op[0] == '=' || op[0] == '!') && op[1] == '=' && op[2] == '\0') + return (1); /* `==' and `!=' */ +#if defined (PATTERN_MATCHING) + else if (op[2] == '\0' && op[1] == '~' && (op[0] == '=' || op[0] == '!')) + return (1); +#endif + else if (op[0] != '-' || op[1] == '\0' || op[2] == '\0' || op[3] != '\0') + return (0); + else + { + if (op[2] == 't') + switch (op[1]) + { + case 'n': /* -nt */ + case 'o': /* -ot */ + case 'l': /* -lt */ + case 'g': /* -gt */ + return (1); + default: + return (0); + } + else if (op[1] == 'e') + switch (op[2]) + { + case 'q': /* -eq */ + case 'f': /* -ef */ + return (1); + default: + return (0); + } + else if (op[2] == 'e') + switch (op[1]) + { + case 'n': /* -ne */ + case 'g': /* -ge */ + case 'l': /* -le */ + return (1); + default: + return (0); + } + else + return (0); + } +} + +/* Return non-zero if OP is one of the test command's unary operators. */ +int +test_unop (op) + char *op; +{ + if (op[0] != '-' || (op[1] && op[2] != 0)) + return (0); + + switch (op[1]) + { + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'k': case 'n': + case 'o': case 'p': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'z': + case 'G': case 'L': case 'O': case 'S': case 'N': + case 'R': + return (1); + } + + return (0); +} + +static int +two_arguments () +{ + if (argv[pos][0] == '!' && argv[pos][1] == '\0') + return (argv[pos + 1][0] == '\0'); + else if (argv[pos][0] == '-' && argv[pos][1] && argv[pos][2] == '\0') + { + if (test_unop (argv[pos])) + return (unary_operator ()); + else + test_syntax_error (_("%s: unary operator expected"), argv[pos]); + } + else + test_syntax_error (_("%s: unary operator expected"), argv[pos]); + + return (0); +} + +#define ANDOR(s) (s[0] == '-' && (s[1] == 'a' || s[1] == 'o') && s[2] == 0) + +/* This could be augmented to handle `-t' as equivalent to `-t 1', but + POSIX requires that `-t' be given an argument. */ +#define ONE_ARG_TEST(s) ((s)[0] != '\0') + +static int +three_arguments () +{ + int value; + + if (test_binop (argv[pos+1])) + { + value = binary_operator (); + pos = argc; + } + else if (ANDOR (argv[pos+1])) + { + if (argv[pos+1][1] == 'a') + value = ONE_ARG_TEST(argv[pos]) && ONE_ARG_TEST(argv[pos+2]); + else + value = ONE_ARG_TEST(argv[pos]) || ONE_ARG_TEST(argv[pos+2]); + pos = argc; + } + else if (argv[pos][0] == '!' && argv[pos][1] == '\0') + { + advance (1); + value = !two_arguments (); + pos = argc; + } + else if (argv[pos][0] == '(' && argv[pos+2][0] == ')') + { + value = ONE_ARG_TEST(argv[pos+1]); + pos = argc; + } + else + test_syntax_error (_("%s: binary operator expected"), argv[pos+1]); + + return (value); +} + +/* This is an implementation of a Posix.2 proposal by David Korn. */ +static int +posixtest () +{ + int value; + + switch (argc - 1) /* one extra passed in */ + { + case 0: + value = FALSE; + pos = argc; + break; + + case 1: + value = ONE_ARG_TEST(argv[1]); + pos = argc; + break; + + case 2: + value = two_arguments (); + pos = argc; + break; + + case 3: + value = three_arguments (); + break; + + case 4: + if (argv[pos][0] == '!' && argv[pos][1] == '\0') + { + advance (1); + value = !three_arguments (); + break; + } + else if (argv[pos][0] == '(' && argv[pos][1] == '\0' && argv[argc-1][0] == ')' && argv[argc-1][1] == '\0') + { + advance (1); + value = two_arguments (); + pos = argc; + break; + } + /* FALLTHROUGH */ + default: + value = expr (); + } + + return (value); +} + +/* + * [: + * '[' expr ']' + * test: + * test expr + */ +int +test_command (margc, margv) + int margc; + char **margv; +{ + int value; + int code; + + USE_VAR(margc); + + code = setjmp_nosigs (test_exit_buf); + + if (code) + return (test_error_return); + + argv = margv; + + if (margv[0] && margv[0][0] == '[' && margv[0][1] == '\0') + { + --margc; + + if (margv[margc] && (margv[margc][0] != ']' || margv[margc][1])) + test_syntax_error (_("missing `]'"), (char *)NULL); + + if (margc < 2) + test_exit (SHELL_BOOLEAN (FALSE)); + } + + argc = margc; + pos = 1; + + if (pos >= argc) + test_exit (SHELL_BOOLEAN (FALSE)); + + noeval = 0; + value = posixtest (); + + if (pos != argc) + { + if (pos < argc && argv[pos][0] == '-') + test_syntax_error (_("syntax error: `%s' unexpected"), argv[pos]); + else + test_syntax_error (_("too many arguments"), (char *)NULL); + } + + test_exit (SHELL_BOOLEAN (value)); +} diff --git a/third_party/bash/test.h b/third_party/bash/test.h new file mode 100644 index 000000000..ffd79e5e0 --- /dev/null +++ b/third_party/bash/test.h @@ -0,0 +1,40 @@ +/* test.h -- external interface to the conditional command code. */ + +/* Copyright (C) 1997-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _TEST_H_ +#define _TEST_H_ + +#include "stdc.h" + +/* Values for the flags argument to binary_test */ +#define TEST_PATMATCH 0x01 +#define TEST_ARITHEXP 0x02 +#define TEST_LOCALE 0x04 +#define TEST_ARRAYEXP 0x08 /* array subscript expansion */ + +extern int test_unop PARAMS((char *)); +extern int test_binop PARAMS((char *)); + +extern int unary_test PARAMS((char *, char *, int)); +extern int binary_test PARAMS((char *, char *, char *, int)); + +extern int test_command PARAMS((int, char **)); + +#endif /* _TEST_H_ */ diff --git a/third_party/bash/tilde.c b/third_party/bash/tilde.c new file mode 100644 index 000000000..5bafbb214 --- /dev/null +++ b/third_party/bash/tilde.c @@ -0,0 +1,493 @@ +/* tilde.c -- Tilde expansion code (~/foo := $HOME/foo). */ + +/* Copyright (C) 1988-2020 Free Software Foundation, Inc. + + This file is part of the GNU Readline Library (Readline), a library + for reading lines of text with interactive input and history editing. + + Readline is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Readline is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline. If not, see . +*/ + +#if defined (HAVE_CONFIG_H) +# include "config.h" +#endif + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#if defined (HAVE_STRING_H) +# include +#else /* !HAVE_STRING_H */ +# include +#endif /* !HAVE_STRING_H */ + +#if defined (HAVE_STDLIB_H) +# include +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include +#if defined (HAVE_PWD_H) +#include +#endif + +#include "tilde.h" + +#if defined (TEST) || defined (STATIC_MALLOC) +static void *xmalloc (), *xrealloc (); +#else +# include "xmalloc.h" +#endif /* TEST || STATIC_MALLOC */ + +#if !defined (HAVE_GETPW_DECLS) +# if defined (HAVE_GETPWUID) +extern struct passwd *getpwuid (uid_t); +# endif +# if defined (HAVE_GETPWNAM) +extern struct passwd *getpwnam (const char *); +# endif +#endif /* !HAVE_GETPW_DECLS */ + +#if !defined (savestring) +#define savestring(x) strcpy ((char *)xmalloc (1 + strlen (x)), (x)) +#endif /* !savestring */ + +#if !defined (NULL) +# if defined (__STDC__) +# define NULL ((void *) 0) +# else +# define NULL 0x0 +# endif /* !__STDC__ */ +#endif /* !NULL */ + +/* If being compiled as part of bash, these will be satisfied from + variables.o. If being compiled as part of readline, they will + be satisfied from shell.o. */ +extern char *sh_get_home_dir (void); +extern char *sh_get_env_value (const char *); + +/* The default value of tilde_additional_prefixes. This is set to + whitespace preceding a tilde so that simple programs which do not + perform any word separation get desired behaviour. */ +static const char *default_prefixes[] = + { " ~", "\t~", (const char *)NULL }; + +/* The default value of tilde_additional_suffixes. This is set to + whitespace or newline so that simple programs which do not + perform any word separation get desired behaviour. */ +static const char *default_suffixes[] = + { " ", "\n", (const char *)NULL }; + +/* If non-null, this contains the address of a function that the application + wants called before trying the standard tilde expansions. The function + is called with the text sans tilde, and returns a malloc()'ed string + which is the expansion, or a NULL pointer if the expansion fails. */ +tilde_hook_func_t *tilde_expansion_preexpansion_hook = (tilde_hook_func_t *)NULL; + +/* If non-null, this contains the address of a function to call if the + standard meaning for expanding a tilde fails. The function is called + with the text (sans tilde, as in "foo"), and returns a malloc()'ed string + which is the expansion, or a NULL pointer if there is no expansion. */ +tilde_hook_func_t *tilde_expansion_failure_hook = (tilde_hook_func_t *)NULL; + +/* When non-null, this is a NULL terminated array of strings which + are duplicates for a tilde prefix. Bash uses this to expand + `=~' and `:~'. */ +char **tilde_additional_prefixes = (char **)default_prefixes; + +/* When non-null, this is a NULL terminated array of strings which match + the end of a username, instead of just "/". Bash sets this to + `:' and `=~'. */ +char **tilde_additional_suffixes = (char **)default_suffixes; + +static int tilde_find_prefix (const char *, int *); +static int tilde_find_suffix (const char *); +static char *isolate_tilde_prefix (const char *, int *); +static char *glue_prefix_and_suffix (char *, const char *, int); + +/* Find the start of a tilde expansion in STRING, and return the index of + the tilde which starts the expansion. Place the length of the text + which identified this tilde starter in LEN, excluding the tilde itself. */ +static int +tilde_find_prefix (const char *string, int *len) +{ + register int i, j, string_len; + register char **prefixes; + + prefixes = tilde_additional_prefixes; + + string_len = strlen (string); + *len = 0; + + if (*string == '\0' || *string == '~') + return (0); + + if (prefixes) + { + for (i = 0; i < string_len; i++) + { + for (j = 0; prefixes[j]; j++) + { + if (strncmp (string + i, prefixes[j], strlen (prefixes[j])) == 0) + { + *len = strlen (prefixes[j]) - 1; + return (i + *len); + } + } + } + } + return (string_len); +} + +/* Find the end of a tilde expansion in STRING, and return the index of + the character which ends the tilde definition. */ +static int +tilde_find_suffix (const char *string) +{ + register int i, j, string_len; + register char **suffixes; + + suffixes = tilde_additional_suffixes; + string_len = strlen (string); + + for (i = 0; i < string_len; i++) + { +#if defined (__MSDOS__) + if (string[i] == '/' || string[i] == '\\' /* || !string[i] */) +#else + if (string[i] == '/' /* || !string[i] */) +#endif + break; + + for (j = 0; suffixes && suffixes[j]; j++) + { + if (strncmp (string + i, suffixes[j], strlen (suffixes[j])) == 0) + return (i); + } + } + return (i); +} + +/* Return a new string which is the result of tilde expanding STRING. */ +char * +tilde_expand (const char *string) +{ + char *result; + int result_size, result_index; + + result_index = result_size = 0; + if (result = strchr (string, '~')) + result = (char *)xmalloc (result_size = (strlen (string) + 16)); + else + result = (char *)xmalloc (result_size = (strlen (string) + 1)); + + /* Scan through STRING expanding tildes as we come to them. */ + while (1) + { + register int start, end; + char *tilde_word, *expansion; + int len; + + /* Make START point to the tilde which starts the expansion. */ + start = tilde_find_prefix (string, &len); + + /* Copy the skipped text into the result. */ + if ((result_index + start + 1) > result_size) + result = (char *)xrealloc (result, 1 + (result_size += (start + 20))); + + strncpy (result + result_index, string, start); + result_index += start; + + /* Advance STRING to the starting tilde. */ + string += start; + + /* Make END be the index of one after the last character of the + username. */ + end = tilde_find_suffix (string); + + /* If both START and END are zero, we are all done. */ + if (!start && !end) + break; + + /* Expand the entire tilde word, and copy it into RESULT. */ + tilde_word = (char *)xmalloc (1 + end); + strncpy (tilde_word, string, end); + tilde_word[end] = '\0'; + string += end; + + expansion = tilde_expand_word (tilde_word); + + if (expansion == 0) + expansion = tilde_word; + else + xfree (tilde_word); + + len = strlen (expansion); +#ifdef __CYGWIN__ + /* Fix for Cygwin to prevent ~user/xxx from expanding to //xxx when + $HOME for `user' is /. On cygwin, // denotes a network drive. */ + if (len > 1 || *expansion != '/' || *string != '/') +#endif + { + if ((result_index + len + 1) > result_size) + result = (char *)xrealloc (result, 1 + (result_size += (len + 20))); + + strcpy (result + result_index, expansion); + result_index += len; + } + xfree (expansion); + } + + result[result_index] = '\0'; + + return (result); +} + +/* Take FNAME and return the tilde prefix we want expanded. If LENP is + non-null, the index of the end of the prefix into FNAME is returned in + the location it points to. */ +static char * +isolate_tilde_prefix (const char *fname, int *lenp) +{ + char *ret; + int i; + + ret = (char *)xmalloc (strlen (fname)); +#if defined (__MSDOS__) + for (i = 1; fname[i] && fname[i] != '/' && fname[i] != '\\'; i++) +#else + for (i = 1; fname[i] && fname[i] != '/'; i++) +#endif + ret[i - 1] = fname[i]; + ret[i - 1] = '\0'; + if (lenp) + *lenp = i; + return ret; +} + +#if 0 +/* Public function to scan a string (FNAME) beginning with a tilde and find + the portion of the string that should be passed to the tilde expansion + function. Right now, it just calls tilde_find_suffix and allocates new + memory, but it can be expanded to do different things later. */ +char * +tilde_find_word (const char *fname, int flags, int *lenp) +{ + int x; + char *r; + + x = tilde_find_suffix (fname); + if (x == 0) + { + r = savestring (fname); + if (lenp) + *lenp = 0; + } + else + { + r = (char *)xmalloc (1 + x); + strncpy (r, fname, x); + r[x] = '\0'; + if (lenp) + *lenp = x; + } + + return r; +} +#endif + +/* Return a string that is PREFIX concatenated with SUFFIX starting at + SUFFIND. */ +static char * +glue_prefix_and_suffix (char *prefix, const char *suffix, int suffind) +{ + char *ret; + int plen, slen; + + plen = (prefix && *prefix) ? strlen (prefix) : 0; + slen = strlen (suffix + suffind); + ret = (char *)xmalloc (plen + slen + 1); + if (plen) + strcpy (ret, prefix); + strcpy (ret + plen, suffix + suffind); + return ret; +} + +/* Do the work of tilde expansion on FILENAME. FILENAME starts with a + tilde. If there is no expansion, call tilde_expansion_failure_hook. + This always returns a newly-allocated string, never static storage. */ +char * +tilde_expand_word (const char *filename) +{ + char *dirname, *expansion, *username; + int user_len; + struct passwd *user_entry; + + if (filename == 0) + return ((char *)NULL); + + if (*filename != '~') + return (savestring (filename)); + + /* A leading `~/' or a bare `~' is *always* translated to the value of + $HOME or the home directory of the current user, regardless of any + preexpansion hook. */ + if (filename[1] == '\0' || filename[1] == '/') + { + /* Prefix $HOME to the rest of the string. */ + expansion = sh_get_env_value ("HOME"); +#if defined (_WIN32) + if (expansion == 0) + expansion = sh_get_env_value ("APPDATA"); +#endif + + /* If there is no HOME variable, look up the directory in + the password database. */ + if (expansion == 0) + expansion = sh_get_home_dir (); + + return (glue_prefix_and_suffix (expansion, filename, 1)); + } + + username = isolate_tilde_prefix (filename, &user_len); + + if (tilde_expansion_preexpansion_hook) + { + expansion = (*tilde_expansion_preexpansion_hook) (username); + if (expansion) + { + dirname = glue_prefix_and_suffix (expansion, filename, user_len); + xfree (username); + xfree (expansion); + return (dirname); + } + } + + /* No preexpansion hook, or the preexpansion hook failed. Look in the + password database. */ + dirname = (char *)NULL; +#if defined (HAVE_GETPWNAM) + user_entry = getpwnam (username); +#else + user_entry = 0; +#endif + if (user_entry == 0) + { + /* If the calling program has a special syntax for expanding tildes, + and we couldn't find a standard expansion, then let them try. */ + if (tilde_expansion_failure_hook) + { + expansion = (*tilde_expansion_failure_hook) (username); + if (expansion) + { + dirname = glue_prefix_and_suffix (expansion, filename, user_len); + xfree (expansion); + } + } + /* If we don't have a failure hook, or if the failure hook did not + expand the tilde, return a copy of what we were passed. */ + if (dirname == 0) + dirname = savestring (filename); + } +#if defined (HAVE_GETPWENT) + else + dirname = glue_prefix_and_suffix (user_entry->pw_dir, filename, user_len); +#endif + + xfree (username); +#if defined (HAVE_GETPWENT) + endpwent (); +#endif + return (dirname); +} + + +#if defined (TEST) +#undef NULL +#include + +main (int argc, char **argv) +{ + char *result, line[512]; + int done = 0; + + while (!done) + { + printf ("~expand: "); + fflush (stdout); + + if (!gets (line)) + strcpy (line, "done"); + + if ((strcmp (line, "done") == 0) || + (strcmp (line, "quit") == 0) || + (strcmp (line, "exit") == 0)) + { + done = 1; + break; + } + + result = tilde_expand (line); + printf (" --> %s\n", result); + free (result); + } + exit (0); +} + +static void memory_error_and_abort (void); + +static void * +xmalloc (size_t bytes) +{ + void *temp = (char *)malloc (bytes); + + if (!temp) + memory_error_and_abort (); + return (temp); +} + +static void * +xrealloc (void *pointer, int bytes) +{ + void *temp; + + if (!pointer) + temp = malloc (bytes); + else + temp = realloc (pointer, bytes); + + if (!temp) + memory_error_and_abort (); + + return (temp); +} + +static void +memory_error_and_abort (void) +{ + fprintf (stderr, "readline: out of virtual memory\n"); + abort (); +} + +/* + * Local variables: + * compile-command: "gcc -g -DTEST -o tilde tilde.c" + * end: + */ +#endif /* TEST */ diff --git a/third_party/bash/tilde.h b/third_party/bash/tilde.h new file mode 100644 index 000000000..bc8022afc --- /dev/null +++ b/third_party/bash/tilde.h @@ -0,0 +1,68 @@ +/* tilde.h: Externally available variables and function in libtilde.a. */ + +/* Copyright (C) 1992-2009,2021 Free Software Foundation, Inc. + + This file contains the Readline Library (Readline), a set of + routines for providing Emacs style line input to programs that ask + for it. + + Readline is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Readline is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Readline. If not, see . +*/ + +#if !defined (_TILDE_H_) +# define _TILDE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef char *tilde_hook_func_t (char *); + +/* If non-null, this contains the address of a function that the application + wants called before trying the standard tilde expansions. The function + is called with the text sans tilde, and returns a malloc()'ed string + which is the expansion, or a NULL pointer if the expansion fails. */ +extern tilde_hook_func_t *tilde_expansion_preexpansion_hook; + +/* If non-null, this contains the address of a function to call if the + standard meaning for expanding a tilde fails. The function is called + with the text (sans tilde, as in "foo"), and returns a malloc()'ed string + which is the expansion, or a NULL pointer if there is no expansion. */ +extern tilde_hook_func_t *tilde_expansion_failure_hook; + +/* When non-null, this is a NULL terminated array of strings which + are duplicates for a tilde prefix. Bash uses this to expand + `=~' and `:~'. */ +extern char **tilde_additional_prefixes; + +/* When non-null, this is a NULL terminated array of strings which match + the end of a username, instead of just "/". Bash sets this to + `:' and `=~'. */ +extern char **tilde_additional_suffixes; + +/* Return a new string which is the result of tilde expanding STRING. */ +extern char *tilde_expand (const char *); + +/* Do the work of tilde expansion on FILENAME. FILENAME starts with a + tilde. If there is no expansion, call tilde_expansion_failure_hook. */ +extern char *tilde_expand_word (const char *); + +/* Find the portion of the string beginning with ~ that should be expanded. */ +extern char *tilde_find_word (const char *, int, int *); + +#ifdef __cplusplus +} +#endif + +#endif /* _TILDE_H_ */ diff --git a/third_party/bash/timer.h b/third_party/bash/timer.h new file mode 100644 index 000000000..277947563 --- /dev/null +++ b/third_party/bash/timer.h @@ -0,0 +1,64 @@ +/* timer.h -- data structures used by the shell timers in lib/sh/timers.c */ + +/* Copyright (C) 2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "bashjmp.h" +typedef struct _shtimer +{ + struct timeval tmout; + + int fd; + int flags; + + int alrmflag; /* should be set by alrm_handler */ + + SigHandler *alrm_handler; + SigHandler *old_handler; + + procenv_t jmpenv; + + int (*tm_handler) (struct _shtimer *); /* called on timeout if set */ + PTR_T *data; /* reserved */ +} sh_timer; + +#define SHTIMER_ALARM 0x01 /* mutually exclusive */ +#define SHTIMER_SELECT 0x02 +#define SHTIMER_LONGJMP 0x04 + +#define SHTIMER_SIGSET 0x100 +#define SHTIMER_ALRMSET 0x200 + +extern sh_timer *shtimer_alloc (void); +extern void shtimer_flush (sh_timer *); +extern void shtimer_dispose (sh_timer *); + +extern void shtimer_set (sh_timer *, time_t, long); +extern void shtimer_unset (sh_timer *); + +extern void shtimer_cleanup (sh_timer *); +extern void shtimer_clear (sh_timer *); + +extern int shtimer_chktimeout (sh_timer *); + +extern int shtimer_select (sh_timer *); +extern int shtimer_alrm (sh_timer *); diff --git a/third_party/bash/timers.c b/third_party/bash/timers.c new file mode 100644 index 000000000..69b754c97 --- /dev/null +++ b/third_party/bash/timers.c @@ -0,0 +1,262 @@ +/* timers - functions to manage shell timers */ + +/* Copyright (C) 2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include "posixtime.h" + +#if defined (HAVE_UNISTD_H) +#include +#endif + +#if defined (HAVE_SELECT) +# include "posixselect.h" +# include "stat-time.h" +#endif + +#include "sig.h" +#include "bashjmp.h" +#include "xmalloc.h" + +#include "timer.h" + +#include +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#ifndef FREE +#define FREE(s) do { if (s) free (s); } while (0) +#endif + +extern unsigned int falarm (unsigned int, unsigned int); + +static void shtimer_zero (sh_timer *); + +static void +shtimer_zero (sh_timer *t) +{ + t->tmout.tv_sec = 0; + t->tmout.tv_usec = 0; + + t->fd = -1; + t->flags = t->alrmflag = 0; + + t->alrm_handler = t->old_handler = 0; + + memset (t->jmpenv, '\0', sizeof (t->jmpenv)); + + t->tm_handler = 0; + t->data = 0; +} + +sh_timer * +shtimer_alloc (void) +{ + sh_timer *t; + + t = (sh_timer *)xmalloc (sizeof (sh_timer)); + shtimer_zero (t); + return t; +} + +void +shtimer_flush (sh_timer *t) +{ + /* The caller can manage t->data arbitrarily as long as it frees and sets + t->data to 0 before calling this function. Otherwise, we do what we can + to avoid memleaks. */ + FREE (t->data); + shtimer_zero (t); +} + +void +shtimer_dispose (sh_timer *t) +{ + free (t); +} + +/* We keep the timer as an offset into the future from the time it's set. */ +void +shtimer_set (sh_timer *t, time_t sec, long usec) +{ + struct timeval now; + + if (t->flags & SHTIMER_ALARM) + { + t->alrmflag = 0; /* just paranoia */ + t->old_handler = set_signal_handler (SIGALRM, t->alrm_handler); + t->flags |= SHTIMER_SIGSET; + falarm (t->tmout.tv_sec = sec, t->tmout.tv_usec = usec); + t->flags |= SHTIMER_ALRMSET; + return; + } + + if (gettimeofday (&now, 0) < 0) + timerclear (&now); + + t->tmout.tv_sec = now.tv_sec + sec; + t->tmout.tv_usec = now.tv_usec + usec; + if (t->tmout.tv_usec > USEC_PER_SEC) + { + t->tmout.tv_sec++; + t->tmout.tv_usec -= USEC_PER_SEC; + } +} + +void +shtimer_unset (sh_timer *t) +{ + t->tmout.tv_sec = 0; + t->tmout.tv_usec = 0; + + if (t->flags & SHTIMER_ALARM) + { + t->alrmflag = 0; + if (t->flags & SHTIMER_ALRMSET) + falarm (0, 0); + if (t->old_handler && (t->flags & SHTIMER_SIGSET)) + { + set_signal_handler (SIGALRM, t->old_handler); + t->flags &= ~SHTIMER_SIGSET; + t->old_handler = 0; + } + } +} + +void +shtimer_cleanup (sh_timer *t) +{ + shtimer_unset (t); +} + +void +shtimer_clear (sh_timer *t) +{ + shtimer_unset (t); + shtimer_dispose (t); +} + +int +shtimer_chktimeout (sh_timer *t) +{ + struct timeval now; + int r; + + /* Use the flag to avoid returning sigalrm_seen here */ + if (t->flags & SHTIMER_ALARM) + return t->alrmflag; + + /* Could check a flag for this */ + if (t->tmout.tv_sec == 0 && t->tmout.tv_usec == 0) + return 0; + + if (gettimeofday (&now, 0) < 0) + return 0; + r = ((now.tv_sec > t->tmout.tv_sec) || + (now.tv_sec == t->tmout.tv_sec && now.tv_usec >= t->tmout.tv_usec)); + + return r; +} + +#if defined (HAVE_SELECT) || defined (HAVE_PSELECT) +int +shtimer_select (sh_timer *t) +{ + int r, nfd; + sigset_t blocked_sigs, prevmask; + struct timeval now, tv; + fd_set readfds; +#if defined (HAVE_PSELECT) + struct timespec ts; +#endif + + /* We don't want a SIGCHLD to interrupt this */ + sigemptyset (&blocked_sigs); +# if defined (SIGCHLD) + sigaddset (&blocked_sigs, SIGCHLD); +# endif + + if (gettimeofday (&now, 0) < 0) + { + if (t->flags & SHTIMER_LONGJMP) + sh_longjmp (t->jmpenv, 1); + else + return -1; + } + + /* If the timer has already expired, return immediately */ + if ((now.tv_sec > t->tmout.tv_sec) || + (now.tv_sec == t->tmout.tv_sec && now.tv_usec >= t->tmout.tv_usec)) + { + if (t->flags & SHTIMER_LONGJMP) + sh_longjmp (t->jmpenv, 1); + else if (t->tm_handler) + return ((*t->tm_handler) (t)); + else + return 0; + } + + /* compute timeout */ + tv.tv_sec = t->tmout.tv_sec - now.tv_sec; + tv.tv_usec = t->tmout.tv_usec - now.tv_usec; + if (tv.tv_usec < 0) + { + tv.tv_sec--; + tv.tv_usec += USEC_PER_SEC; + } + +#if defined (HAVE_PSELECT) + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; +#else + sigemptyset (&prevmask); +#endif /* !HAVE_PSELECT */ + + nfd = (t->fd >= 0) ? t->fd + 1 : 0; + FD_ZERO (&readfds); + if (t->fd >= 0) + FD_SET (t->fd, &readfds); + +#if defined (HAVE_PSELECT) + r = pselect(nfd, &readfds, (fd_set *)0, (fd_set *)0, &ts, &blocked_sigs); +#else + sigprocmask (SIG_SETMASK, &blocked_sigs, &prevmask); + r = select(nfd, &readfds, (fd_set *)0, (fd_set *)0, &tv); + sigprocmask (SIG_SETMASK, &prevmask, NULL); +#endif + + if (r < 0) + return r; /* caller will handle */ + else if (r == 0 && (t->flags & SHTIMER_LONGJMP)) + sh_longjmp (t->jmpenv, 1); + else if (r == 0 && t->tm_handler) + return ((*t->tm_handler) (t)); + else + return r; +} +#endif /* !HAVE_TIMEVAL || !HAVE_SELECT */ + +int +shtimer_alrm (sh_timer *t) +{ + return 0; +} diff --git a/third_party/bash/timeval.c b/third_party/bash/timeval.c new file mode 100644 index 000000000..a5a5bc946 --- /dev/null +++ b/third_party/bash/timeval.c @@ -0,0 +1,179 @@ +/* timeval.c - functions to perform operations on struct timevals */ + +/* Copyright (C) 1999 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_TIMEVAL) + +#include +#include "posixtime.h" + +#include "bashintl.h" +#include "stdc.h" + +#ifndef locale_decpoint +extern int locale_decpoint PARAMS((void)); +#endif + +#include + +struct timeval * +difftimeval (d, t1, t2) + struct timeval *d, *t1, *t2; +{ + d->tv_sec = t2->tv_sec - t1->tv_sec; + d->tv_usec = t2->tv_usec - t1->tv_usec; + if (d->tv_usec < 0) + { + d->tv_usec += 1000000; + d->tv_sec -= 1; + if (d->tv_sec < 0) /* ??? -- BSD/OS does this */ + { + d->tv_sec = 0; + d->tv_usec = 0; + } + } + return d; +} + +struct timeval * +addtimeval (d, t1, t2) + struct timeval *d, *t1, *t2; +{ + d->tv_sec = t1->tv_sec + t2->tv_sec; + d->tv_usec = t1->tv_usec + t2->tv_usec; + if (d->tv_usec >= 1000000) + { + d->tv_usec -= 1000000; + d->tv_sec += 1; + } + return d; +} + +struct timeval * +multimeval (d, m) + struct timeval *d; + int m; +{ + time_t t; + + t = d->tv_usec * m; + d->tv_sec = d->tv_sec * m + t / 1000000; + d->tv_usec = t % 1000000; + return d; +} + +struct timeval * +divtimeval (d, m) + struct timeval *d; + int m; +{ + time_t t; + + t = d->tv_sec; + d->tv_sec = t / m; + d->tv_usec = (d->tv_usec + 1000000 * (t % m)) / m; + return d; +} + +/* Do "cpu = ((user + sys) * 10000) / real;" with timevals. + Barely-tested code from Deven T. Corzine . */ +int +timeval_to_cpu (rt, ut, st) + struct timeval *rt, *ut, *st; /* real, user, sys */ +{ + struct timeval t1, t2; + register int i; + + addtimeval (&t1, ut, st); + t2.tv_sec = rt->tv_sec; + t2.tv_usec = rt->tv_usec; + + for (i = 0; i < 6; i++) + { + if ((t1.tv_sec > 99999999) || (t2.tv_sec > 99999999)) + break; + t1.tv_sec *= 10; + t1.tv_sec += t1.tv_usec / 100000; + t1.tv_usec *= 10; + t1.tv_usec %= 1000000; + t2.tv_sec *= 10; + t2.tv_sec += t2.tv_usec / 100000; + t2.tv_usec *= 10; + t2.tv_usec %= 1000000; + } + for (i = 0; i < 4; i++) + { + if (t1.tv_sec < 100000000) + t1.tv_sec *= 10; + else + t2.tv_sec /= 10; + } + + return ((t2.tv_sec == 0) ? 0 : t1.tv_sec / t2.tv_sec); +} + +/* Convert a pointer to a struct timeval to seconds and thousandths of a + second, returning the values in *SP and *SFP, respectively. This does + rounding on the fractional part, not just truncation to three places. */ +void +timeval_to_secs (tvp, sp, sfp) + struct timeval *tvp; + time_t *sp; + int *sfp; +{ + int rest; + + *sp = tvp->tv_sec; + + *sfp = tvp->tv_usec % 1000000; /* pretty much a no-op */ + rest = *sfp % 1000; + *sfp = (*sfp * 1000) / 1000000; + if (rest >= 500) + *sfp += 1; + + /* Sanity check */ + if (*sfp >= 1000) + { + *sp += 1; + *sfp -= 1000; + } +} + +/* Print the contents of a struct timeval * in a standard way to stdio + stream FP. */ +void +print_timeval (fp, tvp) + FILE *fp; + struct timeval *tvp; +{ + time_t timestamp; + long minutes; + int seconds, seconds_fraction; + + timeval_to_secs (tvp, ×tamp, &seconds_fraction); + + minutes = timestamp / 60; + seconds = timestamp % 60; + + fprintf (fp, "%ldm%d%c%03ds", minutes, seconds, locale_decpoint (), seconds_fraction); +} + +#endif /* HAVE_TIMEVAL */ diff --git a/third_party/bash/tmpfile.c b/third_party/bash/tmpfile.c new file mode 100644 index 000000000..8f5cf3f11 --- /dev/null +++ b/third_party/bash/tmpfile.c @@ -0,0 +1,311 @@ +/* + * tmpfile.c - functions to create and safely open temp files for the shell. + */ + +/* Copyright (C) 2000-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" +#include "posixtime.h" +#include "filecntl.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashansi.h" + +#include +#include + +#include "shell.h" + +#ifndef errno +extern int errno; +#endif + +#define BASEOPENFLAGS (O_CREAT | O_TRUNC | O_EXCL | O_BINARY) + +#define DEFAULT_TMPDIR "." /* bogus default, should be changed */ +#define DEFAULT_NAMEROOT "shtmp" + +/* Use ANSI-C rand() interface if random(3) is not available */ +#if !HAVE_RANDOM +#define random() rand() +#endif + +extern pid_t dollar_dollar_pid; + +static char *get_sys_tmpdir PARAMS((void)); +static char *get_tmpdir PARAMS((int)); + +static char *sys_tmpdir = (char *)NULL; +static int ntmpfiles; +static int tmpnamelen = -1; +static unsigned long filenum = 1L; + +static char * +get_sys_tmpdir () +{ + if (sys_tmpdir) + return sys_tmpdir; + +#ifdef P_tmpdir + sys_tmpdir = P_tmpdir; + if (file_iswdir (sys_tmpdir)) + return sys_tmpdir; +#endif + + sys_tmpdir = "/tmp"; + if (file_iswdir (sys_tmpdir)) + return sys_tmpdir; + + sys_tmpdir = "/var/tmp"; + if (file_iswdir (sys_tmpdir)) + return sys_tmpdir; + + sys_tmpdir = "/usr/tmp"; + if (file_iswdir (sys_tmpdir)) + return sys_tmpdir; + + sys_tmpdir = DEFAULT_TMPDIR; + + return sys_tmpdir; +} + +static char * +get_tmpdir (flags) + int flags; +{ + char *tdir; + + tdir = (flags & MT_USETMPDIR) ? get_string_value ("TMPDIR") : (char *)NULL; + if (tdir && (file_iswdir (tdir) == 0 || strlen (tdir) > PATH_MAX)) + tdir = 0; + + if (tdir == 0) + tdir = get_sys_tmpdir (); + +#if defined (HAVE_PATHCONF) && defined (_PC_NAME_MAX) + if (tmpnamelen == -1) + tmpnamelen = pathconf (tdir, _PC_NAME_MAX); +#else + tmpnamelen = 0; +#endif + + return tdir; +} + +static void +sh_seedrand () +{ +#if HAVE_RANDOM + int d; + static int seeded = 0; + if (seeded == 0) + { + struct timeval tv; + + gettimeofday (&tv, NULL); + srandom (tv.tv_sec ^ tv.tv_usec ^ (getpid () << 16) ^ (uintptr_t)&d); + seeded = 1; + } +#endif +} + +char * +sh_mktmpname (nameroot, flags) + char *nameroot; + int flags; +{ + char *filename, *tdir, *lroot; + struct stat sb; + int r, tdlen; + static int seeded = 0; + + filename = (char *)xmalloc (PATH_MAX + 1); + tdir = get_tmpdir (flags); + tdlen = strlen (tdir); + + lroot = nameroot ? nameroot : DEFAULT_NAMEROOT; + if (nameroot == 0) + flags &= ~MT_TEMPLATE; + + if ((flags & MT_TEMPLATE) && strlen (nameroot) > PATH_MAX) + flags &= ~MT_TEMPLATE; + +#ifdef USE_MKTEMP + if (flags & MT_TEMPLATE) + strcpy (filename, nameroot); + else + sprintf (filename, "%s/%s.XXXXXX", tdir, lroot); + if (mktemp (filename) == 0) + { + free (filename); + filename = NULL; + } +#else /* !USE_MKTEMP */ + sh_seedrand (); + while (1) + { + filenum = (filenum << 1) ^ + (unsigned long) time ((time_t *)0) ^ + (unsigned long) dollar_dollar_pid ^ + (unsigned long) ((flags & MT_USERANDOM) ? random () : ntmpfiles++); + sprintf (filename, "%s/%s-%lu", tdir, lroot, filenum); + if (tmpnamelen > 0 && tmpnamelen < 32) + filename[tdlen + 1 + tmpnamelen] = '\0'; +# ifdef HAVE_LSTAT + r = lstat (filename, &sb); +# else + r = stat (filename, &sb); +# endif + if (r < 0 && errno == ENOENT) + break; + } +#endif /* !USE_MKTEMP */ + + return filename; +} + +int +sh_mktmpfd (nameroot, flags, namep) + char *nameroot; + int flags; + char **namep; +{ + char *filename, *tdir, *lroot; + int fd, tdlen; + + filename = (char *)xmalloc (PATH_MAX + 1); + tdir = get_tmpdir (flags); + tdlen = strlen (tdir); + + lroot = nameroot ? nameroot : DEFAULT_NAMEROOT; + if (nameroot == 0) + flags &= ~MT_TEMPLATE; + + if ((flags & MT_TEMPLATE) && strlen (nameroot) > PATH_MAX) + flags &= ~MT_TEMPLATE; + +#ifdef USE_MKSTEMP + if (flags & MT_TEMPLATE) + strcpy (filename, nameroot); + else + sprintf (filename, "%s/%s.XXXXXX", tdir, lroot); + fd = mkstemp (filename); + if (fd < 0 || namep == 0) + { + free (filename); + filename = NULL; + } + if (namep) + *namep = filename; + return fd; +#else /* !USE_MKSTEMP */ + sh_seedrand (); + do + { + filenum = (filenum << 1) ^ + (unsigned long) time ((time_t *)0) ^ + (unsigned long) dollar_dollar_pid ^ + (unsigned long) ((flags & MT_USERANDOM) ? random () : ntmpfiles++); + sprintf (filename, "%s/%s-%lu", tdir, lroot, filenum); + if (tmpnamelen > 0 && tmpnamelen < 32) + filename[tdlen + 1 + tmpnamelen] = '\0'; + fd = open (filename, BASEOPENFLAGS | ((flags & MT_READWRITE) ? O_RDWR : O_WRONLY), 0600); + } + while (fd < 0 && errno == EEXIST); + + if (namep) + *namep = filename; + else + free (filename); + + return fd; +#endif /* !USE_MKSTEMP */ +} + +FILE * +sh_mktmpfp (nameroot, flags, namep) + char *nameroot; + int flags; + char **namep; +{ + int fd; + FILE *fp; + + fd = sh_mktmpfd (nameroot, flags, namep); + if (fd < 0) + return ((FILE *)NULL); + fp = fdopen (fd, (flags & MT_READWRITE) ? "w+" : "w"); + if (fp == 0) + close (fd); + return fp; +} + +char * +sh_mktmpdir (nameroot, flags) + char *nameroot; + int flags; +{ + char *filename, *tdir, *lroot, *dirname; + int fd, tdlen; + +#ifdef USE_MKDTEMP + filename = (char *)xmalloc (PATH_MAX + 1); + tdir = get_tmpdir (flags); + tdlen = strlen (tdir); + + lroot = nameroot ? nameroot : DEFAULT_NAMEROOT; + if (nameroot == 0) + flags &= ~MT_TEMPLATE; + + if ((flags & MT_TEMPLATE) && strlen (nameroot) > PATH_MAX) + flags &= ~MT_TEMPLATE; + + if (flags & MT_TEMPLATE) + strcpy (filename, nameroot); + else + sprintf (filename, "%s/%s.XXXXXX", tdir, lroot); + dirname = mkdtemp (filename); + if (dirname == 0) + { + free (filename); + filename = NULL; + } + return dirname; +#else /* !USE_MKDTEMP */ + filename = (char *)NULL; + do + { + filename = sh_mktmpname (nameroot, flags); + fd = mkdir (filename, 0700); + if (fd == 0) + break; + free (filename); + filename = (char *)NULL; + } + while (fd < 0 && errno == EEXIST); + + return (filename); +#endif /* !USE_MKDTEMP */ +} diff --git a/third_party/bash/trap.c b/third_party/bash/trap.c new file mode 100644 index 000000000..40afd9a78 --- /dev/null +++ b/third_party/bash/trap.c @@ -0,0 +1,1575 @@ +/* trap.c -- Not the trap command, but useful functions for manipulating + those objects. The trap command is in builtins/trap.def. */ + +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "bashtypes.h" +#include "bashansi.h" + +#include +#include + +#include "bashintl.h" + +#include + +#include "trap.h" + +#include "shell.h" +#include "execute_cmd.h" +#include "flags.h" +#include "parser.h" +#include "input.h" /* for save_token_state, restore_token_state */ +#include "jobs.h" +#include "signames.h" +#include "builtins.h" +#include "common.h" +#include "builtext.h" + +#if defined (READLINE) +# include "third_party/readline/readline.h" +# include "bashline.h" +#endif + +#ifndef errno +extern int errno; +#endif + +/* Flags which describe the current handling state of a signal. */ +#define SIG_INHERITED 0x0 /* Value inherited from parent. */ +#define SIG_TRAPPED 0x1 /* Currently trapped. */ +#define SIG_HARD_IGNORE 0x2 /* Signal was ignored on shell entry. */ +#define SIG_SPECIAL 0x4 /* Treat this signal specially. */ +#define SIG_NO_TRAP 0x8 /* Signal cannot be trapped. */ +#define SIG_INPROGRESS 0x10 /* Signal handler currently executing. */ +#define SIG_CHANGED 0x20 /* Trap value changed in trap handler. */ +#define SIG_IGNORED 0x40 /* The signal is currently being ignored. */ + +#define SPECIAL_TRAP(s) ((s) == EXIT_TRAP || (s) == DEBUG_TRAP || (s) == ERROR_TRAP || (s) == RETURN_TRAP) + +/* An array of such flags, one for each signal, describing what the + shell will do with a signal. DEBUG_TRAP == NSIG; some code below + assumes this. */ +static int sigmodes[BASH_NSIG]; + +static void free_trap_command (int); +static void change_signal (int, char *); + +static int _run_trap_internal (int, char *); + +static void free_trap_string (int); +static void reset_signal (int); +static void restore_signal (int); +static void reset_or_restore_signal_handlers (sh_resetsig_func_t *); +static void reinit_trap (int); + +static void trap_if_untrapped (int, char *); + +/* Variables used here but defined in other files. */ + +extern volatile int from_return_trap; +extern int waiting_for_child; + +extern WORD_LIST *subst_assign_varlist; + +/* The list of things to do originally, before we started trapping. */ +SigHandler *original_signals[NSIG]; + +/* For each signal, a slot for a string, which is a command to be + executed when that signal is received. The slot can also contain + DEFAULT_SIG, which means do whatever you were going to do before + you were so rudely interrupted, or IGNORE_SIG, which says ignore + this signal. */ +char *trap_list[BASH_NSIG]; + +/* A bitmap of signals received for which we have trap handlers. */ +int pending_traps[NSIG]; + +/* Set to the number of the signal we're running the trap for + 1. + Used in execute_cmd.c and builtins/common.c to clean up when + parse_and_execute does not return normally after executing the + trap command (e.g., when `return' is executed in the trap command). */ +int running_trap; + +/* Set to last_command_exit_value before running a trap. */ +int trap_saved_exit_value; + +/* The (trapped) signal received while executing in the `wait' builtin */ +int wait_signal_received; + +int trapped_signal_received; + +/* Set to 1 to suppress the effect of `set v' in the DEBUG trap. */ +int suppress_debug_trap_verbose = 0; + +#define GETORIGSIG(sig) \ + do { \ + original_signals[sig] = (SigHandler *)set_signal_handler (sig, SIG_DFL); \ + set_signal_handler (sig, original_signals[sig]); \ + if (original_signals[sig] == SIG_IGN) \ + sigmodes[sig] |= SIG_HARD_IGNORE; \ + } while (0) + +#define SETORIGSIG(sig,handler) \ + do { \ + original_signals[sig] = handler; \ + if (original_signals[sig] == SIG_IGN) \ + sigmodes[sig] |= SIG_HARD_IGNORE; \ + } while (0) + +#define GET_ORIGINAL_SIGNAL(sig) \ + if (sig && sig < NSIG && original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) \ + GETORIGSIG(sig) + +void +initialize_traps () +{ + register int i; + + initialize_signames(); + + trap_list[EXIT_TRAP] = trap_list[DEBUG_TRAP] = trap_list[ERROR_TRAP] = trap_list[RETURN_TRAP] = (char *)NULL; + sigmodes[EXIT_TRAP] = sigmodes[DEBUG_TRAP] = sigmodes[ERROR_TRAP] = sigmodes[RETURN_TRAP] = SIG_INHERITED; + original_signals[EXIT_TRAP] = IMPOSSIBLE_TRAP_HANDLER; + + for (i = 1; i < NSIG; i++) + { + pending_traps[i] = 0; + trap_list[i] = (char *)DEFAULT_SIG; + sigmodes[i] = SIG_INHERITED; /* XXX - only set, not used */ + original_signals[i] = IMPOSSIBLE_TRAP_HANDLER; + } + + /* Show which signals are treated specially by the shell. */ +#if defined (SIGCHLD) + GETORIGSIG (SIGCHLD); + sigmodes[SIGCHLD] |= (SIG_SPECIAL | SIG_NO_TRAP); +#endif /* SIGCHLD */ + + GETORIGSIG (SIGINT); + sigmodes[SIGINT] |= SIG_SPECIAL; + +#if defined (__BEOS__) + /* BeOS sets SIGINT to SIG_IGN! */ + original_signals[SIGINT] = SIG_DFL; + sigmodes[SIGINT] &= ~SIG_HARD_IGNORE; +#endif + + GETORIGSIG (SIGQUIT); + sigmodes[SIGQUIT] |= SIG_SPECIAL; + + if (interactive) + { + GETORIGSIG (SIGTERM); + sigmodes[SIGTERM] |= SIG_SPECIAL; + } + + get_original_tty_job_signals (); +} + +#ifdef DEBUG +/* Return a printable representation of the trap handler for SIG. */ +static char * +trap_handler_string (sig) + int sig; +{ + if (trap_list[sig] == (char *)DEFAULT_SIG) + return "DEFAULT_SIG"; + else if (trap_list[sig] == (char *)IGNORE_SIG) + return "IGNORE_SIG"; + else if (trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER) + return "IMPOSSIBLE_TRAP_HANDLER"; + else if (trap_list[sig]) + return trap_list[sig]; + else + return "NULL"; +} +#endif + +/* Return the print name of this signal. */ +char * +signal_name (sig) + int sig; +{ + char *ret; + + /* on cygwin32, signal_names[sig] could be null */ + ret = (sig >= BASH_NSIG || sig < 0 || signal_names[sig] == NULL) + ? _("invalid signal number") + : signal_names[sig]; + + return ret; +} + +/* Turn a string into a signal number, or a number into + a signal number. If STRING is "2", "SIGINT", or "INT", + then (int)2 is returned. Return NO_SIG if STRING doesn't + contain a valid signal descriptor. */ +int +decode_signal (string, flags) + char *string; + int flags; +{ + intmax_t sig; + char *name; + + if (legal_number (string, &sig)) + return ((sig >= 0 && sig < NSIG) ? (int)sig : NO_SIG); + +#if defined (SIGRTMIN) && defined (SIGRTMAX) + if (STREQN (string, "SIGRTMIN+", 9) || ((flags & DSIG_NOCASE) && strncasecmp (string, "SIGRTMIN+", 9) == 0)) + { + if (legal_number (string+9, &sig) && sig >= 0 && sig <= SIGRTMAX - SIGRTMIN) + return (SIGRTMIN + sig); + else + return NO_SIG; + } + else if (STREQN (string, "RTMIN+", 6) || ((flags & DSIG_NOCASE) && strncasecmp (string, "RTMIN+", 6) == 0)) + { + if (legal_number (string+6, &sig) && sig >= 0 && sig <= SIGRTMAX - SIGRTMIN) + return (SIGRTMIN + sig); + else + return NO_SIG; + } +#endif /* SIGRTMIN && SIGRTMAX */ + + /* A leading `SIG' may be omitted. */ + for (sig = 0; sig < BASH_NSIG; sig++) + { + name = signal_names[sig]; + if (name == 0 || name[0] == '\0') + continue; + + /* Check name without the SIG prefix first case sensitively or + insensitively depending on whether flags includes DSIG_NOCASE */ + if (STREQN (name, "SIG", 3)) + { + name += 3; + + if ((flags & DSIG_NOCASE) && strcasecmp (string, name) == 0) + return ((int)sig); + else if ((flags & DSIG_NOCASE) == 0 && strcmp (string, name) == 0) + return ((int)sig); + /* If we can't use the `SIG' prefix to match, punt on this + name now. */ + else if ((flags & DSIG_SIGPREFIX) == 0) + continue; + } + + /* Check name with SIG prefix case sensitively or insensitively + depending on whether flags includes DSIG_NOCASE */ + name = signal_names[sig]; + if ((flags & DSIG_NOCASE) && strcasecmp (string, name) == 0) + return ((int)sig); + else if ((flags & DSIG_NOCASE) == 0 && strcmp (string, name) == 0) + return ((int)sig); + } + + return (NO_SIG); +} + +/* Non-zero when we catch a trapped signal. */ +static int catch_flag; + +void +run_pending_traps () +{ + register int sig; + int x; + volatile int old_exit_value, old_running; + WORD_LIST *save_subst_varlist; + HASH_TABLE *save_tempenv; + sh_parser_state_t pstate; + volatile int save_return_catch_flag, function_code; + procenv_t save_return_catch; +#if defined (ARRAY_VARS) + ARRAY *ps; +#endif + + if (catch_flag == 0) /* simple optimization */ + return; + + if (running_trap > 0) + { + internal_debug ("run_pending_traps: recursive invocation while running trap for signal %d", running_trap-1); +#if defined (SIGWINCH) + if (running_trap == SIGWINCH+1 && pending_traps[SIGWINCH]) + return; /* no recursive SIGWINCH trap invocations */ +#endif + /* could check for running the trap handler for the same signal here + (running_trap == sig+1) */ + if (evalnest_max > 0 && evalnest > evalnest_max) + { + internal_error (_("trap handler: maximum trap handler level exceeded (%d)"), evalnest_max); + evalnest = 0; + jump_to_top_level (DISCARD); + } + } + + catch_flag = trapped_signal_received = 0; + + /* Preserve $? when running trap. */ + trap_saved_exit_value = old_exit_value = last_command_exit_value; +#if defined (ARRAY_VARS) + ps = save_pipestatus_array (); +#endif + old_running = running_trap; + + for (sig = 1; sig < NSIG; sig++) + { + /* XXX this could be made into a counter by using + while (pending_traps[sig]--) instead of the if statement. */ + if (pending_traps[sig]) + { + /* XXX - set last_command_exit_value = trap_saved_exit_value here? */ + running_trap = sig + 1; + + if (sig == SIGINT) + { + pending_traps[sig] = 0; /* XXX */ + /* We don't modify evalnest here, since run_interrupt_trap() calls + _run_trap_internal, which does. */ + run_interrupt_trap (0); + CLRINTERRUPT; /* interrupts don't stack */ + } +#if defined (JOB_CONTROL) && defined (SIGCHLD) + else if (sig == SIGCHLD && + trap_list[SIGCHLD] != (char *)IMPOSSIBLE_TRAP_HANDLER && + (sigmodes[SIGCHLD] & SIG_INPROGRESS) == 0) + { + sigmodes[SIGCHLD] |= SIG_INPROGRESS; + /* We modify evalnest here even though run_sigchld_trap can run + the trap action more than once */ + evalnest++; + x = pending_traps[sig]; + pending_traps[sig] = 0; + run_sigchld_trap (x); /* use as counter */ + running_trap = 0; + evalnest--; + sigmodes[SIGCHLD] &= ~SIG_INPROGRESS; + /* continue here rather than reset pending_traps[SIGCHLD] below in + case there are recursive calls to run_pending_traps and children + have been reaped while run_sigchld_trap was running. */ + continue; + } + else if (sig == SIGCHLD && + trap_list[SIGCHLD] == (char *)IMPOSSIBLE_TRAP_HANDLER && + (sigmodes[SIGCHLD] & SIG_INPROGRESS) != 0) + { + /* This can happen when run_pending_traps is called while + running a SIGCHLD trap handler. */ + running_trap = 0; + /* want to leave pending_traps[SIGCHLD] alone here */ + continue; /* XXX */ + } + else if (sig == SIGCHLD && (sigmodes[SIGCHLD] & SIG_INPROGRESS)) + { + /* whoops -- print warning? */ + running_trap = 0; /* XXX */ + /* want to leave pending_traps[SIGCHLD] alone here */ + continue; + } +#endif + else if (trap_list[sig] == (char *)DEFAULT_SIG || + trap_list[sig] == (char *)IGNORE_SIG || + trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER) + { + /* This is possible due to a race condition. Say a bash + process has SIGTERM trapped. A subshell is spawned + using { list; } & and the parent does something and kills + the subshell with SIGTERM. It's possible for the subshell + to set pending_traps[SIGTERM] to 1 before the code in + execute_cmd.c eventually calls restore_original_signals + to reset the SIGTERM signal handler in the subshell. The + next time run_pending_traps is called, pending_traps[SIGTERM] + will be 1, but the trap handler in trap_list[SIGTERM] will + be invalid (probably DEFAULT_SIG, but it could be IGNORE_SIG). + Unless we catch this, the subshell will dump core when + trap_list[SIGTERM] == DEFAULT_SIG, because DEFAULT_SIG is + usually 0x0. */ + internal_warning (_("run_pending_traps: bad value in trap_list[%d]: %p"), + sig, trap_list[sig]); + if (trap_list[sig] == (char *)DEFAULT_SIG) + { + internal_warning (_("run_pending_traps: signal handler is SIG_DFL, resending %d (%s) to myself"), sig, signal_name (sig)); + kill (getpid (), sig); + } + } + else + { + save_parser_state (&pstate); + save_subst_varlist = subst_assign_varlist; + subst_assign_varlist = 0; + save_tempenv = temporary_env; + temporary_env = 0; /* traps should not run with temporary env */ + +#if defined (JOB_CONTROL) + save_pipeline (1); /* XXX only provides one save level */ +#endif + /* XXX - set pending_traps[sig] = 0 here? */ + pending_traps[sig] = 0; + evalnest++; + + function_code = 0; + save_return_catch_flag = return_catch_flag; + if (return_catch_flag) + { + COPY_PROCENV (return_catch, save_return_catch); + function_code = setjmp_nosigs (return_catch); + } + + if (function_code == 0) + x = parse_and_execute (savestring (trap_list[sig]), "trap", SEVAL_NONINT|SEVAL_NOHIST|SEVAL_RESETLINE); + else + { + parse_and_execute_cleanup (sig + 1); /* XXX - could use -1 */ + x = return_catch_value; + } + + evalnest--; +#if defined (JOB_CONTROL) + restore_pipeline (1); +#endif + + subst_assign_varlist = save_subst_varlist; + restore_parser_state (&pstate); + temporary_env = save_tempenv; + + if (save_return_catch_flag) + { + return_catch_flag = save_return_catch_flag; + return_catch_value = x; + COPY_PROCENV (save_return_catch, return_catch); + if (function_code) + { + running_trap = old_running; /* XXX */ + /* caller will set last_command_exit_value */ + sh_longjmp (return_catch, 1); + } + } + } + + pending_traps[sig] = 0; /* XXX - move before evalstring? */ + running_trap = old_running; + } + } + +#if defined (ARRAY_VARS) + restore_pipestatus_array (ps); +#endif + last_command_exit_value = old_exit_value; +} + +/* Set the private state variables noting that we received a signal SIG + for which we have a trap set. */ +void +set_trap_state (sig) + int sig; +{ + catch_flag = 1; + pending_traps[sig]++; + trapped_signal_received = sig; +} + +sighandler +trap_handler (sig) + int sig; +{ + int oerrno; + + if ((sigmodes[sig] & SIG_TRAPPED) == 0) + { + internal_debug ("trap_handler: signal %d: signal not trapped", sig); + SIGRETURN (0); + } + + /* This means we're in a subshell, but have not yet reset the handler for + trapped signals. We're not supposed to execute the trap in this situation; + we should restore the original signal and resend the signal to ourselves + to preserve the Posix "signal traps that are not being ignored shall be + set to the default action" semantics. */ + if ((subshell_environment & SUBSHELL_IGNTRAP) && trap_list[sig] != (char *)IGNORE_SIG) + { + sigset_t mask; + + /* Paranoia */ + if (original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) + original_signals[sig] = SIG_DFL; + + restore_signal (sig); + + /* Make sure we let the signal we just caught through */ + sigemptyset (&mask); + sigprocmask (SIG_SETMASK, (sigset_t *)NULL, &mask); + sigdelset (&mask, sig); + sigprocmask (SIG_SETMASK, &mask, (sigset_t *)NULL); + + kill (getpid (), sig); + + SIGRETURN (0); + } + + if ((sig >= NSIG) || + (trap_list[sig] == (char *)DEFAULT_SIG) || + (trap_list[sig] == (char *)IGNORE_SIG)) + programming_error (_("trap_handler: bad signal %d"), sig); + else + { + oerrno = errno; +#if defined (MUST_REINSTALL_SIGHANDLERS) +# if defined (JOB_CONTROL) && defined (SIGCHLD) + if (sig != SIGCHLD) +# endif /* JOB_CONTROL && SIGCHLD */ + set_signal_handler (sig, trap_handler); +#endif /* MUST_REINSTALL_SIGHANDLERS */ + + set_trap_state (sig); + + if (this_shell_builtin && (this_shell_builtin == wait_builtin)) + { + wait_signal_received = sig; + if (waiting_for_child && wait_intr_flag) + sh_longjmp (wait_intr_buf, 1); + } + +#if defined (READLINE) + /* Set the event hook so readline will call it after the signal handlers + finish executing, so if this interrupted character input we can get + quick response. */ + if (RL_ISSTATE (RL_STATE_SIGHANDLER)) + bashline_set_event_hook (); +#endif + + errno = oerrno; + } + + SIGRETURN (0); +} + +int +next_pending_trap (start) + int start; +{ + register int i; + + for (i = start; i < NSIG; i++) + if (pending_traps[i]) + return i; + return -1; +} + +int +first_pending_trap () +{ + return (next_pending_trap (1)); +} + +/* Return > 0 if any of the "real" signals (not fake signals like EXIT) are + trapped. */ +int +any_signals_trapped () +{ + register int i; + + for (i = 1; i < NSIG; i++) + if ((sigmodes[i] & SIG_TRAPPED) && (sigmodes[i] & SIG_IGNORED) == 0) + return i; + return -1; +} + +void +clear_pending_traps () +{ + register int i; + + for (i = 1; i < NSIG; i++) + pending_traps[i] = 0; +} + +void +check_signals () +{ + /* Add any other shell timeouts here */ + check_read_timeout (); /* set by the read builtin */ + QUIT; +} + +/* Convenience functions the rest of the shell can use */ +void +check_signals_and_traps () +{ + check_signals (); + + run_pending_traps (); +} + +#if defined (JOB_CONTROL) && defined (SIGCHLD) + +#ifdef INCLUDE_UNUSED +/* Make COMMAND_STRING be executed when SIGCHLD is caught. */ +void +set_sigchld_trap (command_string) + char *command_string; +{ + set_signal (SIGCHLD, command_string); +} +#endif + +/* Make COMMAND_STRING be executed when SIGCHLD is caught iff SIGCHLD + is not already trapped. IMPOSSIBLE_TRAP_HANDLER is used as a sentinel + to make sure that a SIGCHLD trap handler run via run_sigchld_trap can + reset the disposition to the default and not have the original signal + accidentally restored, undoing the user's command. */ +void +maybe_set_sigchld_trap (command_string) + char *command_string; +{ + if ((sigmodes[SIGCHLD] & SIG_TRAPPED) == 0 && trap_list[SIGCHLD] == (char *)IMPOSSIBLE_TRAP_HANDLER) + set_signal (SIGCHLD, command_string); +} + +/* Temporarily set the SIGCHLD trap string to IMPOSSIBLE_TRAP_HANDLER. Used + as a sentinel in run_sigchld_trap and maybe_set_sigchld_trap to see whether + or not a SIGCHLD trap handler reset SIGCHLD disposition to the default. */ +void +set_impossible_sigchld_trap () +{ + restore_default_signal (SIGCHLD); + change_signal (SIGCHLD, (char *)IMPOSSIBLE_TRAP_HANDLER); + sigmodes[SIGCHLD] &= ~SIG_TRAPPED; /* maybe_set_sigchld_trap checks this */ +} + +/* Act as if we received SIGCHLD NCHILD times and increment + pending_traps[SIGCHLD] by that amount. This allows us to still run the + SIGCHLD trap once for each exited child. */ +void +queue_sigchld_trap (nchild) + int nchild; +{ + if (nchild > 0) + { + catch_flag = 1; + pending_traps[SIGCHLD] += nchild; + trapped_signal_received = SIGCHLD; + } +} +#endif /* JOB_CONTROL && SIGCHLD */ + +/* Set a trap for SIG only if SIG is not already trapped. */ +static inline void +trap_if_untrapped (sig, command) + int sig; + char *command; +{ + if ((sigmodes[sig] & SIG_TRAPPED) == 0) + set_signal (sig, command); +} + +void +set_debug_trap (command) + char *command; +{ + set_signal (DEBUG_TRAP, command); +} + +/* Separate function to call when functions and sourced files want to restore + the original version of the DEBUG trap before returning. Unless the -T + option is set, source and shell function execution save the old debug trap + and unset the trap. If the function or sourced file changes the DEBUG trap, + SIG_TRAPPED will be set and we don't bother restoring the original trap string. + This is used by both functions and the source builtin. */ +void +maybe_set_debug_trap (command) + char *command; +{ + trap_if_untrapped (DEBUG_TRAP, command); +} + +void +set_error_trap (command) + char *command; +{ + set_signal (ERROR_TRAP, command); +} + +void +maybe_set_error_trap (command) + char *command; +{ + trap_if_untrapped (ERROR_TRAP, command); +} + +void +set_return_trap (command) + char *command; +{ + set_signal (RETURN_TRAP, command); +} + +void +maybe_set_return_trap (command) + char *command; +{ + trap_if_untrapped (RETURN_TRAP, command); +} + +#ifdef INCLUDE_UNUSED +void +set_sigint_trap (command) + char *command; +{ + set_signal (SIGINT, command); +} +#endif + +/* Reset the SIGINT handler so that subshells that are doing `shellsy' + things, like waiting for command substitution or executing commands + in explicit subshells ( ( cmd ) ), can catch interrupts properly. */ +SigHandler * +set_sigint_handler () +{ + if (sigmodes[SIGINT] & SIG_HARD_IGNORE) + return ((SigHandler *)SIG_IGN); + + else if (sigmodes[SIGINT] & SIG_IGNORED) + return ((SigHandler *)set_signal_handler (SIGINT, SIG_IGN)); /* XXX */ + + else if (sigmodes[SIGINT] & SIG_TRAPPED) + return ((SigHandler *)set_signal_handler (SIGINT, trap_handler)); + + /* The signal is not trapped, so set the handler to the shell's special + interrupt handler. */ + else if (interactive) /* XXX - was interactive_shell */ + return (set_signal_handler (SIGINT, sigint_sighandler)); + else + return (set_signal_handler (SIGINT, termsig_sighandler)); +} + +/* Return the correct handler for signal SIG according to the values in + sigmodes[SIG]. */ +SigHandler * +trap_to_sighandler (sig) + int sig; +{ + if (sigmodes[sig] & (SIG_IGNORED|SIG_HARD_IGNORE)) + return (SIG_IGN); + else if (sigmodes[sig] & SIG_TRAPPED) + return (trap_handler); + else + return (SIG_DFL); +} + +/* Set SIG to call STRING as a command. */ +void +set_signal (sig, string) + int sig; + char *string; +{ + sigset_t set, oset; + + if (SPECIAL_TRAP (sig)) + { + change_signal (sig, savestring (string)); + if (sig == EXIT_TRAP && interactive == 0) + initialize_terminating_signals (); + return; + } + + /* A signal ignored on entry to the shell cannot be trapped or reset, but + no error is reported when attempting to do so. -- Posix.2 */ + if (sigmodes[sig] & SIG_HARD_IGNORE) + return; + + /* Make sure we have original_signals[sig] if the signal has not yet + been trapped. */ + if ((sigmodes[sig] & SIG_TRAPPED) == 0) + { + /* If we aren't sure of the original value, check it. */ + if (original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) + GETORIGSIG (sig); + if (original_signals[sig] == SIG_IGN) + return; + } + + /* Only change the system signal handler if SIG_NO_TRAP is not set. + The trap command string is changed in either case. The shell signal + handlers for SIGINT and SIGCHLD run the user specified traps in an + environment in which it is safe to do so. */ + if ((sigmodes[sig] & SIG_NO_TRAP) == 0) + { + BLOCK_SIGNAL (sig, set, oset); + change_signal (sig, savestring (string)); + set_signal_handler (sig, trap_handler); + UNBLOCK_SIGNAL (oset); + } + else + change_signal (sig, savestring (string)); +} + +static void +free_trap_command (sig) + int sig; +{ + if ((sigmodes[sig] & SIG_TRAPPED) && trap_list[sig] && + (trap_list[sig] != (char *)IGNORE_SIG) && + (trap_list[sig] != (char *)DEFAULT_SIG) && + (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER)) + free (trap_list[sig]); +} + +/* If SIG has a string assigned to it, get rid of it. Then give it + VALUE. */ +static void +change_signal (sig, value) + int sig; + char *value; +{ + if ((sigmodes[sig] & SIG_INPROGRESS) == 0) + free_trap_command (sig); + trap_list[sig] = value; + + sigmodes[sig] |= SIG_TRAPPED; + if (value == (char *)IGNORE_SIG) + sigmodes[sig] |= SIG_IGNORED; + else + sigmodes[sig] &= ~SIG_IGNORED; + if (sigmodes[sig] & SIG_INPROGRESS) + sigmodes[sig] |= SIG_CHANGED; +} + +void +get_original_signal (sig) + int sig; +{ + /* If we aren't sure the of the original value, then get it. */ + if (sig > 0 && sig < NSIG && original_signals[sig] == (SigHandler *)IMPOSSIBLE_TRAP_HANDLER) + GETORIGSIG (sig); +} + +void +get_all_original_signals () +{ + register int i; + + for (i = 1; i < NSIG; i++) + GET_ORIGINAL_SIGNAL (i); +} + +void +set_original_signal (sig, handler) + int sig; + SigHandler *handler; +{ + if (sig > 0 && sig < NSIG && original_signals[sig] == (SigHandler *)IMPOSSIBLE_TRAP_HANDLER) + SETORIGSIG (sig, handler); +} + +/* Restore the default action for SIG; i.e., the action the shell + would have taken before you used the trap command. This is called + from trap_builtin (), which takes care to restore the handlers for + the signals the shell treats specially. */ +void +restore_default_signal (sig) + int sig; +{ + if (SPECIAL_TRAP (sig)) + { + if ((sig != DEBUG_TRAP && sig != ERROR_TRAP && sig != RETURN_TRAP) || + (sigmodes[sig] & SIG_INPROGRESS) == 0) + free_trap_command (sig); + trap_list[sig] = (char *)NULL; + sigmodes[sig] &= ~SIG_TRAPPED; + if (sigmodes[sig] & SIG_INPROGRESS) + sigmodes[sig] |= SIG_CHANGED; + return; + } + + GET_ORIGINAL_SIGNAL (sig); + + /* A signal ignored on entry to the shell cannot be trapped or reset, but + no error is reported when attempting to do so. Thanks Posix.2. */ + if (sigmodes[sig] & SIG_HARD_IGNORE) + return; + + /* If we aren't trapping this signal, don't bother doing anything else. */ + /* We special-case SIGCHLD and IMPOSSIBLE_TRAP_HANDLER (see above) as a + sentinel to determine whether or not disposition is reset to the default + while the trap handler is executing. */ + if (((sigmodes[sig] & SIG_TRAPPED) == 0) && + (sig != SIGCHLD || (sigmodes[sig] & SIG_INPROGRESS) == 0 || trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER)) + return; + + /* Only change the signal handler for SIG if it allows it. */ + if ((sigmodes[sig] & SIG_NO_TRAP) == 0) + set_signal_handler (sig, original_signals[sig]); + + /* Change the trap command in either case. */ + change_signal (sig, (char *)DEFAULT_SIG); + + /* Mark the signal as no longer trapped. */ + sigmodes[sig] &= ~SIG_TRAPPED; +} + +/* Make this signal be ignored. */ +void +ignore_signal (sig) + int sig; +{ + if (SPECIAL_TRAP (sig) && ((sigmodes[sig] & SIG_IGNORED) == 0)) + { + change_signal (sig, (char *)IGNORE_SIG); + return; + } + + GET_ORIGINAL_SIGNAL (sig); + + /* A signal ignored on entry to the shell cannot be trapped or reset. + No error is reported when the user attempts to do so. */ + if (sigmodes[sig] & SIG_HARD_IGNORE) + return; + + /* If already trapped and ignored, no change necessary. */ + if (sigmodes[sig] & SIG_IGNORED) + return; + + /* Only change the signal handler for SIG if it allows it. */ + if ((sigmodes[sig] & SIG_NO_TRAP) == 0) + set_signal_handler (sig, SIG_IGN); + + /* Change the trap command in either case. */ + change_signal (sig, (char *)IGNORE_SIG); +} + +/* Handle the calling of "trap 0". The only sticky situation is when + the command to be executed includes an "exit". This is why we have + to provide our own place for top_level to jump to. */ +int +run_exit_trap () +{ + char *trap_command; + int code, function_code, retval; +#if defined (ARRAY_VARS) + ARRAY *ps; +#endif + + trap_saved_exit_value = last_command_exit_value; +#if defined (ARRAY_VARS) + ps = save_pipestatus_array (); +#endif + function_code = 0; + + /* Run the trap only if signal 0 is trapped and not ignored, and we are not + currently running in the trap handler (call to exit in the list of + commands given to trap 0). */ + if ((sigmodes[EXIT_TRAP] & SIG_TRAPPED) && + (sigmodes[EXIT_TRAP] & (SIG_IGNORED|SIG_INPROGRESS)) == 0) + { + trap_command = savestring (trap_list[EXIT_TRAP]); + sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED; + sigmodes[EXIT_TRAP] |= SIG_INPROGRESS; + + retval = trap_saved_exit_value; + running_trap = 1; + + code = setjmp_nosigs (top_level); + + /* If we're in a function, make sure return longjmps come here, too. */ + if (return_catch_flag) + function_code = setjmp_nosigs (return_catch); + + if (code == 0 && function_code == 0) + { + reset_parser (); + parse_and_execute (trap_command, "exit trap", SEVAL_NONINT|SEVAL_NOHIST|SEVAL_RESETLINE); + } + else if (code == ERREXIT) + retval = last_command_exit_value; + else if (code == EXITPROG || code == EXITBLTIN) + retval = last_command_exit_value; + else if (function_code != 0) + retval = return_catch_value; + else + retval = trap_saved_exit_value; + + running_trap = 0; +#if defined (ARRAY_VARS) + array_dispose (ps); +#endif + + return retval; + } + +#if defined (ARRAY_VARS) + restore_pipestatus_array (ps); +#endif + return (trap_saved_exit_value); +} + +void +run_trap_cleanup (sig) + int sig; +{ + /* XXX - should we clean up trap_list[sig] == IMPOSSIBLE_TRAP_HANDLER? */ + sigmodes[sig] &= ~(SIG_INPROGRESS|SIG_CHANGED); +} + +#define RECURSIVE_SIG(s) (SPECIAL_TRAP(s) == 0) + +/* Run a trap command for SIG. SIG is one of the signals the shell treats + specially. Returns the exit status of the executed trap command list. */ +static int +_run_trap_internal (sig, tag) + int sig; + char *tag; +{ + char *trap_command, *old_trap; + int trap_exit_value; + volatile int save_return_catch_flag, function_code; + int old_modes, old_running, old_int; + int flags; + procenv_t save_return_catch; + WORD_LIST *save_subst_varlist; + HASH_TABLE *save_tempenv; + sh_parser_state_t pstate; +#if defined (ARRAY_VARS) + ARRAY *ps; +#endif + + old_modes = old_running = -1; + + trap_exit_value = function_code = 0; + trap_saved_exit_value = last_command_exit_value; + /* Run the trap only if SIG is trapped and not ignored, and we are not + currently executing in the trap handler. */ + if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0) && + (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER) && +#if 1 + /* Uncomment this to allow some special signals to recursively execute + trap handlers. */ + (RECURSIVE_SIG (sig) || (sigmodes[sig] & SIG_INPROGRESS) == 0)) +#else + ((sigmodes[sig] & SIG_INPROGRESS) == 0)) +#endif + { + old_trap = trap_list[sig]; + old_modes = sigmodes[sig]; + old_running = running_trap; + + sigmodes[sig] |= SIG_INPROGRESS; + sigmodes[sig] &= ~SIG_CHANGED; /* just to be sure */ + trap_command = savestring (old_trap); + + running_trap = sig + 1; + + old_int = interrupt_state; /* temporarily suppress pending interrupts */ + CLRINTERRUPT; + +#if defined (ARRAY_VARS) + ps = save_pipestatus_array (); +#endif + + save_parser_state (&pstate); + save_subst_varlist = subst_assign_varlist; + subst_assign_varlist = 0; + save_tempenv = temporary_env; + temporary_env = 0; /* traps should not run with temporary env */ + +#if defined (JOB_CONTROL) + if (sig != DEBUG_TRAP) /* run_debug_trap does this */ + save_pipeline (1); /* XXX only provides one save level */ +#endif + + /* If we're in a function, make sure return longjmps come here, too. */ + save_return_catch_flag = return_catch_flag; + if (return_catch_flag) + { + COPY_PROCENV (return_catch, save_return_catch); + function_code = setjmp_nosigs (return_catch); + } + + flags = SEVAL_NONINT|SEVAL_NOHIST; + if (sig != DEBUG_TRAP && sig != RETURN_TRAP && sig != ERROR_TRAP) + flags |= SEVAL_RESETLINE; + evalnest++; + if (function_code == 0) + { + parse_and_execute (trap_command, tag, flags); + trap_exit_value = last_command_exit_value; + } + else + trap_exit_value = return_catch_value; + evalnest--; + +#if defined (JOB_CONTROL) + if (sig != DEBUG_TRAP) /* run_debug_trap does this */ + restore_pipeline (1); +#endif + + subst_assign_varlist = save_subst_varlist; + restore_parser_state (&pstate); + +#if defined (ARRAY_VARS) + restore_pipestatus_array (ps); +#endif + + temporary_env = save_tempenv; + + if ((old_modes & SIG_INPROGRESS) == 0) + sigmodes[sig] &= ~SIG_INPROGRESS; + + running_trap = old_running; + interrupt_state = old_int; + + if (sigmodes[sig] & SIG_CHANGED) + { +#if 0 + /* Special traps like EXIT, DEBUG, RETURN are handled explicitly in + the places where they can be changed using unwind-protects. For + example, look at execute_cmd.c:execute_function(). */ + if (SPECIAL_TRAP (sig) == 0) +#endif + free (old_trap); + sigmodes[sig] &= ~SIG_CHANGED; + + CHECK_TERMSIG; /* some pathological conditions lead here */ + } + + if (save_return_catch_flag) + { + return_catch_flag = save_return_catch_flag; + return_catch_value = trap_exit_value; + COPY_PROCENV (save_return_catch, return_catch); + if (function_code) + { +#if 0 + from_return_trap = sig == RETURN_TRAP; +#endif + sh_longjmp (return_catch, 1); + } + } + } + + return trap_exit_value; +} + +int +run_debug_trap () +{ + int trap_exit_value, old_verbose; + pid_t save_pgrp; +#if defined (PGRP_PIPE) + int save_pipe[2]; +#endif + + /* XXX - question: should the DEBUG trap inherit the RETURN trap? */ + trap_exit_value = 0; + if ((sigmodes[DEBUG_TRAP] & SIG_TRAPPED) && ((sigmodes[DEBUG_TRAP] & SIG_IGNORED) == 0) && ((sigmodes[DEBUG_TRAP] & SIG_INPROGRESS) == 0)) + { +#if defined (JOB_CONTROL) + save_pgrp = pipeline_pgrp; + pipeline_pgrp = 0; + save_pipeline (1); +# if defined (PGRP_PIPE) + save_pgrp_pipe (save_pipe, 1); +# endif + stop_making_children (); +#endif + + old_verbose = echo_input_at_read; + echo_input_at_read = suppress_debug_trap_verbose ? 0 : echo_input_at_read; + + trap_exit_value = _run_trap_internal (DEBUG_TRAP, "debug trap"); + + echo_input_at_read = old_verbose; + +#if defined (JOB_CONTROL) + pipeline_pgrp = save_pgrp; + restore_pipeline (1); +# if defined (PGRP_PIPE) + close_pgrp_pipe (); + restore_pgrp_pipe (save_pipe); +# endif + if (pipeline_pgrp > 0 && ((subshell_environment & (SUBSHELL_ASYNC|SUBSHELL_PIPE)) == 0)) + give_terminal_to (pipeline_pgrp, 1); + + notify_and_cleanup (); +#endif + +#if defined (DEBUGGER) + /* If we're in the debugger and the DEBUG trap returns 2 while we're in + a function or sourced script, we force a `return'. */ + if (debugging_mode && trap_exit_value == 2 && return_catch_flag) + { + return_catch_value = trap_exit_value; + sh_longjmp (return_catch, 1); + } +#endif + } + return trap_exit_value; +} + +void +run_error_trap () +{ + if ((sigmodes[ERROR_TRAP] & SIG_TRAPPED) && ((sigmodes[ERROR_TRAP] & SIG_IGNORED) == 0) && (sigmodes[ERROR_TRAP] & SIG_INPROGRESS) == 0) + _run_trap_internal (ERROR_TRAP, "error trap"); +} + +void +run_return_trap () +{ + int old_exit_value; + +#if 0 + if ((sigmodes[DEBUG_TRAP] & SIG_TRAPPED) && (sigmodes[DEBUG_TRAP] & SIG_INPROGRESS)) + return; +#endif + + if ((sigmodes[RETURN_TRAP] & SIG_TRAPPED) && ((sigmodes[RETURN_TRAP] & SIG_IGNORED) == 0) && (sigmodes[RETURN_TRAP] & SIG_INPROGRESS) == 0) + { + old_exit_value = last_command_exit_value; + _run_trap_internal (RETURN_TRAP, "return trap"); + last_command_exit_value = old_exit_value; + } +} + +/* Run a trap set on SIGINT. This is called from throw_to_top_level (), and + declared here to localize the trap functions. */ +void +run_interrupt_trap (will_throw) + int will_throw; /* from throw_to_top_level? */ +{ + if (will_throw && running_trap > 0) + run_trap_cleanup (running_trap - 1); + pending_traps[SIGINT] = 0; /* run_pending_traps does this */ + catch_flag = 0; + _run_trap_internal (SIGINT, "interrupt trap"); +} + +/* Free all the allocated strings in the list of traps and reset the trap + values to the default. Intended to be called from subshells that want + to complete work done by reset_signal_handlers upon execution of a + subsequent `trap' command that changes a signal's disposition. We need + to make sure that we duplicate the behavior of + reset_or_restore_signal_handlers and not change the disposition of signals + that are set to be ignored. */ +void +free_trap_strings () +{ + register int i; + + for (i = 0; i < NSIG; i++) + { + if (trap_list[i] != (char *)IGNORE_SIG) + free_trap_string (i); + } + for (i = NSIG; i < BASH_NSIG; i++) + { + /* Don't free the trap string if the subshell inherited the trap */ + if ((sigmodes[i] & SIG_TRAPPED) == 0) + { + free_trap_string (i); + trap_list[i] = (char *)NULL; + } + } +} + +/* Free a trap command string associated with SIG without changing signal + disposition. Intended to be called from free_trap_strings() */ +static void +free_trap_string (sig) + int sig; +{ + change_signal (sig, (char *)DEFAULT_SIG); + sigmodes[sig] &= ~SIG_TRAPPED; /* XXX - SIG_INPROGRESS? */ +} + +/* Reset the handler for SIG to the original value but leave the trap string + in place. */ +static void +reset_signal (sig) + int sig; +{ + set_signal_handler (sig, original_signals[sig]); + sigmodes[sig] &= ~SIG_TRAPPED; /* XXX - SIG_INPROGRESS? */ +} + +/* Set the handler signal SIG to the original and free any trap + command associated with it. */ +static void +restore_signal (sig) + int sig; +{ + set_signal_handler (sig, original_signals[sig]); + change_signal (sig, (char *)DEFAULT_SIG); + sigmodes[sig] &= ~SIG_TRAPPED; +} + +static void +reset_or_restore_signal_handlers (reset) + sh_resetsig_func_t *reset; +{ + register int i; + + /* Take care of the exit trap first */ + if (sigmodes[EXIT_TRAP] & SIG_TRAPPED) + { + sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED; /* XXX - SIG_INPROGRESS? */ + if (reset != reset_signal) + { + free_trap_command (EXIT_TRAP); + trap_list[EXIT_TRAP] = (char *)NULL; + } + } + + for (i = 1; i < NSIG; i++) + { + if (sigmodes[i] & SIG_TRAPPED) + { + if (trap_list[i] == (char *)IGNORE_SIG) + set_signal_handler (i, SIG_IGN); + else + (*reset) (i); + } + else if (sigmodes[i] & SIG_SPECIAL) + (*reset) (i); + pending_traps[i] = 0; /* XXX */ + } + + /* Command substitution and other child processes don't inherit the + debug, error, or return traps. If we're in the debugger, and the + `functrace' or `errtrace' options have been set, then let command + substitutions inherit them. Let command substitution inherit the + RETURN trap if we're in the debugger and tracing functions. */ + if (function_trace_mode == 0) + { + sigmodes[DEBUG_TRAP] &= ~SIG_TRAPPED; + sigmodes[RETURN_TRAP] &= ~SIG_TRAPPED; + } + if (error_trace_mode == 0) + sigmodes[ERROR_TRAP] &= ~SIG_TRAPPED; +} + +/* Reset trapped signals to their original values, but don't free the + trap strings. Called by the command substitution code and other places + that create a "subshell environment". */ +void +reset_signal_handlers () +{ + reset_or_restore_signal_handlers (reset_signal); +} + +/* Reset all trapped signals to their original values. Signals set to be + ignored with trap '' SIGNAL should be ignored, so we make sure that they + are. Called by child processes after they are forked. */ +void +restore_original_signals () +{ + reset_or_restore_signal_handlers (restore_signal); +} + +/* Change the flags associated with signal SIG without changing the trap + string. The string is TRAP_LIST[SIG] if we need it. */ +static void +reinit_trap (sig) + int sig; +{ + sigmodes[sig] |= SIG_TRAPPED; + if (trap_list[sig] == (char *)IGNORE_SIG) + sigmodes[sig] |= SIG_IGNORED; + else + sigmodes[sig] &= ~SIG_IGNORED; + if (sigmodes[sig] & SIG_INPROGRESS) + sigmodes[sig] |= SIG_CHANGED; +} + +/* Undo the effects of reset_signal_handlers(), which unsets the traps but + leaves the trap strings in place. This understands how reset_signal_handlers + works. */ +void +restore_traps () +{ + char *trapstr; + int i; + + /* Take care of the exit trap first. If TRAP_LIST[0] is non-null, the trap + has been set. */ + trapstr = trap_list[EXIT_TRAP]; + if (trapstr) + reinit_trap (EXIT_TRAP); + + /* Then DEBUG, RETURN, and ERROR. TRAP_LIST[N] == 0 if these signals are + not trapped. This knows what reset_signal_handlers does for these traps */ + trapstr = trap_list[DEBUG_TRAP]; + if (trapstr && function_trace_mode == 0) + reinit_trap (DEBUG_TRAP); + trapstr = trap_list[RETURN_TRAP]; + if (trapstr && function_trace_mode == 0) + reinit_trap (RETURN_TRAP); + trapstr = trap_list[ERROR_TRAP]; + if (trapstr && error_trace_mode == 0) + reinit_trap (ERROR_TRAP); + + /* And finally all the `real' signals. reset_signal_handlers just changes the + signal handler for these signals, leaving the trap value in place. We + intuit what to do based on that value. We assume that signals marked as + SIG_SPECIAL are reinitialized by initialize_signals (), so we don't + change the signal handler unless the signal is supposed to be ignored. */ + for (i = 1; i < NSIG; i++) + { + trapstr = trap_list[i]; + if (sigmodes[i] & SIG_SPECIAL) + { + if (trapstr && trapstr != (char *)DEFAULT_SIG) + reinit_trap (i); + if (trapstr == (char *)IGNORE_SIG && (sigmodes[i] & SIG_NO_TRAP) == 0) + set_signal_handler (i, SIG_IGN); + } + else if (trapstr == (char *)IGNORE_SIG) + { + reinit_trap (i); + if ((sigmodes[i] & SIG_NO_TRAP) == 0) + set_signal_handler (i, SIG_IGN); + } + else if (trapstr != (char *)DEFAULT_SIG) + /* set_signal duplicates the string argument before freeing it. */ + set_signal (i, trapstr); + + pending_traps[i] = 0; /* XXX */ + } +} + +/* If a trap handler exists for signal SIG, then call it; otherwise just + return failure. Returns 1 if it called the trap handler. */ +int +maybe_call_trap_handler (sig) + int sig; +{ + /* Call the trap handler for SIG if the signal is trapped and not ignored. */ + if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0)) + { + switch (sig) + { + case SIGINT: + run_interrupt_trap (0); + break; + case EXIT_TRAP: + run_exit_trap (); + break; + case DEBUG_TRAP: + run_debug_trap (); + break; + case ERROR_TRAP: + run_error_trap (); + break; + default: + trap_handler (sig); + break; + } + return (1); + } + else + return (0); +} + +int +signal_is_trapped (sig) + int sig; +{ + return (sigmodes[sig] & SIG_TRAPPED); +} + +int +signal_is_pending (sig) + int sig; +{ + return (pending_traps[sig]); +} + +int +signal_is_special (sig) + int sig; +{ + return (sigmodes[sig] & SIG_SPECIAL); +} + +int +signal_is_ignored (sig) + int sig; +{ + return (sigmodes[sig] & SIG_IGNORED); +} + +int +signal_is_hard_ignored (sig) + int sig; +{ + return (sigmodes[sig] & SIG_HARD_IGNORE); +} + +void +set_signal_hard_ignored (sig) + int sig; +{ + sigmodes[sig] |= SIG_HARD_IGNORE; + original_signals[sig] = SIG_IGN; +} + +void +set_signal_ignored (sig) + int sig; +{ + original_signals[sig] = SIG_IGN; +} + +int +signal_in_progress (sig) + int sig; +{ + return (sigmodes[sig] & SIG_INPROGRESS); +} + +#if 0 /* unused */ +int +block_trapped_signals (maskp, omaskp) + sigset_t *maskp; + sigset_t *omaskp; +{ + int i; + + sigemptyset (maskp); + for (i = 1; i < NSIG; i++) + if (sigmodes[i] & SIG_TRAPPED) + sigaddset (maskp, i); + return (sigprocmask (SIG_BLOCK, maskp, omaskp)); +} + +int +unblock_trapped_signals (maskp) + sigset_t *maskp; +{ + return (sigprocmask (SIG_SETMASK, maskp, 0)); +} +#endif diff --git a/third_party/bash/trap.h b/third_party/bash/trap.h new file mode 100644 index 000000000..89402a1bc --- /dev/null +++ b/third_party/bash/trap.h @@ -0,0 +1,129 @@ +/* trap.h -- data structures used in the trap mechanism. */ + +/* Copyright (C) 1993-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_TRAP_H_) +#define _TRAP_H_ + +#include "stdc.h" + +#if !defined (SIG_DFL) +#include "bashtypes.h" +#include +#endif /* SIG_DFL */ + +#if !defined (NSIG) +#define NSIG 64 +#endif /* !NSIG */ + +#define NO_SIG -1 +#define DEFAULT_SIG SIG_DFL +#define IGNORE_SIG SIG_IGN + +/* Special shell trap names. */ +#define DEBUG_TRAP NSIG +#define ERROR_TRAP NSIG+1 +#define RETURN_TRAP NSIG+2 +#define EXIT_TRAP 0 + +/* system signals plus special bash traps */ +#define BASH_NSIG NSIG+3 + +/* Flags values for decode_signal() */ +#define DSIG_SIGPREFIX 0x01 /* don't allow `SIG' PREFIX */ +#define DSIG_NOCASE 0x02 /* case-insensitive comparison */ + +/* A value which can never be the target of a trap handler. */ +#define IMPOSSIBLE_TRAP_HANDLER (SigHandler *)initialize_traps + +#define signal_object_p(x,f) (decode_signal (x,f) != NO_SIG) + +#define TRAP_STRING(s) \ + (signal_is_trapped (s) && signal_is_ignored (s) == 0) ? trap_list[s] \ + : (char *)NULL + +extern char *trap_list[]; + +extern int trapped_signal_received; +extern int wait_signal_received; +extern int running_trap; +extern int trap_saved_exit_value; +extern int suppress_debug_trap_verbose; + +/* Externally-visible functions declared in trap.c. */ +extern void initialize_traps PARAMS((void)); + +extern void run_pending_traps PARAMS((void)); + +extern void queue_sigchld_trap PARAMS((int)); +extern void maybe_set_sigchld_trap PARAMS((char *)); +extern void set_impossible_sigchld_trap PARAMS((void)); +extern void set_sigchld_trap PARAMS((char *)); + +extern void set_debug_trap PARAMS((char *)); +extern void set_error_trap PARAMS((char *)); +extern void set_return_trap PARAMS((char *)); + +extern void maybe_set_debug_trap PARAMS((char *)); +extern void maybe_set_error_trap PARAMS((char *)); +extern void maybe_set_return_trap PARAMS((char *)); + +extern void set_sigint_trap PARAMS((char *)); +extern void set_signal PARAMS((int, char *)); + +extern void restore_default_signal PARAMS((int)); +extern void ignore_signal PARAMS((int)); +extern int run_exit_trap PARAMS((void)); +extern void run_trap_cleanup PARAMS((int)); +extern int run_debug_trap PARAMS((void)); +extern void run_error_trap PARAMS((void)); +extern void run_return_trap PARAMS((void)); + +extern void free_trap_strings PARAMS((void)); +extern void reset_signal_handlers PARAMS((void)); +extern void restore_original_signals PARAMS((void)); +extern void restore_traps PARAMS((void)); + +extern void get_original_signal PARAMS((int)); +extern void get_all_original_signals PARAMS((void)); + +extern char *signal_name PARAMS((int)); + +extern int decode_signal PARAMS((char *, int)); +extern void run_interrupt_trap PARAMS((int)); +extern int maybe_call_trap_handler PARAMS((int)); +extern int signal_is_special PARAMS((int)); +extern int signal_is_trapped PARAMS((int)); +extern int signal_is_pending PARAMS((int)); +extern int signal_is_ignored PARAMS((int)); +extern int signal_is_hard_ignored PARAMS((int)); +extern void set_signal_hard_ignored PARAMS((int)); +extern void set_signal_ignored PARAMS((int)); +extern int signal_in_progress PARAMS((int)); + +extern void set_trap_state PARAMS((int)); + +extern int next_pending_trap PARAMS((int)); +extern int first_pending_trap PARAMS((void)); +extern void clear_pending_traps PARAMS((void)); +extern int any_signals_trapped PARAMS((void)); +extern void check_signals PARAMS((void)); +extern void check_signals_and_traps PARAMS((void)); + +#endif /* _TRAP_H_ */ diff --git a/third_party/bash/typemax.h b/third_party/bash/typemax.h new file mode 100644 index 000000000..e3b98f472 --- /dev/null +++ b/third_party/bash/typemax.h @@ -0,0 +1,141 @@ +/* typemax.h -- encapsulate max values for long, long long, etc. */ + +/* Copyright (C) 2001-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* + * NOTE: This should be included after config.h, limits.h, stdint.h, and + * inttypes.h + */ + +#ifndef _SH_TYPEMAX_H +#define _SH_TYPEMAX_H + +#ifndef CHAR_BIT +# define CHAR_BIT 8 +#endif + +/* Nonzero if the integer type T is signed. */ +#ifndef TYPE_SIGNED +# define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) +#endif + +#ifndef TYPE_SIGNED_MAGNITUDE +# define TYPE_SIGNED_MAGNITUDE(t) ((t) ~ (t) 0 < (t) -1) +#endif + +#ifndef TYPE_WIDTH +# define TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT) +#endif + +#ifndef TYPE_MINIMUM +# define TYPE_MINIMUM(t) ((t) ~ TYPE_MAXIMUM (t)) +#endif + +#ifndef TYPE_MAXIMUM +# define TYPE_MAXIMUM(t) \ + ((t) (! TYPE_SIGNED (t) \ + ? (t) -1 \ + : ((((t) 1 << (TYPE_WIDTH (t) - 2)) - 1) * 2 + 1))) +#endif + +#ifdef HAVE_LONG_LONG_INT +# ifndef LLONG_MAX +# define LLONG_MAX TYPE_MAXIMUM(long long int) +# define LLONG_MIN TYPE_MINIMUM(long long int) +# endif +# ifndef ULLONG_MAX +# define ULLONG_MAX TYPE_MAXIMUM(unsigned long long int) +# endif +#endif + +#ifndef ULONG_MAX +# define ULONG_MAX ((unsigned long) ~(unsigned long) 0) +#endif + +#ifndef LONG_MAX +# define LONG_MAX ((long int) (ULONG_MAX >> 1)) +# define LONG_MIN ((long int) (-LONG_MAX - 1L)) +#endif + +#ifndef INT_MAX /* ouch */ +# define INT_MAX TYPE_MAXIMUM(int) +# define INT_MIN TYPE_MINIMUM(int) +# define UINT_MAX ((unsigned int) ~(unsigned int)0) +#endif + +#ifndef SHRT_MAX +# define SHRT_MAX TYPE_MAXIMUM(short) +# define SHRT_MIN TYPE_MINIMUM(short) +# define USHRT_MAX ((unsigned short) ~(unsigned short)0) +#endif + +#ifndef UCHAR_MAX +# define UCHAR_MAX 255 +#endif + +/* workaround for gcc bug in versions < 2.7 */ +#if defined (HAVE_LONG_LONG_INT) && __GNUC__ == 2 && __GNUC_MINOR__ < 7 +static const unsigned long long int maxquad = ULLONG_MAX; +# undef ULLONG_MAX +# define ULLONG_MAX maxquad +#endif + +#if !defined (INTMAX_MAX) || !defined (INTMAX_MIN) + +#if SIZEOF_INTMAX_T == SIZEOF_LONG_LONG +# define INTMAX_MAX LLONG_MAX +# define INTMAX_MIN LLONG_MIN +#elif SIZEOF_INTMAX_T == SIZEOF_LONG +# define INTMAX_MAX LONG_MAX +# define INTMAX_MIN LONG_MIN +#else +# define INTMAX_MAX INT_MAX +# define INTMAX_MIN INT_MIN +#endif + +#endif + +#ifndef SSIZE_MAX +# define SSIZE_MAX INT_MAX +#endif + +#ifndef SIZE_MAX +# define SIZE_MAX ((size_t) ~(size_t)0) +#endif + +#ifndef sh_imaxabs +# define sh_imaxabs(x) (((x) >= 0) ? (x) : -(x)) +#endif + +/* Handle signed arithmetic overflow and underflow. Have to do it this way + to avoid compilers optimizing out simpler overflow checks. */ + +/* Make sure that a+b does not exceed MAXV or is smaller than MINV (if b < 0). + Assumes that b > 0 if a > 0 and b < 0 if a < 0 */ +#define ADDOVERFLOW(a,b,minv,maxv) \ + ((((a) > 0) && ((b) > ((maxv) - (a)))) || \ + (((a) < 0) && ((b) < ((minv) - (a))))) + +/* Make sure that a-b is not smaller than MINV or exceeds MAXV (if b < 0). + Assumes that b > 0 if a > 0 and b < 0 if a < 0 */ +#define SUBOVERFLOW(a,b,minv,maxv) \ + ((((b) > 0) && ((a) < ((minv) + (b)))) || \ + (((b) < 0) && ((a) > ((maxv) + (b))))) + +#endif /* _SH_TYPEMAX_H */ diff --git a/third_party/bash/uconvert.c b/third_party/bash/uconvert.c new file mode 100644 index 000000000..457552eb4 --- /dev/null +++ b/third_party/bash/uconvert.c @@ -0,0 +1,124 @@ +/* uconvert - convert string representations of decimal numbers into whole + number/fractional value pairs. */ + +/* Copyright (C) 2008,2009,2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#include "posixtime.h" + +#if defined (HAVE_UNISTD_H) +#include +#endif + +#include +#include "chartypes.h" + +#include "shell.h" +#include "builtins.h" + +#define DECIMAL '.' /* XXX - should use locale */ + +#define RETURN(x) \ +do { \ + if (ip) *ip = ipart * mult; \ + if (up) *up = upart; \ + if (ep) *ep = p; \ + return (x); \ +} while (0) + +/* + * An incredibly simplistic floating point converter. + */ +static int multiplier[7] = { 1, 100000, 10000, 1000, 100, 10, 1 }; + +/* Take a decimal number int-part[.[micro-part]] and convert it to the whole + and fractional portions. The fractional portion is returned in + millionths (micro); callers are responsible for multiplying appropriately. + EP, if non-null, gets the address of the character where conversion stops. + Return 1 if value converted; 0 if invalid integer for either whole or + fractional parts. */ +int +uconvert(s, ip, up, ep) + char *s; + long *ip, *up; + char **ep; +{ + int n, mult; + long ipart, upart; + char *p; + + ipart = upart = 0; + mult = 1; + + if (s && (*s == '-' || *s == '+')) + { + mult = (*s == '-') ? -1 : 1; + p = s + 1; + } + else + p = s; + + for ( ; p && *p; p++) + { + if (*p == DECIMAL) /* decimal point */ + break; + if (DIGIT(*p) == 0) + RETURN(0); + ipart = (ipart * 10) + (*p - '0'); + } + + if (p == 0 || *p == 0) /* callers ensure p can never be 0; this is to shut up clang */ + RETURN(1); + + if (*p == DECIMAL) + p++; + + /* Look for up to six digits past a decimal point. */ + for (n = 0; n < 6 && p[n]; n++) + { + if (DIGIT(p[n]) == 0) + { + if (ep) + { + upart *= multiplier[n]; + p += n; /* To set EP */ + } + RETURN(0); + } + upart = (upart * 10) + (p[n] - '0'); + } + + /* Now convert to millionths */ + upart *= multiplier[n]; + + if (n == 6 && p[6] >= '5' && p[6] <= '9') + upart++; /* round up 1 */ + + if (ep) + { + p += n; + while (DIGIT(*p)) + p++; + } + + RETURN(1); +} diff --git a/third_party/bash/ufuncs.c b/third_party/bash/ufuncs.c new file mode 100644 index 000000000..4dc4853b1 --- /dev/null +++ b/third_party/bash/ufuncs.c @@ -0,0 +1,140 @@ +/* ufuncs - sleep and alarm functions that understand fractional values */ + +/* Copyright (C) 2008,2009-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" + +#include "posixtime.h" + +#if defined (HAVE_UNISTD_H) +#include +#endif + +#include +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#if defined (HAVE_SELECT) +# include "posixselect.h" +# include "quit.h" +# include "trap.h" +# include "stat-time.h" +#endif + +/* A version of `alarm' using setitimer if it's available. */ + +#if defined (HAVE_SETITIMER) +unsigned int +falarm(secs, usecs) + unsigned int secs, usecs; +{ + struct itimerval it, oit; + + it.it_interval.tv_sec = 0; + it.it_interval.tv_usec = 0; + + it.it_value.tv_sec = secs; + it.it_value.tv_usec = usecs; + + if (setitimer(ITIMER_REAL, &it, &oit) < 0) + return (-1); /* XXX will be converted to unsigned */ + + /* Backwards compatibility with alarm(3) */ + if (oit.it_value.tv_usec) + oit.it_value.tv_sec++; + return (oit.it_value.tv_sec); +} +#else +int +falarm (secs, usecs) + unsigned int secs, usecs; +{ + if (secs == 0 && usecs == 0) + return (alarm (0)); + + if (secs == 0 || usecs >= 500000) + { + secs++; + usecs = 0; + } + return (alarm (secs)); +} +#endif /* !HAVE_SETITIMER */ + +/* A version of sleep using fractional seconds and select. I'd like to use + `usleep', but it's already taken */ + +#if defined (HAVE_TIMEVAL) && (defined (HAVE_SELECT) || defined (HAVE_PSELECT)) +int +fsleep(sec, usec) + unsigned int sec, usec; +{ + int e, r; + sigset_t blocked_sigs, prevmask; +#if defined (HAVE_PSELECT) + struct timespec ts; +#else + struct timeval tv; +#endif + + sigemptyset (&blocked_sigs); +# if defined (SIGCHLD) + sigaddset (&blocked_sigs, SIGCHLD); +# endif + +#if defined (HAVE_PSELECT) + ts.tv_sec = sec; + ts.tv_nsec = usec * 1000; +#else + sigemptyset (&prevmask); + tv.tv_sec = sec; + tv.tv_usec = usec; +#endif /* !HAVE_PSELECT */ + + do + { +#if defined (HAVE_PSELECT) + r = pselect(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &ts, &blocked_sigs); +#else + sigprocmask (SIG_SETMASK, &blocked_sigs, &prevmask); + r = select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv); + sigprocmask (SIG_SETMASK, &prevmask, NULL); +#endif + e = errno; + if (r < 0 && errno == EINTR) + return -1; /* caller will handle */ + errno = e; + } + while (r < 0 && errno == EINTR); + + return r; +} +#else /* !HAVE_TIMEVAL || !HAVE_SELECT */ +int +fsleep(sec, usec) + long sec, usec; +{ + if (usec >= 500000) /* round */ + sec++; + return (sleep(sec)); +} +#endif /* !HAVE_TIMEVAL || !HAVE_SELECT */ diff --git a/third_party/bash/unicode.c b/third_party/bash/unicode.c new file mode 100644 index 000000000..a79ec4aab --- /dev/null +++ b/third_party/bash/unicode.c @@ -0,0 +1,339 @@ +/* unicode.c - functions to convert unicode characters */ + +/* Copyright (C) 2010-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#if defined (HANDLE_MULTIBYTE) + +#include "stdc.h" +#include +#include "bashansi.h" +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +#if HAVE_ICONV +# include +#endif + +#include "xmalloc.h" + +#ifndef USHORT_MAX +# ifdef USHRT_MAX +# define USHORT_MAX USHRT_MAX +# else +# define USHORT_MAX ((unsigned short) ~(unsigned short)0) +# endif +#endif + +#if !defined (STREQ) +# define STREQ(a, b) ((a)[0] == (b)[0] && strcmp ((a), (b)) == 0) +#endif /* !STREQ */ + +#if defined (HAVE_LOCALE_CHARSET) +extern const char *locale_charset PARAMS((void)); +#else +extern char *get_locale_var PARAMS((char *)); +#endif + +extern int locale_utf8locale; + +static int u32init = 0; +static int utf8locale = 0; +#if defined (HAVE_ICONV) +static iconv_t localconv; +#endif + +#ifndef HAVE_LOCALE_CHARSET +static char charsetbuf[40]; + +static char * +stub_charset () +{ + char *locale, *s, *t; + + locale = get_locale_var ("LC_CTYPE"); + if (locale == 0 || *locale == 0) + { + strcpy (charsetbuf, "ASCII"); + return charsetbuf; + } + s = strrchr (locale, '.'); + if (s) + { + strncpy (charsetbuf, s+1, sizeof (charsetbuf) - 1); + charsetbuf[sizeof (charsetbuf) - 1] = '\0'; + t = strchr (charsetbuf, '@'); + if (t) + *t = 0; + return charsetbuf; + } + strncpy (charsetbuf, locale, sizeof (charsetbuf) - 1); + charsetbuf[sizeof (charsetbuf) - 1] = '\0'; + return charsetbuf; +} +#endif + +void +u32reset () +{ +#if defined (HAVE_ICONV) + if (u32init && localconv != (iconv_t)-1) + { + iconv_close (localconv); + localconv = (iconv_t)-1; + } +#endif + u32init = 0; + utf8locale = 0; +} + +/* u32toascii ? */ +int +u32tochar (x, s) + unsigned long x; + char *s; +{ + int l; + + l = (x <= UCHAR_MAX) ? 1 : ((x <= USHORT_MAX) ? 2 : 4); + + if (x <= UCHAR_MAX) + s[0] = x & 0xFF; + else if (x <= USHORT_MAX) /* assume unsigned short = 16 bits */ + { + s[0] = (x >> 8) & 0xFF; + s[1] = x & 0xFF; + } + else + { + s[0] = (x >> 24) & 0xFF; + s[1] = (x >> 16) & 0xFF; + s[2] = (x >> 8) & 0xFF; + s[3] = x & 0xFF; + } + s[l] = '\0'; + return l; +} + +int +u32tocesc (wc, s) + u_bits32_t wc; + char *s; +{ + int l; + + if (wc < 0x10000) + l = sprintf (s, "\\u%04X", wc); + else + l = sprintf (s, "\\U%08X", wc); + return l; +} + +/* Convert unsigned 32-bit int to utf-8 character string */ +int +u32toutf8 (wc, s) + u_bits32_t wc; + char *s; +{ + int l; + + if (wc < 0x0080) + { + s[0] = (char)wc; + l = 1; + } + else if (wc < 0x0800) + { + s[0] = (wc >> 6) | 0xc0; + s[1] = (wc & 0x3f) | 0x80; + l = 2; + } + else if (wc < 0x10000) + { + /* Technically, we could return 0 here if 0xd800 <= wc <= 0x0dfff */ + s[0] = (wc >> 12) | 0xe0; + s[1] = ((wc >> 6) & 0x3f) | 0x80; + s[2] = (wc & 0x3f) | 0x80; + l = 3; + } + else if (wc < 0x200000) + { + s[0] = (wc >> 18) | 0xf0; + s[1] = ((wc >> 12) & 0x3f) | 0x80; + s[2] = ((wc >> 6) & 0x3f) | 0x80; + s[3] = (wc & 0x3f) | 0x80; + l = 4; + } + /* Strictly speaking, UTF-8 doesn't have characters longer than 4 bytes */ + else if (wc < 0x04000000) + { + s[0] = (wc >> 24) | 0xf8; + s[1] = ((wc >> 18) & 0x3f) | 0x80; + s[2] = ((wc >> 12) & 0x3f) | 0x80; + s[3] = ((wc >> 6) & 0x3f) | 0x80; + s[4] = (wc & 0x3f) | 0x80; + l = 5; + } + else if (wc < 0x080000000) + { + s[0] = (wc >> 30) | 0xfc; + s[1] = ((wc >> 24) & 0x3f) | 0x80; + s[2] = ((wc >> 18) & 0x3f) | 0x80; + s[3] = ((wc >> 12) & 0x3f) | 0x80; + s[4] = ((wc >> 6) & 0x3f) | 0x80; + s[5] = (wc & 0x3f) | 0x80; + l = 6; + } + else + l = 0; + + s[l] = '\0'; + return l; +} + +/* Convert a 32-bit unsigned int (unicode) to a UTF-16 string. Rarely used, + only if sizeof(wchar_t) == 2. */ +int +u32toutf16 (c, s) + u_bits32_t c; + wchar_t *s; +{ + int l; + + l = 0; + if (c < 0x0d800 || (c >= 0x0e000 && c <= 0x0ffff)) + { + s[0] = (wchar_t) (c & 0xFFFF); + l = 1; + } + else if (c >= 0x10000 && c <= 0x010ffff) + { + c -= 0x010000; + s[0] = (wchar_t)((c >> 10) + 0xd800); + s[1] = (wchar_t)((c & 0x3ff) + 0xdc00); + l = 2; + } + s[l] = 0; + return l; +} + +/* convert a single unicode-32 character into a multibyte string and put the + result in S, which must be large enough (at least max(10,MB_LEN_MAX) bytes) */ +int +u32cconv (c, s) + unsigned long c; + char *s; +{ + wchar_t wc; + wchar_t ws[3]; + int n; +#if HAVE_ICONV + const char *charset; + char obuf[25], *optr; + size_t obytesleft; + const char *iptr; + size_t sn; +#endif + +#if __STDC_ISO_10646__ + wc = c; + if (sizeof (wchar_t) == 4 && c <= 0x7fffffff) + n = wctomb (s, wc); + else if (sizeof (wchar_t) == 2 && c <= 0x10ffff && u32toutf16 (c, ws)) + n = wcstombs (s, ws, MB_LEN_MAX); + else + n = -1; + if (n != -1) + return n; +#endif + +#if HAVE_ICONV + /* this is mostly from coreutils-8.5/lib/unicodeio.c */ + if (u32init == 0) + { + utf8locale = locale_utf8locale; + localconv = (iconv_t)-1; + if (utf8locale == 0) + { +#if HAVE_LOCALE_CHARSET + charset = locale_charset (); +#elif HAVE_NL_LANGINFO + charset = nl_langinfo (CODESET); +#else + charset = stub_charset (); +#endif + localconv = iconv_open (charset, "UTF-8"); + if (localconv == (iconv_t)-1) + /* We assume ASCII when presented with an unknown encoding. */ + localconv = iconv_open ("ASCII", "UTF-8"); + } + u32init = 1; + } + + /* NL_LANGINFO and locale_charset used when setting locale_utf8locale */ + + /* If we have a UTF-8 locale, convert to UTF-8 and return converted value. */ + n = u32toutf8 (c, s); + if (utf8locale) + return n; + + /* If the conversion is not supported, even the ASCII requested above, we + bail now. Currently we return the UTF-8 conversion. We could return + u32tocesc(). */ + if (localconv == (iconv_t)-1) + return n; + + optr = obuf; + obytesleft = sizeof (obuf); + iptr = s; + sn = n; + + iconv (localconv, NULL, NULL, NULL, NULL); + + if (iconv (localconv, (ICONV_CONST char **)&iptr, &sn, &optr, &obytesleft) == (size_t)-1) + { + /* You get ISO C99 escape sequences if iconv fails */ + n = u32tocesc (c, s); + return n; + } + + *optr = '\0'; + + /* number of chars to be copied is optr - obuf if we want to do bounds + checking */ + strcpy (s, obuf); + return (optr - obuf); +#endif /* HAVE_ICONV */ + + if (locale_utf8locale) + n = u32toutf8 (c, s); + else + n = u32tocesc (c, s); /* fallback is ISO C99 escape sequences */ + return n; +} +#else +void +u32reset () +{ +} +#endif /* HANDLE_MULTIBYTE */ diff --git a/third_party/bash/unionwait.h b/third_party/bash/unionwait.h new file mode 100644 index 000000000..b1b4dfaff --- /dev/null +++ b/third_party/bash/unionwait.h @@ -0,0 +1,98 @@ +/* unionwait.h -- definitions for using a `union wait' on systems without + one. */ + +/* Copyright (C) 1996 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#ifndef _UNIONWAIT_H +#define _UNIONWAIT_H + +#if !defined (WORDS_BIGENDIAN) +union wait + { + int w_status; /* used in syscall */ + + /* Terminated process status. */ + struct + { + unsigned short + w_Termsig : 7, /* termination signal */ + w_Coredump : 1, /* core dump indicator */ + w_Retcode : 8, /* exit code if w_termsig==0 */ + w_Fill1 : 16; /* high 16 bits unused */ + } w_T; + + /* Stopped process status. Returned + only for traced children unless requested + with the WUNTRACED option bit. */ + struct + { + unsigned short + w_Stopval : 8, /* == W_STOPPED if stopped */ + w_Stopsig : 8, /* actually zero on XENIX */ + w_Fill2 : 16; /* high 16 bits unused */ + } w_S; + }; + +#else /* WORDS_BIGENDIAN */ + +/* This is for big-endian machines like the IBM RT, HP 9000, or Sun-3 */ + +union wait + { + int w_status; /* used in syscall */ + + /* Terminated process status. */ + struct + { + unsigned short w_Fill1 : 16; /* high 16 bits unused */ + unsigned w_Retcode : 8; /* exit code if w_termsig==0 */ + unsigned w_Coredump : 1; /* core dump indicator */ + unsigned w_Termsig : 7; /* termination signal */ + } w_T; + + /* Stopped process status. Returned + only for traced children unless requested + with the WUNTRACED option bit. */ + struct + { + unsigned short w_Fill2 : 16; /* high 16 bits unused */ + unsigned w_Stopsig : 8; /* signal that stopped us */ + unsigned w_Stopval : 8; /* == W_STOPPED if stopped */ + } w_S; + }; + +#endif /* WORDS_BIGENDIAN */ + +#define w_termsig w_T.w_Termsig +#define w_coredump w_T.w_Coredump +#define w_retcode w_T.w_Retcode +#define w_stopval w_S.w_Stopval +#define w_stopsig w_S.w_Stopsig + +#define WSTOPPED 0177 +#define WIFSTOPPED(x) ((x).w_stopval == WSTOPPED) +#define WIFEXITED(x) ((x).w_stopval != WSTOPPED && (x).w_termsig == 0) +#define WIFSIGNALED(x) ((x).w_stopval != WSTOPPED && (x).w_termsig != 0) + +#define WTERMSIG(x) ((x).w_termsig) +#define WSTOPSIG(x) ((x).w_stopsig) +#define WEXITSTATUS(x) ((x).w_retcode) +#define WIFCORED(x) ((x).w_coredump) + +#endif /* _UNIONWAIT_H */ diff --git a/third_party/bash/unwind_prot.c b/third_party/bash/unwind_prot.c new file mode 100644 index 000000000..ec82393d5 --- /dev/null +++ b/third_party/bash/unwind_prot.c @@ -0,0 +1,383 @@ +/* unwind_prot.c - a simple unwind-protect system for internal variables */ + +/* I can't stand it anymore! Please can't we just write the + whole Unix system in lisp or something? */ + +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* **************************************************************** */ +/* */ +/* Unwind Protection Scheme for Bash */ +/* */ +/* **************************************************************** */ +#include "config.h" + +#include "bashtypes.h" +#include "bashansi.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_STDDEF_H) +# include +#endif + +#ifndef offsetof +# define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif + +#include "command.h" +#include "general.h" +#include "unwind_prot.h" +#include "sig.h" +#include "quit.h" +#include "bashintl.h" /* for _() */ +#include "error.h" /* for internal_warning */ +#include "ocache.h" + +/* Structure describing a saved variable and the value to restore it to. */ +typedef struct { + char *variable; + int size; + char desired_setting[1]; /* actual size is `size' */ +} SAVED_VAR; + +/* If HEAD.CLEANUP is null, then ARG.V contains a tag to throw back to. + If HEAD.CLEANUP is restore_variable, then SV.V contains the saved + variable. Otherwise, call HEAD.CLEANUP (ARG.V) to clean up. */ +typedef union uwp { + struct uwp_head { + union uwp *next; + Function *cleanup; + } head; + struct { + struct uwp_head uwp_head; + char *v; + } arg; + struct { + struct uwp_head uwp_head; + SAVED_VAR v; + } sv; +} UNWIND_ELT; + +static void without_interrupts PARAMS((VFunction *, char *, char *)); +static void unwind_frame_discard_internal PARAMS((char *, char *)); +static void unwind_frame_run_internal PARAMS((char *, char *)); +static void add_unwind_protect_internal PARAMS((Function *, char *)); +static void remove_unwind_protect_internal PARAMS((char *, char *)); +static void run_unwind_protects_internal PARAMS((char *, char *)); +static void clear_unwind_protects_internal PARAMS((char *, char *)); +static inline void restore_variable PARAMS((SAVED_VAR *)); +static void unwind_protect_mem_internal PARAMS((char *, char *)); + +static UNWIND_ELT *unwind_protect_list = (UNWIND_ELT *)NULL; + +/* Allocating from a cache of unwind-protect elements */ +#define UWCACHESIZE 128 + +sh_obj_cache_t uwcache = {0, 0, 0}; + +#if 0 +#define uwpalloc(elt) (elt) = (UNWIND_ELT *)xmalloc (sizeof (UNWIND_ELT)) +#define uwpfree(elt) free(elt) +#else +#define uwpalloc(elt) ocache_alloc (uwcache, UNWIND_ELT, elt) +#define uwpfree(elt) ocache_free (uwcache, UNWIND_ELT, elt) +#endif + +void +uwp_init () +{ + ocache_create (uwcache, UNWIND_ELT, UWCACHESIZE); +} + +/* Run a function without interrupts. This relies on the fact that the + FUNCTION cannot call QUIT (). */ +static void +without_interrupts (function, arg1, arg2) + VFunction *function; + char *arg1, *arg2; +{ + (*function)(arg1, arg2); +} + +/* Start the beginning of a region. */ +void +begin_unwind_frame (tag) + char *tag; +{ + add_unwind_protect ((Function *)NULL, tag); +} + +/* Discard the unwind protects back to TAG. */ +void +discard_unwind_frame (tag) + char *tag; +{ + if (unwind_protect_list) + without_interrupts (unwind_frame_discard_internal, tag, (char *)NULL); +} + +/* Run the unwind protects back to TAG. */ +void +run_unwind_frame (tag) + char *tag; +{ + if (unwind_protect_list) + without_interrupts (unwind_frame_run_internal, tag, (char *)NULL); +} + +/* Add the function CLEANUP with ARG to the list of unwindable things. */ +void +add_unwind_protect (cleanup, arg) + Function *cleanup; + char *arg; +{ + without_interrupts (add_unwind_protect_internal, (char *)cleanup, arg); +} + +/* Remove the top unwind protect from the list. */ +void +remove_unwind_protect () +{ + if (unwind_protect_list) + without_interrupts + (remove_unwind_protect_internal, (char *)NULL, (char *)NULL); +} + +/* Run the list of cleanup functions in unwind_protect_list. */ +void +run_unwind_protects () +{ + if (unwind_protect_list) + without_interrupts + (run_unwind_protects_internal, (char *)NULL, (char *)NULL); +} + +/* Erase the unwind-protect list. If flags is 1, free the elements. */ +void +clear_unwind_protect_list (flags) + int flags; +{ + char *flag; + + if (unwind_protect_list) + { + flag = flags ? "" : (char *)NULL; + without_interrupts + (clear_unwind_protects_internal, flag, (char *)NULL); + } +} + +int +have_unwind_protects () +{ + return (unwind_protect_list != 0); +} + +int +unwind_protect_tag_on_stack (tag) + const char *tag; +{ + UNWIND_ELT *elt; + + elt = unwind_protect_list; + while (elt) + { + if (elt->head.cleanup == 0 && STREQ (elt->arg.v, tag)) + return 1; + elt = elt->head.next; + } + return 0; +} + +/* **************************************************************** */ +/* */ +/* The Actual Functions */ +/* */ +/* **************************************************************** */ + +static void +add_unwind_protect_internal (cleanup, arg) + Function *cleanup; + char *arg; +{ + UNWIND_ELT *elt; + + uwpalloc (elt); + elt->head.next = unwind_protect_list; + elt->head.cleanup = cleanup; + elt->arg.v = arg; + unwind_protect_list = elt; +} + +static void +remove_unwind_protect_internal (ignore1, ignore2) + char *ignore1, *ignore2; +{ + UNWIND_ELT *elt; + + elt = unwind_protect_list; + if (elt) + { + unwind_protect_list = unwind_protect_list->head.next; + uwpfree (elt); + } +} + +static void +run_unwind_protects_internal (ignore1, ignore2) + char *ignore1, *ignore2; +{ + unwind_frame_run_internal ((char *) NULL, (char *) NULL); +} + +static void +clear_unwind_protects_internal (flag, ignore) + char *flag, *ignore; +{ + if (flag) + { + while (unwind_protect_list) + remove_unwind_protect_internal ((char *)NULL, (char *)NULL); + } + unwind_protect_list = (UNWIND_ELT *)NULL; +} + +static void +unwind_frame_discard_internal (tag, ignore) + char *tag, *ignore; +{ + UNWIND_ELT *elt; + int found; + + found = 0; + while (elt = unwind_protect_list) + { + unwind_protect_list = unwind_protect_list->head.next; + if (elt->head.cleanup == 0 && (STREQ (elt->arg.v, tag))) + { + uwpfree (elt); + found = 1; + break; + } + else + uwpfree (elt); + } + + if (found == 0) + internal_warning (_("unwind_frame_discard: %s: frame not found"), tag); +} + +/* Restore the value of a variable, based on the contents of SV. + sv->desired_setting is a block of memory SIZE bytes long holding the + value itself. This block of memory is copied back into the variable. */ +static inline void +restore_variable (sv) + SAVED_VAR *sv; +{ + FASTCOPY (sv->desired_setting, sv->variable, sv->size); +} + +static void +unwind_frame_run_internal (tag, ignore) + char *tag, *ignore; +{ + UNWIND_ELT *elt; + int found; + + found = 0; + while (elt = unwind_protect_list) + { + unwind_protect_list = elt->head.next; + + /* If tag, then compare. */ + if (elt->head.cleanup == 0) + { + if (tag && STREQ (elt->arg.v, tag)) + { + uwpfree (elt); + found = 1; + break; + } + } + else + { + if (elt->head.cleanup == (Function *) restore_variable) + restore_variable (&elt->sv.v); + else + (*(elt->head.cleanup)) (elt->arg.v); + } + + uwpfree (elt); + } + if (tag && found == 0) + internal_warning (_("unwind_frame_run: %s: frame not found"), tag); +} + +static void +unwind_protect_mem_internal (var, psize) + char *var; + char *psize; +{ + int size, allocated; + UNWIND_ELT *elt; + + size = *(int *) psize; + allocated = size + offsetof (UNWIND_ELT, sv.v.desired_setting[0]); + if (allocated < sizeof (UNWIND_ELT)) + allocated = sizeof (UNWIND_ELT); + elt = (UNWIND_ELT *)xmalloc (allocated); + elt->head.next = unwind_protect_list; + elt->head.cleanup = (Function *) restore_variable; + elt->sv.v.variable = var; + elt->sv.v.size = size; + FASTCOPY (var, elt->sv.v.desired_setting, size); + unwind_protect_list = elt; +} + +/* Save the value of a variable so it will be restored when unwind-protects + are run. VAR is a pointer to the variable. SIZE is the size in + bytes of VAR. */ +void +unwind_protect_mem (var, size) + char *var; + int size; +{ + without_interrupts (unwind_protect_mem_internal, var, (char *) &size); +} + +#if defined (DEBUG) +#include + +void +print_unwind_protect_tags () +{ + UNWIND_ELT *elt; + + elt = unwind_protect_list; + while (elt) + { + if (elt->head.cleanup == 0) + fprintf(stderr, "tag: %s\n", elt->arg.v); + elt = elt->head.next; + } +} +#endif diff --git a/third_party/bash/unwind_prot.h b/third_party/bash/unwind_prot.h new file mode 100644 index 000000000..97b3af902 --- /dev/null +++ b/third_party/bash/unwind_prot.h @@ -0,0 +1,52 @@ +/* unwind_prot.h - Macros and functions for hacking unwind protection. */ + +/* Copyright (C) 1993-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_UNWIND_PROT_H) +#define _UNWIND_PROT_H + +extern void uwp_init PARAMS((void)); + +/* Run a function without interrupts. */ +extern void begin_unwind_frame PARAMS((char *)); +extern void discard_unwind_frame PARAMS((char *)); +extern void run_unwind_frame PARAMS((char *)); +extern void add_unwind_protect (); /* Not portable to arbitrary C99 hosts. */ +extern void remove_unwind_protect PARAMS((void)); +extern void run_unwind_protects PARAMS((void)); +extern void clear_unwind_protect_list PARAMS((int)); +extern int have_unwind_protects PARAMS((void)); +extern int unwind_protect_tag_on_stack PARAMS((const char *)); +extern void uwp_init PARAMS((void)); + +/* Define for people who like their code to look a certain way. */ +#define end_unwind_frame() + +/* How to protect a variable. */ +#define unwind_protect_var(X) unwind_protect_mem ((char *)&(X), sizeof (X)) +extern void unwind_protect_mem PARAMS((char *, int)); + +/* Backwards compatibility */ +#define unwind_protect_int unwind_protect_var +#define unwind_protect_short unwind_protect_var +#define unwind_protect_string unwind_protect_var +#define unwind_protect_pointer unwind_protect_var +#define unwind_protect_jmp_buf unwind_protect_var + +#endif /* _UNWIND_PROT_H */ diff --git a/third_party/bash/utf8.c b/third_party/bash/utf8.c new file mode 100644 index 000000000..88eaa4f42 --- /dev/null +++ b/third_party/bash/utf8.c @@ -0,0 +1,196 @@ +/* utf8.c - UTF-8 character handling functions */ + +/* Copyright (C) 2018 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#ifdef HAVE_STDLIB_H +# include +#endif + +#include "bashansi.h" +#include "shmbutil.h" + +extern int locale_mb_cur_max; +extern int locale_utf8locale; + +#if defined (HANDLE_MULTIBYTE) + +char * +utf8_mbschr (s, c) + const char *s; + int c; +{ + return strchr (s, c); /* for now */ +} + +int +utf8_mbscmp (s1, s2) + const char *s1, *s2; +{ + /* Use the fact that the UTF-8 encoding preserves lexicographic order. */ + return strcmp (s1, s2); +} + +char * +utf8_mbsmbchar (str) + const char *str; +{ + register char *s; + + for (s = (char *)str; *s; s++) + if ((*s & 0xc0) == 0x80) + return s; + return (0); +} + +int +utf8_mbsnlen(src, srclen, maxlen) + const char *src; + size_t srclen; + int maxlen; +{ + register int sind, count; + + for (sind = count = 0; src[sind] && sind <= maxlen; sind++) + { + if ((src[sind] & 0xc0) != 0x80) + count++; + } + return (count); +} + +/* Adapted from GNU gnulib. Handles UTF-8 characters up to 4 bytes long */ +int +utf8_mblen (s, n) + const char *s; + size_t n; +{ + unsigned char c, c1, c2, c3; + + if (s == 0) + return (0); /* no shift states */ + if (n <= 0) + return (-1); + + c = (unsigned char)*s; + if (c < 0x80) + return (c != 0); + if (c >= 0xc2) + { + c1 = (unsigned char)s[1]; + if (c < 0xe0) + { + if (n == 1) + return -2; + + /* + * c c1 + * + * U+0080..U+07FF C2..DF 80..BF + */ + + if (n >= 2 && (c1 ^ 0x80) < 0x40) /* 0x80..0xbf */ + return 2; + } + else if (c < 0xf0) + { + if (n == 1) + return -2; + + /* + * c c1 c2 + * + * U+0800..U+0FFF E0 A0..BF 80..BF + * U+1000..U+CFFF E1..EC 80..BF 80..BF + * U+D000..U+D7FF ED 80..9F 80..BF + * U+E000..U+FFFF EE..EF 80..BF 80..BF + */ + + if ((c1 ^ 0x80) < 0x40 + && (c >= 0xe1 || c1 >= 0xa0) + && (c != 0xed || c1 < 0xa0)) + { + if (n == 2) + return -2; /* incomplete */ + + c2 = (unsigned char)s[2]; + if ((c2 ^ 0x80) < 0x40) + return 3; + } + } + else if (c <= 0xf4) + { + if (n == 1) + return -2; + + /* + * c c1 c2 c3 + * + * U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + * U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + * U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + */ + if (((c1 ^ 0x80) < 0x40) + && (c >= 0xf1 || c1 >= 0x90) + && (c < 0xf4 || (c == 0xf4 && c1 < 0x90))) + { + if (n == 2) + return -2; /* incomplete */ + + c2 = (unsigned char)s[2]; + if ((c2 ^ 0x80) < 0x40) + { + if (n == 3) + return -2; + + c3 = (unsigned char)s[3]; + if ((c3 ^ 0x80) < 0x40) + return 4; + } + } + } + } + /* invalid or incomplete multibyte character */ + return -1; +} + +/* We can optimize this if we know the locale is UTF-8, but needs to handle + malformed byte sequences. */ +size_t +utf8_mbstrlen(s) + const char *s; +{ + size_t clen, nc; + int mb_cur_max; + + nc = 0; + mb_cur_max = MB_CUR_MAX; + while (*s && (clen = (size_t)utf8_mblen(s, mb_cur_max)) != 0) + { + if (MB_INVALIDCH(clen)) + clen = 1; /* assume single byte */ + + s += clen; + nc++; + } + return nc; +} + +#endif diff --git a/third_party/bash/variables.c b/third_party/bash/variables.c new file mode 100644 index 000000000..58abd5b86 --- /dev/null +++ b/third_party/bash/variables.c @@ -0,0 +1,6590 @@ +/* variables.c -- Functions for hacking shell variables. */ + +/* Copyright (C) 1987-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" +#include "posixtime.h" + +#if defined (__QNX__) +# if defined (__QNXNTO__) +# include +# else +# include +# endif /* !__QNXNTO__ */ +#endif /* __QNX__ */ + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include "chartypes.h" +#if defined (HAVE_PWD_H) +# include +#endif +#include "bashansi.h" +#include "bashintl.h" +#include "filecntl.h" + +#define NEED_XTRACE_SET_DECL + +#include "shell.h" +#include "parser.h" +#include "flags.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "mailcheck.h" +#include "input.h" +#include "hashcmd.h" +#include "pathexp.h" +#include "alias.h" +#include "jobs.h" + +#include "version.h" + +#include "getopt.h" +#include "common.h" +#include "builtext.h" + +#if defined (READLINE) +# include "bashline.h" +# include "third_party/readline/readline.h" +#else +# include "tilde.h" +#endif + +#if defined (HISTORY) +# include "bashhist.h" +# include "third_party/readline/history.h" +#endif /* HISTORY */ + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +#define VARIABLES_HASH_BUCKETS 1024 /* must be power of two */ +#define FUNCTIONS_HASH_BUCKETS 512 +#define TEMPENV_HASH_BUCKETS 4 /* must be power of two */ + +#define BASHFUNC_PREFIX "BASH_FUNC_" +#define BASHFUNC_PREFLEN 10 /* == strlen(BASHFUNC_PREFIX */ +#define BASHFUNC_SUFFIX "%%" +#define BASHFUNC_SUFFLEN 2 /* == strlen(BASHFUNC_SUFFIX) */ + +#if ARRAY_EXPORT +#define BASHARRAY_PREFIX "BASH_ARRAY_" +#define BASHARRAY_PREFLEN 11 +#define BASHARRAY_SUFFIX "%%" +#define BASHARRAY_SUFFLEN 2 + +#define BASHASSOC_PREFIX "BASH_ASSOC_" +#define BASHASSOC_PREFLEN 11 +#define BASHASSOC_SUFFIX "%%" /* needs to be the same as BASHARRAY_SUFFIX */ +#define BASHASSOC_SUFFLEN 2 +#endif + +/* flags for find_variable_internal */ + +#define FV_FORCETEMPENV 0x01 +#define FV_SKIPINVISIBLE 0x02 +#define FV_NODYNAMIC 0x04 + +extern char **environ; + +/* Variables used here and defined in other files. */ +extern time_t shell_start_time; +extern struct timeval shellstart; + +/* The list of shell variables that the user has created at the global + scope, or that came from the environment. */ +VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL; + +/* The current list of shell variables, including function scopes */ +VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL; + +/* The list of shell functions that the user has created, or that came from + the environment. */ +HASH_TABLE *shell_functions = (HASH_TABLE *)NULL; + +HASH_TABLE *invalid_env = (HASH_TABLE *)NULL; + +#if defined (DEBUGGER) +/* The table of shell function definitions that the user defined or that + came from the environment. */ +HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL; +#endif + +/* The current variable context. This is really a count of how deep into + executing functions we are. */ +int variable_context = 0; + +/* If non-zero, local variables inherit values and attributes from a variable + with the same name at a previous scope. */ +int localvar_inherit = 0; + +/* If non-zero, calling `unset' on local variables in previous scopes marks + them as invisible so lookups find them unset. This is the same behavior + as local variables in the current local scope. */ +int localvar_unset = 0; + +/* The set of shell assignments which are made only in the environment + for a single command. */ +HASH_TABLE *temporary_env = (HASH_TABLE *)NULL; + +/* Set to non-zero if an assignment error occurs while putting variables + into the temporary environment. */ +int tempenv_assign_error; + +/* Some funky variables which are known about specially. Here is where + "$*", "$1", and all the cruft is kept. */ +char *dollar_vars[10]; +WORD_LIST *rest_of_args = (WORD_LIST *)NULL; +int posparam_count = 0; + +/* The value of $$. */ +pid_t dollar_dollar_pid; + +/* Non-zero means that we have to remake EXPORT_ENV. */ +int array_needs_making = 1; + +/* The number of times BASH has been executed. This is set + by initialize_variables (). */ +int shell_level = 0; + +/* An array which is passed to commands as their environment. It is + manufactured from the union of the initial environment and the + shell variables that are marked for export. */ +char **export_env = (char **)NULL; +static int export_env_index; +static int export_env_size; + +#if defined (READLINE) +static int winsize_assignment; /* currently assigning to LINES or COLUMNS */ +#endif + +SHELL_VAR nameref_invalid_value; +static SHELL_VAR nameref_maxloop_value; + +static HASH_TABLE *last_table_searched; /* hash_lookup sets this */ +static VAR_CONTEXT *last_context_searched; + +/* Some forward declarations. */ +static void create_variable_tables PARAMS((void)); + +static void set_machine_vars PARAMS((void)); +static void set_home_var PARAMS((void)); +static void set_shell_var PARAMS((void)); +static char *get_bash_name PARAMS((void)); +static void initialize_shell_level PARAMS((void)); +static void uidset PARAMS((void)); +#if defined (ARRAY_VARS) +static void make_vers_array PARAMS((void)); +#endif + +static SHELL_VAR *null_assign PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +#if defined (ARRAY_VARS) +static SHELL_VAR *null_array_assign PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +#endif +static SHELL_VAR *get_self PARAMS((SHELL_VAR *)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *init_dynamic_array_var PARAMS((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +static SHELL_VAR *init_dynamic_assoc_var PARAMS((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +#endif + +static inline SHELL_VAR *set_int_value (SHELL_VAR *, intmax_t, int); +static inline SHELL_VAR *set_string_value (SHELL_VAR *, const char *, int); + +static SHELL_VAR *assign_seconds PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_seconds PARAMS((SHELL_VAR *)); +static SHELL_VAR *init_seconds_var PARAMS((void)); + +static SHELL_VAR *assign_random PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_random PARAMS((SHELL_VAR *)); + +static SHELL_VAR *get_urandom PARAMS((SHELL_VAR *)); + +static SHELL_VAR *assign_lineno PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_lineno PARAMS((SHELL_VAR *)); + +static SHELL_VAR *assign_subshell PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_subshell PARAMS((SHELL_VAR *)); + +static SHELL_VAR *get_epochseconds PARAMS((SHELL_VAR *)); +static SHELL_VAR *get_epochrealtime PARAMS((SHELL_VAR *)); + +static SHELL_VAR *get_bashpid PARAMS((SHELL_VAR *)); + +static SHELL_VAR *get_bash_argv0 PARAMS((SHELL_VAR *)); +static SHELL_VAR *assign_bash_argv0 PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +static void set_argv0 PARAMS((void)); + +#if defined (HISTORY) +static SHELL_VAR *get_histcmd PARAMS((SHELL_VAR *)); +#endif + +#if defined (READLINE) +static SHELL_VAR *get_comp_wordbreaks PARAMS((SHELL_VAR *)); +static SHELL_VAR *assign_comp_wordbreaks PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR *assign_dirstack PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +static SHELL_VAR *get_dirstack PARAMS((SHELL_VAR *)); +#endif + +#if defined (ARRAY_VARS) +static SHELL_VAR *get_groupset PARAMS((SHELL_VAR *)); +# if defined (DEBUGGER) +static SHELL_VAR *get_bashargcv PARAMS((SHELL_VAR *)); +# endif +static SHELL_VAR *build_hashcmd PARAMS((SHELL_VAR *)); +static SHELL_VAR *get_hashcmd PARAMS((SHELL_VAR *)); +static SHELL_VAR *assign_hashcmd PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +# if defined (ALIAS) +static SHELL_VAR *build_aliasvar PARAMS((SHELL_VAR *)); +static SHELL_VAR *get_aliasvar PARAMS((SHELL_VAR *)); +static SHELL_VAR *assign_aliasvar PARAMS((SHELL_VAR *, char *, arrayind_t, char *)); +# endif +#endif + +static SHELL_VAR *get_funcname PARAMS((SHELL_VAR *)); +static SHELL_VAR *init_funcname_var PARAMS((void)); + +static void initialize_dynamic_variables PARAMS((void)); + +static SHELL_VAR *bind_invalid_envvar PARAMS((const char *, char *, int)); + +static int var_sametype PARAMS((SHELL_VAR *, SHELL_VAR *)); + +static SHELL_VAR *hash_lookup PARAMS((const char *, HASH_TABLE *)); +static SHELL_VAR *new_shell_variable PARAMS((const char *)); +static SHELL_VAR *make_new_variable PARAMS((const char *, HASH_TABLE *)); +static SHELL_VAR *bind_variable_internal PARAMS((const char *, char *, HASH_TABLE *, int, int)); + +static void dispose_variable_value PARAMS((SHELL_VAR *)); +static void free_variable_hash_data PARAMS((PTR_T)); + +static VARLIST *vlist_alloc PARAMS((int)); +static VARLIST *vlist_realloc PARAMS((VARLIST *, int)); +static void vlist_add PARAMS((VARLIST *, SHELL_VAR *, int)); + +static void flatten PARAMS((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int)); + +static int qsort_var_comp PARAMS((SHELL_VAR **, SHELL_VAR **)); + +static SHELL_VAR **vapply PARAMS((sh_var_map_func_t *)); +static SHELL_VAR **fapply PARAMS((sh_var_map_func_t *)); + +static int visible_var PARAMS((SHELL_VAR *)); +static int visible_and_exported PARAMS((SHELL_VAR *)); +static int export_environment_candidate PARAMS((SHELL_VAR *)); +static int local_and_exported PARAMS((SHELL_VAR *)); +static int visible_variable_in_context PARAMS((SHELL_VAR *)); +static int variable_in_context PARAMS((SHELL_VAR *)); +#if defined (ARRAY_VARS) +static int visible_array_vars PARAMS((SHELL_VAR *)); +#endif + +static SHELL_VAR *find_variable_internal PARAMS((const char *, int)); + +static SHELL_VAR *find_nameref_at_context PARAMS((SHELL_VAR *, VAR_CONTEXT *)); +static SHELL_VAR *find_variable_nameref_context PARAMS((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **)); +static SHELL_VAR *find_variable_last_nameref_context PARAMS((SHELL_VAR *, VAR_CONTEXT *, VAR_CONTEXT **)); + +static SHELL_VAR *bind_tempenv_variable PARAMS((const char *, char *)); +static void push_posix_temp_var PARAMS((PTR_T)); +static void push_temp_var PARAMS((PTR_T)); +static void propagate_temp_var PARAMS((PTR_T)); +static void dispose_temporary_env PARAMS((sh_free_func_t *)); + +static inline char *mk_env_string PARAMS((const char *, const char *, int)); +static char **make_env_array_from_var_list PARAMS((SHELL_VAR **)); +static char **make_var_export_array PARAMS((VAR_CONTEXT *)); +static char **make_func_export_array PARAMS((void)); +static void add_temp_array_to_env PARAMS((char **, int, int)); + +static int n_shell_variables PARAMS((void)); +static int set_context PARAMS((SHELL_VAR *)); + +static void push_func_var PARAMS((PTR_T)); +static void push_builtin_var PARAMS((PTR_T)); +static void push_exported_var PARAMS((PTR_T)); + +static void delete_local_contexts PARAMS((VAR_CONTEXT *)); + +/* This needs to be looked at again. */ +static inline void push_posix_tempvar_internal PARAMS((SHELL_VAR *, int)); + +static inline int find_special_var PARAMS((const char *)); + +static void +create_variable_tables () +{ + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (VARIABLES_HASH_BUCKETS); + } + + if (shell_functions == 0) + shell_functions = hash_create (FUNCTIONS_HASH_BUCKETS); + +#if defined (DEBUGGER) + if (shell_function_defs == 0) + shell_function_defs = hash_create (FUNCTIONS_HASH_BUCKETS); +#endif +} + +/* Initialize the shell variables from the current environment. + If PRIVMODE is nonzero, don't import functions from ENV or + parse $SHELLOPTS. */ +void +initialize_shell_variables (env, privmode) + char **env; + int privmode; +{ + char *name, *string, *temp_string; + int c, char_index, string_index, string_length, ro; + SHELL_VAR *temp_var; + + create_variable_tables (); + + for (string_index = 0; env && (string = env[string_index++]); ) + { + char_index = 0; + name = string; + while ((c = *string++) && c != '=') + ; + if (string[-1] == '=') + char_index = string - name - 1; + + /* If there are weird things in the environment, like `=xxx' or a + string without an `=', just skip them. */ + if (char_index == 0) + continue; + + /* ASSERT(name[char_index] == '=') */ + name[char_index] = '\0'; + /* Now, name = env variable name, string = env variable value, and + char_index == strlen (name) */ + + temp_var = (SHELL_VAR *)NULL; + +#if defined (FUNCTION_IMPORT) + /* If exported function, define it now. Don't import functions from + the environment in privileged mode. */ + if (privmode == 0 && read_but_dont_execute == 0 && + STREQN (BASHFUNC_PREFIX, name, BASHFUNC_PREFLEN) && + STREQ (BASHFUNC_SUFFIX, name + char_index - BASHFUNC_SUFFLEN) && + STREQN ("() {", string, 4)) + { + size_t namelen; + char *tname; /* desired imported function name */ + + namelen = char_index - BASHFUNC_PREFLEN - BASHFUNC_SUFFLEN; + + tname = name + BASHFUNC_PREFLEN; /* start of func name */ + tname[namelen] = '\0'; /* now tname == func name */ + + string_length = strlen (string); + temp_string = (char *)xmalloc (namelen + string_length + 2); + + memcpy (temp_string, tname, namelen); + temp_string[namelen] = ' '; + memcpy (temp_string + namelen + 1, string, string_length + 1); + + /* Don't import function names that are invalid identifiers from the + environment in posix mode, though we still allow them to be defined as + shell variables. */ + if (absolute_program (tname) == 0 && (posixly_correct == 0 || legal_identifier (tname))) + parse_and_execute (temp_string, tname, SEVAL_NONINT|SEVAL_NOHIST|SEVAL_FUNCDEF|SEVAL_ONECMD); + else + free (temp_string); /* parse_and_execute does this */ + + if (temp_var = find_function (tname)) + { + VSETATTR (temp_var, (att_exported|att_imported)); + array_needs_making = 1; + } + else + { + if (temp_var = bind_invalid_envvar (name, string, 0)) + { + VSETATTR (temp_var, (att_exported | att_imported | att_invisible)); + array_needs_making = 1; + } + last_command_exit_value = EXECUTION_FAILURE; + report_error (_("error importing function definition for `%s'"), tname); + } + + /* Restore original suffix */ + tname[namelen] = BASHFUNC_SUFFIX[0]; + } + else +#endif /* FUNCTION_IMPORT */ +#if defined (ARRAY_VARS) +# if ARRAY_EXPORT + /* Array variables may not yet be exported. */ + if (STREQN (BASHARRAY_PREFIX, name, BASHARRAY_PREFLEN) && + STREQN (BASHARRAY_SUFFIX, name + char_index - BASHARRAY_SUFFLEN, BASHARRAY_SUFFLEN) && + *string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')') + { + size_t namelen; + char *tname; /* desired imported array variable name */ + + namelen = char_index - BASHARRAY_PREFLEN - BASHARRAY_SUFFLEN; + + tname = name + BASHARRAY_PREFLEN; /* start of variable name */ + tname[namelen] = '\0'; /* now tname == varname */ + + string_length = 1; + temp_string = extract_array_assignment_list (string, &string_length); + temp_var = assign_array_from_string (tname, temp_string, 0); + FREE (temp_string); + if (temp_var) + { + VSETATTR (temp_var, (att_exported | att_imported)); + array_needs_making = 1; + } + } + else if (STREQN (BASHASSOC_PREFIX, name, BASHASSOC_PREFLEN) && + STREQN (BASHASSOC_SUFFIX, name + char_index - BASHASSOC_SUFFLEN, BASHASSOC_SUFFLEN) && + *string == '(' && string[1] == '[' && string[strlen (string) - 1] == ')') + { + size_t namelen; + char *tname; /* desired imported assoc variable name */ + + namelen = char_index - BASHASSOC_PREFLEN - BASHASSOC_SUFFLEN; + + tname = name + BASHASSOC_PREFLEN; /* start of variable name */ + tname[namelen] = '\0'; /* now tname == varname */ + + /* need to make sure it exists as an associative array first */ + temp_var = find_or_make_array_variable (tname, 2); + if (temp_var) + { + string_length = 1; + temp_string = extract_array_assignment_list (string, &string_length); + temp_var = assign_array_var_from_string (temp_var, temp_string, 0); + } + FREE (temp_string); + if (temp_var) + { + VSETATTR (temp_var, (att_exported | att_imported)); + array_needs_making = 1; + } + } + else +# endif /* ARRAY_EXPORT */ +#endif + { + ro = 0; + /* If we processed a command-line option that caused SHELLOPTS to be + set, it may already be set (and read-only) by the time we process + the shell's environment. */ + if (/* posixly_correct &&*/ STREQ (name, "SHELLOPTS")) + { + temp_var = find_variable ("SHELLOPTS"); + ro = temp_var && readonly_p (temp_var); + if (temp_var) + VUNSETATTR (temp_var, att_readonly); + } + if (legal_identifier (name)) + { + temp_var = bind_variable (name, string, 0); + if (temp_var) + { + VSETATTR (temp_var, (att_exported | att_imported)); + if (ro) + VSETATTR (temp_var, att_readonly); + } + } + else + { + temp_var = bind_invalid_envvar (name, string, 0); + if (temp_var) + VSETATTR (temp_var, (att_exported | att_imported | att_invisible)); + } + if (temp_var) + array_needs_making = 1; + } + + name[char_index] = '='; + /* temp_var can be NULL if it was an exported function with a syntax + error (a different bug, but it still shouldn't dump core). */ + if (temp_var && function_p (temp_var) == 0) /* XXX not yet */ + { + CACHE_IMPORTSTR (temp_var, name); + } + } + + set_pwd (); + + /* Set up initial value of $_ */ + temp_var = set_if_not ("_", dollar_vars[0]); + + /* Remember this pid. */ + dollar_dollar_pid = getpid (); + + /* Now make our own defaults in case the vars that we think are + important are missing. */ + temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE); + temp_var = set_if_not ("TERM", "dumb"); + +#if defined (__QNX__) + /* set node id -- don't import it from the environment */ + { + char node_name[22]; +# if defined (__QNXNTO__) + netmgr_ndtostr(ND2S_LOCAL_STR, ND_LOCAL_NODE, node_name, sizeof(node_name)); +# else + qnx_nidtostr (getnid (), node_name, sizeof (node_name)); +# endif + temp_var = bind_variable ("NODE", node_name, 0); + if (temp_var) + set_auto_export (temp_var); + } +#endif + + /* set up the prompts. */ + if (interactive_shell) + { +#if defined (PROMPT_STRING_DECODE) + set_if_not ("PS1", primary_prompt); +#else + if (current_user.uid == -1) + get_current_user_info (); + set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt); +#endif + set_if_not ("PS2", secondary_prompt); + } + + if (current_user.euid == 0) + bind_variable ("PS4", "+ ", 0); + else + set_if_not ("PS4", "+ "); + + /* Don't allow IFS to be imported from the environment. */ + temp_var = bind_variable ("IFS", " \t\n", 0); + setifs (temp_var); + + /* Magic machine types. Pretty convenient. */ + set_machine_vars (); + + /* Default MAILCHECK for interactive shells. Defer the creation of a + default MAILPATH until the startup files are read, because MAIL + names a mail file if MAILPATH is not set, and we should provide a + default only if neither is set. */ + if (interactive_shell) + { + temp_var = set_if_not ("MAILCHECK", posixly_correct ? "600" : "60"); + VSETATTR (temp_var, att_integer); + } + + /* Do some things with shell level. */ + initialize_shell_level (); + + set_ppid (); + + set_argv0 (); + + /* Initialize the `getopts' stuff. */ + temp_var = bind_variable ("OPTIND", "1", 0); + VSETATTR (temp_var, att_integer); + getopts_reset (0); + bind_variable ("OPTERR", "1", 0); + sh_opterr = 1; + + if (login_shell == 1 && posixly_correct == 0) + set_home_var (); + + /* Get the full pathname to THIS shell, and set the BASH variable + to it. */ + name = get_bash_name (); + temp_var = bind_variable ("BASH", name, 0); + free (name); + + /* Make the exported environment variable SHELL be the user's login + shell. Note that the `tset' command looks at this variable + to determine what style of commands to output; if it ends in "csh", + then C-shell commands are output, else Bourne shell commands. */ + set_shell_var (); + + /* Make a variable called BASH_VERSION which contains the version info. */ + bind_variable ("BASH_VERSION", shell_version_string (), 0); +#if defined (ARRAY_VARS) + make_vers_array (); +#endif + + if (command_execution_string) + bind_variable ("BASH_EXECUTION_STRING", command_execution_string, 0); + + /* Find out if we're supposed to be in Posix.2 mode via an + environment variable. */ + temp_var = find_variable ("POSIXLY_CORRECT"); + if (!temp_var) + temp_var = find_variable ("POSIX_PEDANTIC"); + if (temp_var && imported_p (temp_var)) + sv_strict_posix (temp_var->name); + +#if defined (HISTORY) + /* Set history variables to defaults, and then do whatever we would + do if the variable had just been set. Do this only in the case + that we are remembering commands on the history list. */ + if (remember_on_history) + { + name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0); + + set_if_not ("HISTFILE", name); + free (name); + } +#endif /* HISTORY */ + + /* Seed the random number generators. */ + seedrand (); + seedrand32 (); + + /* Handle some "special" variables that we may have inherited from a + parent shell. */ + if (interactive_shell) + { + temp_var = find_variable ("IGNOREEOF"); + if (!temp_var) + temp_var = find_variable ("ignoreeof"); + if (temp_var && imported_p (temp_var)) + sv_ignoreeof (temp_var->name); + } + +#if defined (HISTORY) + if (interactive_shell && remember_on_history) + { + sv_history_control ("HISTCONTROL"); + sv_histignore ("HISTIGNORE"); + sv_histtimefmt ("HISTTIMEFORMAT"); + } +#endif /* HISTORY */ + +#if defined (READLINE) && defined (STRICT_POSIX) + /* POSIXLY_CORRECT will be 1 here if the shell was compiled + -DSTRICT_POSIX or if POSIXLY_CORRECT was supplied in the shell's + environment */ + if (interactive_shell && posixly_correct && no_line_editing == 0) + rl_prefer_env_winsize = 1; +#endif /* READLINE && STRICT_POSIX */ + + /* Get the user's real and effective user ids. */ + uidset (); + + temp_var = set_if_not ("BASH_LOADABLES_PATH", DEFAULT_LOADABLE_BUILTINS_PATH); + + temp_var = find_variable ("BASH_XTRACEFD"); + if (temp_var && imported_p (temp_var)) + sv_xtracefd (temp_var->name); + + sv_shcompat ("BASH_COMPAT"); + + /* Allow FUNCNEST to be inherited from the environment. */ + sv_funcnest ("FUNCNEST"); + + /* Initialize the dynamic variables, and seed their values. */ + initialize_dynamic_variables (); +} + +/* **************************************************************** */ +/* */ +/* Setting values for special shell variables */ +/* */ +/* **************************************************************** */ + +static void +set_machine_vars () +{ + SHELL_VAR *temp_var; + + temp_var = set_if_not ("HOSTTYPE", HOSTTYPE); + temp_var = set_if_not ("OSTYPE", OSTYPE); + temp_var = set_if_not ("MACHTYPE", MACHTYPE); + + temp_var = set_if_not ("HOSTNAME", current_host_name); +} + +/* Set $HOME to the information in the password file if we didn't get + it from the environment. */ + +/* This function is not static so the tilde and readline libraries can + use it. */ +char * +sh_get_home_dir () +{ + if (current_user.home_dir == 0) + get_current_user_info (); + return current_user.home_dir; +} + +static void +set_home_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("HOME"); + if (temp_var == 0) + temp_var = bind_variable ("HOME", sh_get_home_dir (), 0); +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +/* Set $SHELL to the user's login shell if it is not already set. Call + get_current_user_info if we haven't already fetched the shell. */ +static void +set_shell_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("SHELL"); + if (temp_var == 0) + { + if (current_user.shell == 0) + get_current_user_info (); + temp_var = bind_variable ("SHELL", current_user.shell, 0); + } +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +static char * +get_bash_name () +{ + char *name; + + if ((login_shell == 1) && RELPATH(shell_name)) + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + else if (ABSPATH(shell_name)) + name = savestring (shell_name); + else if (shell_name[0] == '.' && shell_name[1] == '/') + { + /* Fast path for common case. */ + char *cdir; + int len; + + cdir = get_string_value ("PWD"); + if (cdir) + { + len = strlen (cdir); + name = (char *)xmalloc (len + strlen (shell_name) + 1); + strcpy (name, cdir); + strcpy (name + len, shell_name + 1); + } + else + name = savestring (shell_name); + } + else + { + char *tname; + int s; + + tname = find_user_command (shell_name); + + if (tname == 0) + { + /* Try the current directory. If there is not an executable + there, just punt and use the login shell. */ + s = file_status (shell_name); + if (s & FS_EXECABLE) + { + tname = make_absolute (shell_name, get_string_value ("PWD")); + if (*shell_name == '.') + { + name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + if (name == 0) + name = tname; + else + free (tname); + } + else + name = tname; + } + else + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + } + else + { + name = full_pathname (tname); + free (tname); + } + } + + return (name); +} + +void +adjust_shell_level (change) + int change; +{ + char new_level[5], *old_SHLVL; + intmax_t old_level; + SHELL_VAR *temp_var; + + old_SHLVL = get_string_value ("SHLVL"); + if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0) + old_level = 0; + + shell_level = old_level + change; + if (shell_level < 0) + shell_level = 0; + else if (shell_level >= 1000) + { + internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level); + shell_level = 1; + } + + /* We don't need the full generality of itos here. */ + if (shell_level < 10) + { + new_level[0] = shell_level + '0'; + new_level[1] = '\0'; + } + else if (shell_level < 100) + { + new_level[0] = (shell_level / 10) + '0'; + new_level[1] = (shell_level % 10) + '0'; + new_level[2] = '\0'; + } + else if (shell_level < 1000) + { + new_level[0] = (shell_level / 100) + '0'; + old_level = shell_level % 100; + new_level[1] = (old_level / 10) + '0'; + new_level[2] = (old_level % 10) + '0'; + new_level[3] = '\0'; + } + + temp_var = bind_variable ("SHLVL", new_level, 0); + set_auto_export (temp_var); +} + +static void +initialize_shell_level () +{ + adjust_shell_level (1); +} + +/* If we got PWD from the environment, update our idea of the current + working directory. In any case, make sure that PWD exists before + checking it. It is possible for getcwd () to fail on shell startup, + and in that case, PWD would be undefined. If this is an interactive + login shell, see if $HOME is the current working directory, and if + that's not the same string as $PWD, set PWD=$HOME. */ + +void +set_pwd () +{ + SHELL_VAR *temp_var, *home_var; + char *temp_string, *home_string, *current_dir; + + home_var = find_variable ("HOME"); + home_string = home_var ? value_cell (home_var) : (char *)NULL; + + temp_var = find_variable ("PWD"); + /* Follow posix rules for importing PWD */ + if (temp_var && imported_p (temp_var) && + (temp_string = value_cell (temp_var)) && + temp_string[0] == '/' && + same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + { + current_dir = sh_canonpath (temp_string, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + if (current_dir == 0) + current_dir = get_working_directory ("shell_init"); + else + set_working_directory (current_dir); + if (posixly_correct && current_dir) + { + temp_var = bind_variable ("PWD", current_dir, 0); + set_auto_export (temp_var); + } + free (current_dir); + } + else if (home_string && interactive_shell && login_shell && + same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + { + set_working_directory (home_string); + temp_var = bind_variable ("PWD", home_string, 0); + set_auto_export (temp_var); + } + else + { + temp_string = get_working_directory ("shell-init"); + if (temp_string) + { + temp_var = bind_variable ("PWD", temp_string, 0); + set_auto_export (temp_var); + free (temp_string); + } + } + + /* According to the Single Unix Specification, v2, $OLDPWD is an + `environment variable' and therefore should be auto-exported. If we + don't find OLDPWD in the environment, or it doesn't name a directory, + make a dummy invisible variable for OLDPWD, and mark it as exported. */ + temp_var = find_variable ("OLDPWD"); +#if defined (OLDPWD_CHECK_DIRECTORY) + if (temp_var == 0 || value_cell (temp_var) == 0 || file_isdir (value_cell (temp_var)) == 0) +#else + if (temp_var == 0 || value_cell (temp_var) == 0) +#endif + { + temp_var = bind_variable ("OLDPWD", (char *)NULL, 0); + VSETATTR (temp_var, (att_exported | att_invisible)); + } +} + +/* Make a variable $PPID, which holds the pid of the shell's parent. */ +void +set_ppid () +{ + char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name; + SHELL_VAR *temp_var; + + name = inttostr (getppid (), namebuf, sizeof(namebuf)); + temp_var = find_variable ("PPID"); + if (temp_var) + VUNSETATTR (temp_var, (att_readonly | att_exported)); + temp_var = bind_variable ("PPID", name, 0); + VSETATTR (temp_var, (att_readonly | att_integer)); +} + +static void +uidset () +{ + char buff[INT_STRLEN_BOUND(uid_t) + 1], *b; + register SHELL_VAR *v; + + b = inttostr (current_user.uid, buff, sizeof (buff)); + v = find_variable ("UID"); + if (v == 0) + { + v = bind_variable ("UID", b, 0); + VSETATTR (v, (att_readonly | att_integer)); + } + + if (current_user.euid != current_user.uid) + b = inttostr (current_user.euid, buff, sizeof (buff)); + + v = find_variable ("EUID"); + if (v == 0) + { + v = bind_variable ("EUID", b, 0); + VSETATTR (v, (att_readonly | att_integer)); + } +} + +#if defined (ARRAY_VARS) +static void +make_vers_array () +{ + SHELL_VAR *vv; + ARRAY *av; + char *s, d[32], b[INT_STRLEN_BOUND(int) + 1]; + + unbind_variable_noref ("BASH_VERSINFO"); + + vv = make_new_array_variable ("BASH_VERSINFO"); + av = array_cell (vv); + strcpy (d, dist_version); + s = strchr (d, '.'); + if (s) + *s++ = '\0'; + array_insert (av, 0, d); + array_insert (av, 1, s); + s = inttostr (patch_level, b, sizeof (b)); + array_insert (av, 2, s); + s = inttostr (build_version, b, sizeof (b)); + array_insert (av, 3, s); + array_insert (av, 4, release_status); + array_insert (av, 5, MACHTYPE); + + VSETATTR (vv, att_readonly); +} +#endif /* ARRAY_VARS */ + +/* Set the environment variables $LINES and $COLUMNS in response to + a window size change. */ +void +sh_set_lines_and_columns (lines, cols) + int lines, cols; +{ + char val[INT_STRLEN_BOUND(int) + 1], *v; + +#if defined (READLINE) + /* If we are currently assigning to LINES or COLUMNS, don't do anything. */ + if (winsize_assignment) + return; +#endif + + v = inttostr (lines, val, sizeof (val)); + bind_variable ("LINES", v, 0); + + v = inttostr (cols, val, sizeof (val)); + bind_variable ("COLUMNS", v, 0); +} + +/* **************************************************************** */ +/* */ +/* Printing variables and values */ +/* */ +/* **************************************************************** */ + +/* Print LIST (a list of shell variables) to stdout in such a way that + they can be read back in. */ +void +print_var_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + if (invisible_p (var) == 0) + print_assignment (var); +} + +/* Print LIST (a list of shell functions) to stdout in such a way that + they can be read back in. */ +void +print_func_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + { + printf ("%s ", var->name); + print_var_function (var); + printf ("\n"); + } +} + +/* Print the value of a single SHELL_VAR. No newline is + output, but the variable is printed in such a way that + it can be read back in. */ +void +print_assignment (var) + SHELL_VAR *var; +{ + if (var_isset (var) == 0) + return; + + if (function_p (var)) + { + printf ("%s", var->name); + print_var_function (var); + printf ("\n"); + } +#if defined (ARRAY_VARS) + else if (array_p (var)) + print_array_assignment (var, 0); + else if (assoc_p (var)) + print_assoc_assignment (var, 0); +#endif /* ARRAY_VARS */ + else + { + printf ("%s=", var->name); + print_var_value (var, 1); + printf ("\n"); + } +} + +/* Print the value cell of VAR, a shell variable. Do not print + the name, nor leading/trailing newline. If QUOTE is non-zero, + and the value contains shell metacharacters, quote the value + in such a way that it can be read back in. */ +void +print_var_value (var, quote) + SHELL_VAR *var; + int quote; +{ + char *t; + + if (var_isset (var) == 0) + return; + + if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var))) + { + t = ansic_quote (value_cell (var), 0, (int *)0); + printf ("%s", t); + free (t); + } + else if (quote && sh_contains_shell_metas (value_cell (var))) + { + t = sh_single_quote (value_cell (var)); + printf ("%s", t); + free (t); + } + else + printf ("%s", value_cell (var)); +} + +/* Print the function cell of VAR, a shell variable. Do not + print the name, nor leading/trailing newline. */ +void +print_var_function (var) + SHELL_VAR *var; +{ + char *x; + + if (function_p (var) && var_isset (var)) + { + x = named_function_string ((char *)NULL, function_cell(var), FUNC_MULTILINE|FUNC_EXTERNAL); + printf ("%s", x); + } +} + +/* **************************************************************** */ +/* */ +/* Dynamic Variables */ +/* */ +/* **************************************************************** */ + +/* DYNAMIC VARIABLES + + These are variables whose values are generated anew each time they are + referenced. These are implemented using a pair of function pointers + in the struct variable: assign_func, which is called from bind_variable + and, if arrays are compiled into the shell, some of the functions in + arrayfunc.c, and dynamic_value, which is called from find_variable. + + assign_func is called from bind_variable_internal, if + bind_variable_internal discovers that the variable being assigned to + has such a function. The function is called as + SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind) + and the (SHELL_VAR *)temp is returned as the value of bind_variable. It + is usually ENTRY (self). IND is an index for an array variable, and + unused otherwise. + + dynamic_value is called from find_variable_internal to return a `new' + value for the specified dynamic variable. If this function is NULL, + the variable is treated as a `normal' shell variable. If it is not, + however, then this function is called like this: + tempvar = (*(var->dynamic_value)) (var); + + Sometimes `tempvar' will replace the value of `var'. Other times, the + shell will simply use the string value. Pretty object-oriented, huh? + + Be warned, though: if you `unset' a special variable, it loses its + special meaning, even if you subsequently set it. + + The special assignment code would probably have been better put in + subst.c: do_assignment_internal, in the same style as + stupidly_hack_special_variables, but I wanted the changes as + localized as possible. */ + +#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \ + do \ + { \ + v = bind_variable (var, (val), 0); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_array_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ASSOC_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_assoc_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +static SHELL_VAR * +null_assign (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + return (self); +} + +#if defined (ARRAY_VARS) +static SHELL_VAR * +null_array_assign (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + return (self); +} +#endif + +/* Degenerate `dynamic_value' function; just returns what's passed without + manipulation. */ +static SHELL_VAR * +get_self (self) + SHELL_VAR *self; +{ + return (self); +} + +#if defined (ARRAY_VARS) +/* A generic dynamic array variable initializer. Initialize array variable + NAME with dynamic value function GETFUNC and assignment function SETFUNC. */ +static SHELL_VAR * +init_dynamic_array_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} + +static SHELL_VAR * +init_dynamic_assoc_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ASSOC_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} +#endif + +/* Set the string value of VAR to the string representation of VALUE. + Right now this takes an INTMAX_T because that's what itos needs. If + FLAGS&1, we force the integer attribute on. */ +static inline SHELL_VAR * +set_int_value (SHELL_VAR *var, intmax_t value, int flags) +{ + char *p; + + p = itos (value); + FREE (value_cell (var)); + var_setvalue (var, p); + if (flags & 1) + VSETATTR (var, att_integer); + return (var); +} + +static inline SHELL_VAR * +set_string_value (SHELL_VAR *var, const char *value, int flags) +{ + char *p; + + if (value && *value) + p = savestring (value); + else + { + p = (char *)xmalloc (1); + p[0] = '\0'; + } + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +/* The value of $SECONDS. This is the number of seconds since shell + invocation, or, the number of seconds since the last assignment + the + value of the last assignment. */ +static intmax_t seconds_value_assigned; + +static SHELL_VAR * +assign_seconds (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t nval; + int expok; + + if (integer_p (self)) + nval = evalexp (value, 0, &expok); + else + expok = legal_number (value, &nval); + seconds_value_assigned = expok ? nval : 0; + gettimeofday (&shellstart, NULL); + shell_start_time = shellstart.tv_sec; + return (set_int_value (self, nval, integer_p (self) != 0)); +} + +static SHELL_VAR * +get_seconds (var) + SHELL_VAR *var; +{ + time_t time_since_start; + struct timeval tv; + + gettimeofday(&tv, NULL); + time_since_start = tv.tv_sec - shell_start_time; + return (set_int_value (var, seconds_value_assigned + time_since_start, 1)); +} + +static SHELL_VAR * +init_seconds_var () +{ + SHELL_VAR *v; + + v = find_variable ("SECONDS"); + if (v) + { + if (legal_number (value_cell(v), &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + } + INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds); + return v; +} + +/* Functions for $RANDOM and $SRANDOM */ + +int last_random_value; +static int seeded_subshell = 0; + +static SHELL_VAR * +assign_random (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t seedval; + int expok; + + if (integer_p (self)) + seedval = evalexp (value, 0, &expok); + else + expok = legal_number (value, &seedval); + if (expok == 0) + return (self); + sbrand (seedval); + if (subshell_environment) + seeded_subshell = getpid (); + return (set_int_value (self, seedval, integer_p (self) != 0)); +} + +int +get_random_number () +{ + int rv, pid; + + /* Reset for command and process substitution. */ + pid = getpid (); + if (subshell_environment && seeded_subshell != pid) + { + seedrand (); + seeded_subshell = pid; + } + + do + rv = brand (); + while (rv == last_random_value); + + return (last_random_value = rv); +} + +static SHELL_VAR * +get_random (var) + SHELL_VAR *var; +{ + int rv; + + rv = get_random_number (); + return (set_int_value (var, rv, 1)); +} + +static SHELL_VAR * +get_urandom (var) + SHELL_VAR *var; +{ + u_bits32_t rv; + + rv = get_urandom32 (); + return (set_int_value (var, rv, 1)); +} + +static SHELL_VAR * +assign_lineno (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + line_number = line_number_base = new_value; + return (set_int_value (var, line_number, integer_p (var) != 0)); +} + +/* Function which returns the current line number. */ +static SHELL_VAR * +get_lineno (var) + SHELL_VAR *var; +{ + int ln; + + ln = executing_line_number (); + return (set_int_value (var, ln, 0)); +} + +static SHELL_VAR * +assign_subshell (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + subshell_level = new_value; + return var; +} + +static SHELL_VAR * +get_subshell (var) + SHELL_VAR *var; +{ + return (set_int_value (var, subshell_level, 0)); +} + +static SHELL_VAR * +get_epochseconds (var) + SHELL_VAR *var; +{ + intmax_t now; + + now = NOW; + return (set_int_value (var, now, 0)); +} + +static SHELL_VAR * +get_epochrealtime (var) + SHELL_VAR *var; +{ + char buf[32]; + struct timeval tv; + + gettimeofday (&tv, NULL); + snprintf (buf, sizeof (buf), "%u%c%06u", (unsigned)tv.tv_sec, + locale_decpoint (), + (unsigned)tv.tv_usec); + + return (set_string_value (var, buf, 0)); +} + +static SHELL_VAR * +get_bashpid (var) + SHELL_VAR *var; +{ + int pid; + + pid = getpid (); + return (set_int_value (var, pid, 1)); +} + +static SHELL_VAR * +get_bash_argv0 (var) + SHELL_VAR *var; +{ + return (set_string_value (var, dollar_vars[0], 0)); +} + +static char *static_shell_name = 0; + +static SHELL_VAR * +assign_bash_argv0 (var, value, unused, key) + SHELL_VAR *var; + char *value; + arrayind_t unused; + char *key; +{ + size_t vlen; + + if (value == 0) + return var; + + FREE (dollar_vars[0]); + dollar_vars[0] = savestring (value); + + /* Need these gyrations because shell_name isn't dynamically allocated */ + vlen = STRLEN (value); + static_shell_name = xrealloc (static_shell_name, vlen + 1); + strcpy (static_shell_name, value); + + shell_name = static_shell_name; + return var; +} + +static void +set_argv0 () +{ + SHELL_VAR *v; + + v = find_variable ("BASH_ARGV0"); + if (v && imported_p (v)) + assign_bash_argv0 (v, value_cell (v), 0, 0); +} + +static SHELL_VAR * +get_bash_command (var) + SHELL_VAR *var; +{ + char *p; + + p = the_printed_command_except_trap ? the_printed_command_except_trap : ""; + return (set_string_value (var, p, 0)); +} + +#if defined (HISTORY) +static SHELL_VAR * +get_histcmd (var) + SHELL_VAR *var; +{ + int n; + + /* Do the same adjustment here we do in parse.y:prompt_history_number, + assuming that we are in one of two states: decoding this as part of + the prompt string, in which case we do not want to assume that the + command has been saved to the history and the history number incremented, + or the expansion is part of the current command being executed and has + already been saved to history and the history number incremented. + Right now we use EXECUTING as the determinant. */ + n = history_number () - executing; + return (set_int_value (var, n, 0)); +} +#endif + +#if defined (READLINE) +/* When this function returns, VAR->value points to malloced memory. */ +static SHELL_VAR * +get_comp_wordbreaks (var) + SHELL_VAR *var; +{ + /* If we don't have anything yet, assign a default value. */ + if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0) + enable_hostname_completion (perform_hostname_completion); + + return (set_string_value (var, rl_completer_word_break_characters, 0)); +} + +/* When this function returns, rl_completer_word_break_characters points to + malloced memory. */ +static SHELL_VAR * +assign_comp_wordbreaks (self, value, unused, key) + SHELL_VAR *self; + char *value; + arrayind_t unused; + char *key; +{ + if (rl_completer_word_break_characters && + rl_completer_word_break_characters != rl_basic_word_break_characters) + free ((void *)rl_completer_word_break_characters); + + rl_completer_word_break_characters = savestring (value); + return self; +} +#endif /* READLINE */ + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR * +assign_dirstack (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + set_dirstack_element (ind, 1, value); + return self; +} + +static SHELL_VAR * +get_dirstack (self) + SHELL_VAR *self; +{ + ARRAY *a; + WORD_LIST *l; + + l = get_directory_stack (0); + a = array_from_word_list (l); + array_dispose (array_cell (self)); + dispose_words (l); + var_setarray (self, a); + return self; +} +#endif /* PUSHD AND POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) +/* We don't want to initialize the group set with a call to getgroups() + unless we're asked to, but we only want to do it once. */ +static SHELL_VAR * +get_groupset (self) + SHELL_VAR *self; +{ + register int i; + int ng; + ARRAY *a; + static char **group_set = (char **)NULL; + + if (group_set == 0) + { + group_set = get_group_list (&ng); + a = array_cell (self); + for (i = 0; i < ng; i++) + array_insert (a, i, group_set[i]); + } + return (self); +} + +# if defined (DEBUGGER) +static SHELL_VAR * +get_bashargcv (self) + SHELL_VAR *self; +{ + static int self_semaphore = 0; + + /* Backwards compatibility: if we refer to BASH_ARGV or BASH_ARGC at the + top level without enabling debug mode, and we don't have an instance + of the variable set, initialize the arg arrays. + This will already have been done if debugging_mode != 0. */ + if (self_semaphore == 0 && variable_context == 0 && debugging_mode == 0) /* don't do it for shell functions */ + { + self_semaphore = 1; + init_bash_argv (); + self_semaphore = 0; + } + return self; +} +# endif + +static SHELL_VAR * +build_hashcmd (self) + SHELL_VAR *self; +{ + HASH_TABLE *h; + int i; + char *k, *v; + BUCKET_CONTENTS *item; + + h = assoc_cell (self); + if (h) + assoc_dispose (h); + + if (hashed_filenames == 0 || HASH_ENTRIES (hashed_filenames) == 0) + { + var_setvalue (self, (char *)NULL); + return self; + } + + h = assoc_create (hashed_filenames->nbuckets); + for (i = 0; i < hashed_filenames->nbuckets; i++) + { + for (item = hash_items (i, hashed_filenames); item; item = item->next) + { + k = savestring (item->key); + v = pathdata(item)->path; + assoc_insert (h, k, v); + } + } + + var_setvalue (self, (char *)h); + return self; +} + +static SHELL_VAR * +get_hashcmd (self) + SHELL_VAR *self; +{ + build_hashcmd (self); + return (self); +} + +static SHELL_VAR * +assign_hashcmd (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ +#if defined (RESTRICTED_SHELL) + char *full_path; + + if (restricted) + { + if (strchr (value, '/')) + { + sh_restricted (value); + return (SHELL_VAR *)NULL; + } + /* If we are changing the hash table in a restricted shell, make sure the + target pathname can be found using a $PATH search. */ + full_path = find_user_command (value); + if (full_path == 0 || *full_path == 0 || executable_file (full_path) == 0) + { + sh_notfound (value); + free (full_path); + return ((SHELL_VAR *)NULL); + } + free (full_path); + } +#endif + phash_insert (key, value, 0, 0); + return (build_hashcmd (self)); +} + +#if defined (ALIAS) +static SHELL_VAR * +build_aliasvar (self) + SHELL_VAR *self; +{ + HASH_TABLE *h; + int i; + char *k, *v; + BUCKET_CONTENTS *item; + + h = assoc_cell (self); + if (h) + assoc_dispose (h); + + if (aliases == 0 || HASH_ENTRIES (aliases) == 0) + { + var_setvalue (self, (char *)NULL); + return self; + } + + h = assoc_create (aliases->nbuckets); + for (i = 0; i < aliases->nbuckets; i++) + { + for (item = hash_items (i, aliases); item; item = item->next) + { + k = savestring (item->key); + v = ((alias_t *)(item->data))->value; + assoc_insert (h, k, v); + } + } + + var_setvalue (self, (char *)h); + return self; +} + +static SHELL_VAR * +get_aliasvar (self) + SHELL_VAR *self; +{ + build_aliasvar (self); + return (self); +} + +static SHELL_VAR * +assign_aliasvar (self, value, ind, key) + SHELL_VAR *self; + char *value; + arrayind_t ind; + char *key; +{ + if (legal_alias_name (key, 0) == 0) + { + report_error (_("`%s': invalid alias name"), key); + return (self); + } + add_alias (key, value); + return (build_aliasvar (self)); +} +#endif /* ALIAS */ + +#endif /* ARRAY_VARS */ + +/* If ARRAY_VARS is not defined, this just returns the name of any + currently-executing function. If we have arrays, it's a call stack. */ +static SHELL_VAR * +get_funcname (self) + SHELL_VAR *self; +{ +#if ! defined (ARRAY_VARS) + if (variable_context && this_shell_function) + return (set_string_value (self, this_shell_function->name, 0)); +#endif + return (self); +} + +void +make_funcname_visible (on_or_off) + int on_or_off; +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v == 0 || v->dynamic_value == 0) + return; + + if (on_or_off) + VUNSETATTR (v, att_invisible); + else + VSETATTR (v, att_invisible); +} + +static SHELL_VAR * +init_funcname_var () +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v) + return v; +#if defined (ARRAY_VARS) + INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign); +#else + INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign); +#endif + VSETATTR (v, att_invisible|att_noassign); + return v; +} + +static void +initialize_dynamic_variables () +{ + SHELL_VAR *v; + + v = init_seconds_var (); + + INIT_DYNAMIC_VAR ("BASH_ARGV0", (char *)NULL, get_bash_argv0, assign_bash_argv0); + + INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL); + INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell); + + INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random); + VSETATTR (v, att_integer); + INIT_DYNAMIC_VAR ("SRANDOM", (char *)NULL, get_urandom, (sh_var_assign_func_t *)NULL); + VSETATTR (v, att_integer); + INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno); + VSETATTR (v, att_regenerate); + + INIT_DYNAMIC_VAR ("BASHPID", (char *)NULL, get_bashpid, null_assign); + VSETATTR (v, att_integer); + + INIT_DYNAMIC_VAR ("EPOCHSECONDS", (char *)NULL, get_epochseconds, null_assign); + VSETATTR (v, att_regenerate); + INIT_DYNAMIC_VAR ("EPOCHREALTIME", (char *)NULL, get_epochrealtime, null_assign); + VSETATTR (v, att_regenerate); + +#if defined (HISTORY) + INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL); + VSETATTR (v, att_integer); +#endif + +#if defined (READLINE) + INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) + v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0); +#endif /* PUSHD_AND_POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) + v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign); + +# if defined (DEBUGGER) + v = init_dynamic_array_var ("BASH_ARGC", get_bashargcv, null_array_assign, att_noassign|att_nounset); + v = init_dynamic_array_var ("BASH_ARGV", get_bashargcv, null_array_assign, att_noassign|att_nounset); +# endif /* DEBUGGER */ + v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, att_noassign|att_nounset); + v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, att_noassign|att_nounset); + + v = init_dynamic_assoc_var ("BASH_CMDS", get_hashcmd, assign_hashcmd, att_nofree); +# if defined (ALIAS) + v = init_dynamic_assoc_var ("BASH_ALIASES", get_aliasvar, assign_aliasvar, att_nofree); +# endif +#endif + + v = init_funcname_var (); +} + +/* **************************************************************** */ +/* */ +/* Retrieving variables and values */ +/* */ +/* **************************************************************** */ + +#if 0 /* not yet */ +int +var_isset (var) + SHELL_VAR *var; +{ + return (var->value != 0); +} + +int +var_isunset (var) + SHELL_VAR *var; +{ + return (var->value == 0); +} +#endif + +/* How to get a pointer to the shell variable or function named NAME. + HASHED_VARS is a pointer to the hash table containing the list + of interest (either variables or functions). */ + +static SHELL_VAR * +hash_lookup (name, hashed_vars) + const char *name; + HASH_TABLE *hashed_vars; +{ + BUCKET_CONTENTS *bucket; + + bucket = hash_search (name, hashed_vars, 0); + /* If we find the name in HASHED_VARS, set LAST_TABLE_SEARCHED to that + table. */ + if (bucket) + last_table_searched = hashed_vars; + return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL); +} + +SHELL_VAR * +var_lookup (name, vcontext) + const char *name; + VAR_CONTEXT *vcontext; +{ + VAR_CONTEXT *vc; + SHELL_VAR *v; + + v = (SHELL_VAR *)NULL; + for (vc = vcontext; vc; vc = vc->down) + if (v = hash_lookup (name, vc->table)) + break; + + return v; +} + +/* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero, + then also search the temporarily built list of exported variables. + The lookup order is: + temporary_env + shell_variables list +*/ + +SHELL_VAR * +find_variable_internal (name, flags) + const char *name; + int flags; +{ + SHELL_VAR *var; + int search_tempenv, force_tempenv; + VAR_CONTEXT *vc; + + var = (SHELL_VAR *)NULL; + + force_tempenv = (flags & FV_FORCETEMPENV); + + /* If explicitly requested, first look in the temporary environment for + the variable. This allows constructs such as "foo=x eval 'echo $foo'" + to get the `exported' value of $foo. This happens if we are executing + a function or builtin, or if we are looking up a variable in a + "subshell environment". */ + search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment); + + if (search_tempenv && temporary_env) + var = hash_lookup (name, temporary_env); + + if (var == 0) + { + if ((flags & FV_SKIPINVISIBLE) == 0) + var = var_lookup (name, shell_variables); + else + { + /* essentially var_lookup expanded inline so we can check for + att_invisible */ + for (vc = shell_variables; vc; vc = vc->down) + { + var = hash_lookup (name, vc->table); + if (var && invisible_p (var)) + var = 0; + if (var) + break; + } + } + } + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +/* Look up and resolve the chain of nameref variables starting at V all the + way to NULL or non-nameref. */ +SHELL_VAR * +find_variable_nameref (v) + SHELL_VAR *v; +{ + int level, flags; + char *newname; + SHELL_VAR *orig, *oldv; + + level = 0; + orig = v; + while (v && nameref_p (v)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)0); /* error message here? */ + newname = nameref_cell (v); + if (newname == 0 || *newname == '\0') + return ((SHELL_VAR *)0); + oldv = v; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + /* We don't handle array subscripts here. */ + v = find_variable_internal (newname, flags); + if (v == orig || v == oldv) + { + internal_warning (_("%s: circular name reference"), orig->name); +#if 1 + /* XXX - provisional change - circular refs go to + global scope for resolution, without namerefs. */ + if (variable_context && v->context) + return (find_global_variable_noref (v->name)); + else +#endif + return ((SHELL_VAR *)0); + } + } + return v; +} + +/* Resolve the chain of nameref variables for NAME. XXX - could change later */ +SHELL_VAR * +find_variable_last_nameref (name, vflags) + const char *name; + int vflags; +{ + SHELL_VAR *v, *nv; + char *newname; + int level, flags; + + nv = v = find_variable_noref (name); + level = 0; + while (v && nameref_p (v)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)0); /* error message here? */ + newname = nameref_cell (v); + if (newname == 0 || *newname == '\0') + return ((vflags && invisible_p (v)) ? v : (SHELL_VAR *)0); + nv = v; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + /* We don't accommodate array subscripts here. */ + v = find_variable_internal (newname, flags); + } + return nv; +} + +/* Resolve the chain of nameref variables for NAME. XXX - could change later */ +SHELL_VAR * +find_global_variable_last_nameref (name, vflags) + const char *name; + int vflags; +{ + SHELL_VAR *v, *nv; + char *newname; + int level; + + nv = v = find_global_variable_noref (name); + level = 0; + while (v && nameref_p (v)) + { + level++; + if (level > NAMEREF_MAX) + return ((SHELL_VAR *)0); /* error message here? */ + newname = nameref_cell (v); + if (newname == 0 || *newname == '\0') + return ((vflags && invisible_p (v)) ? v : (SHELL_VAR *)0); + nv = v; + /* We don't accommodate array subscripts here. */ + v = find_global_variable_noref (newname); + } + return nv; +} + +static SHELL_VAR * +find_nameref_at_context (v, vc) + SHELL_VAR *v; + VAR_CONTEXT *vc; +{ + SHELL_VAR *nv, *nv2; + char *newname; + int level; + + nv = v; + level = 1; + while (nv && nameref_p (nv)) + { + level++; + if (level > NAMEREF_MAX) + return (&nameref_maxloop_value); + newname = nameref_cell (nv); + if (newname == 0 || *newname == '\0') + return ((SHELL_VAR *)NULL); + nv2 = hash_lookup (newname, vc->table); + if (nv2 == 0) + break; + nv = nv2; + } + return nv; +} + +/* Do nameref resolution from the VC, which is the local context for some + function or builtin, `up' the chain to the global variables context. If + NVCP is not NULL, return the variable context where we finally ended the + nameref resolution (so the bind_variable_internal can use the correct + variable context and hash table). */ +static SHELL_VAR * +find_variable_nameref_context (v, vc, nvcp) + SHELL_VAR *v; + VAR_CONTEXT *vc; + VAR_CONTEXT **nvcp; +{ + SHELL_VAR *nv, *nv2; + VAR_CONTEXT *nvc; + + /* Look starting at the current context all the way `up' */ + for (nv = v, nvc = vc; nvc; nvc = nvc->down) + { + nv2 = find_nameref_at_context (nv, nvc); + if (nv2 == &nameref_maxloop_value) + return (nv2); /* XXX */ + if (nv2 == 0) + continue; + nv = nv2; + if (*nvcp) + *nvcp = nvc; + if (nameref_p (nv) == 0) + break; + } + return (nameref_p (nv) ? (SHELL_VAR *)NULL : nv); +} + +/* Do nameref resolution from the VC, which is the local context for some + function or builtin, `up' the chain to the global variables context. If + NVCP is not NULL, return the variable context where we finally ended the + nameref resolution (so the bind_variable_internal can use the correct + variable context and hash table). */ +static SHELL_VAR * +find_variable_last_nameref_context (v, vc, nvcp) + SHELL_VAR *v; + VAR_CONTEXT *vc; + VAR_CONTEXT **nvcp; +{ + SHELL_VAR *nv, *nv2; + VAR_CONTEXT *nvc; + + /* Look starting at the current context all the way `up' */ + for (nv = v, nvc = vc; nvc; nvc = nvc->down) + { + nv2 = find_nameref_at_context (nv, nvc); + if (nv2 == &nameref_maxloop_value) + return (nv2); /* XXX */ + if (nv2 == 0) + continue; + nv = nv2; + if (*nvcp) + *nvcp = nvc; + } + return (nameref_p (nv) ? nv : (SHELL_VAR *)NULL); +} + +SHELL_VAR * +find_variable_nameref_for_create (name, flags) + const char *name; + int flags; +{ + SHELL_VAR *var; + + /* See if we have a nameref pointing to a variable that hasn't been + created yet. */ + var = find_variable_last_nameref (name, 1); + if ((flags&1) && var && nameref_p (var) && invisible_p (var)) + { + internal_warning (_("%s: removing nameref attribute"), name); + VUNSETATTR (var, att_nameref); + } + if (var && nameref_p (var)) + { + if (legal_identifier (nameref_cell (var)) == 0) + { + sh_invalidid (nameref_cell (var) ? nameref_cell (var) : ""); + return ((SHELL_VAR *)INVALID_NAMEREF_VALUE); + } + } + return (var); +} + +SHELL_VAR * +find_variable_nameref_for_assignment (name, flags) + const char *name; + int flags; +{ + SHELL_VAR *var; + + /* See if we have a nameref pointing to a variable that hasn't been + created yet. */ + var = find_variable_last_nameref (name, 1); + if (var && nameref_p (var) && invisible_p (var)) /* XXX - flags */ + { + internal_warning (_("%s: removing nameref attribute"), name); + VUNSETATTR (var, att_nameref); + } + if (var && nameref_p (var)) + { + if (valid_nameref_value (nameref_cell (var), 1) == 0) + { + sh_invalidid (nameref_cell (var) ? nameref_cell (var) : ""); + return ((SHELL_VAR *)INVALID_NAMEREF_VALUE); + } + } + return (var); +} + +/* If find_variable (name) returns NULL, check that it's not a nameref + referencing a variable that doesn't exist. If it is, return the new + name. If not, return the original name. Kind of like the previous + function, but dealing strictly with names. This takes assignment flags + so it can deal with the various assignment modes used by `declare'. */ +char * +nameref_transform_name (name, flags) + char *name; + int flags; +{ + SHELL_VAR *v; + char *newname; + + v = 0; + if (flags & ASS_MKLOCAL) + { + v = find_variable_last_nameref (name, 1); + /* If we're making local variables, only follow namerefs that point to + non-existent variables at the same variable context. */ + if (v && v->context != variable_context) + v = 0; + } + else if (flags & ASS_MKGLOBAL) + v = (flags & ASS_CHKLOCAL) ? find_variable_last_nameref (name, 1) + : find_global_variable_last_nameref (name, 1); + if (v && nameref_p (v) && valid_nameref_value (nameref_cell (v), 1)) + return nameref_cell (v); + return name; +} + +/* Find a variable, forcing a search of the temporary environment first */ +SHELL_VAR * +find_variable_tempenv (name) + const char *name; +{ + SHELL_VAR *var; + + var = find_variable_internal (name, FV_FORCETEMPENV); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + return (var); +} + +/* Find a variable, not forcing a search of the temporary environment first */ +SHELL_VAR * +find_variable_notempenv (name) + const char *name; +{ + SHELL_VAR *var; + + var = find_variable_internal (name, 0); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + return (var); +} + +SHELL_VAR * +find_global_variable (name) + const char *name; +{ + SHELL_VAR *var; + + var = var_lookup (name, global_variables); + if (var && nameref_p (var)) + var = find_variable_nameref (var); /* XXX - find_global_variable_noref? */ + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +SHELL_VAR * +find_global_variable_noref (name) + const char *name; +{ + SHELL_VAR *var; + + var = var_lookup (name, global_variables); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +SHELL_VAR * +find_shell_variable (name) + const char *name; +{ + SHELL_VAR *var; + + var = var_lookup (name, shell_variables); + if (var && nameref_p (var)) + var = find_variable_nameref (var); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +/* Look up the variable entry named NAME. Returns the entry or NULL. */ +SHELL_VAR * +find_variable (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + last_table_searched = 0; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + if (v && nameref_p (v)) + v = find_variable_nameref (v); + return v; +} + +/* Find the first instance of NAME in the variable context chain; return first + one found without att_invisible set; return 0 if no non-invisible instances + found. */ +SHELL_VAR * +find_variable_no_invisible (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + last_table_searched = 0; + flags = FV_SKIPINVISIBLE; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + if (v && nameref_p (v)) + v = find_variable_nameref (v); + return v; +} + +/* Find the first instance of NAME in the variable context chain; return first + one found even if att_invisible set. */ +SHELL_VAR * +find_variable_for_assignment (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + last_table_searched = 0; + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + if (v && nameref_p (v)) + v = find_variable_nameref (v); + return v; +} + +SHELL_VAR * +find_variable_noref (name) + const char *name; +{ + SHELL_VAR *v; + int flags; + + flags = 0; + if (expanding_redir == 0 && (assigning_in_environment || executing_builtin)) + flags |= FV_FORCETEMPENV; + v = find_variable_internal (name, flags); + return v; +} + +/* Look up the function entry whose name matches STRING. + Returns the entry or NULL. */ +SHELL_VAR * +find_function (name) + const char *name; +{ + return (hash_lookup (name, shell_functions)); +} + +/* Find the function definition for the shell function named NAME. Returns + the entry or NULL. */ +FUNCTION_DEF * +find_function_def (name) + const char *name; +{ +#if defined (DEBUGGER) + return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs)); +#else + return ((FUNCTION_DEF *)0); +#endif +} + +/* Return the value of VAR. VAR is assumed to have been the result of a + lookup without any subscript, if arrays are compiled into the shell. */ +char * +get_variable_value (var) + SHELL_VAR *var; +{ + if (var == 0) + return ((char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (var)) + return (array_reference (array_cell (var), 0)); + else if (assoc_p (var)) + return (assoc_reference (assoc_cell (var), "0")); +#endif + else + return (value_cell (var)); +} + +/* Return the string value of a variable. Return NULL if the variable + doesn't exist. Don't cons a new string. This is a potential memory + leak if the variable is found in the temporary environment, but doesn't + leak in practice. Since functions and variables have separate name + spaces, returns NULL if var_name is a shell function only. */ +char * +get_string_value (var_name) + const char *var_name; +{ + SHELL_VAR *var; + + var = find_variable (var_name); + return ((var) ? get_variable_value (var) : (char *)NULL); +} + +/* This is present for use by the tilde and readline libraries. */ +char * +sh_get_env_value (v) + const char *v; +{ + return get_string_value (v); +} + +/* **************************************************************** */ +/* */ +/* Creating and setting variables */ +/* */ +/* **************************************************************** */ + +static int +var_sametype (v1, v2) + SHELL_VAR *v1; + SHELL_VAR *v2; +{ + if (v1 == 0 || v2 == 0) + return 0; +#if defined (ARRAY_VARS) + else if (assoc_p (v1) && assoc_p (v2)) + return 1; + else if (array_p (v1) && array_p (v2)) + return 1; + else if (array_p (v1) || array_p (v2)) + return 0; + else if (assoc_p (v1) || assoc_p (v2)) + return 0; +#endif + else + return 1; +} + +int +validate_inherited_value (var, type) + SHELL_VAR *var; + int type; +{ +#if defined (ARRAY_VARS) + if (type == att_array && assoc_p (var)) + return 0; + else if (type == att_assoc && array_p (var)) + return 0; + else +#endif + return 1; /* should we run convert_var_to_array here or let the caller? */ +} + +/* Set NAME to VALUE if NAME has no value. */ +SHELL_VAR * +set_if_not (name, value) + char *name, *value; +{ + SHELL_VAR *v; + + if (shell_variables == 0) + create_variable_tables (); + + v = find_variable (name); + if (v == 0) + v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH, 0); + return (v); +} + +/* Create a local variable referenced by NAME. */ +SHELL_VAR * +make_local_variable (name, flags) + const char *name; + int flags; +{ + SHELL_VAR *new_var, *old_var, *old_ref; + VAR_CONTEXT *vc; + int was_tmpvar; + char *old_value; + + /* We don't want to follow the nameref chain when making local variables; we + just want to create them. */ + old_ref = find_variable_noref (name); + if (old_ref && nameref_p (old_ref) == 0) + old_ref = 0; + /* local foo; local foo; is a no-op. */ + old_var = find_variable (name); + if (old_ref == 0 && old_var && local_p (old_var) && old_var->context == variable_context) + return (old_var); + + /* local -n foo; local -n foo; is a no-op. */ + if (old_ref && local_p (old_ref) && old_ref->context == variable_context) + return (old_ref); + + /* From here on, we want to use the refvar, not the variable it references */ + if (old_ref) + old_var = old_ref; + + was_tmpvar = old_var && tempvar_p (old_var); + /* If we're making a local variable in a shell function, the temporary env + has already been merged into the function's variable context stack. We + can assume that a temporary var in the same context appears in the same + VAR_CONTEXT and can safely be returned without creating a new variable + (which results in duplicate names in the same VAR_CONTEXT->table */ + /* We can't just test tmpvar_p because variables in the temporary env given + to a shell function appear in the function's local variable VAR_CONTEXT + but retain their tempvar attribute. We want temporary variables that are + found in temporary_env, hence the test for last_table_searched, which is + set in hash_lookup and only (so far) checked here. */ + if (was_tmpvar && old_var->context == variable_context && last_table_searched != temporary_env) + { + VUNSETATTR (old_var, att_invisible); /* XXX */ + /* We still want to flag this variable as local, though, and set things + up so that it gets treated as a local variable. */ + new_var = old_var; + /* Since we found the variable in a temporary environment, this will + succeed. */ + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + goto set_local_var_flags; + + return (old_var); + } + + /* If we want to change to "inherit the old variable's value" semantics, + here is where to save the old value. */ + old_value = was_tmpvar ? value_cell (old_var) : (char *)NULL; + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("make_local_variable: no function context at current scope")); + return ((SHELL_VAR *)NULL); + } + else if (vc->table == 0) + vc->table = hash_create (TEMPENV_HASH_BUCKETS); + + /* Since this is called only from the local/declare/typeset code, we can + call builtin_error here without worry (of course, it will also work + for anything that sets this_command_name). Variables with the `noassign' + attribute may not be made local. The test against old_var's context + level is to disallow local copies of readonly global variables (since I + believe that this could be a security hole). Readonly copies of calling + function local variables are OK. */ + if (old_var && (noassign_p (old_var) || + (readonly_p (old_var) && old_var->context == 0))) + { + if (readonly_p (old_var)) + sh_readonly (name); + else if (noassign_p (old_var)) + builtin_error (_("%s: variable may not be assigned value"), name); +#if 0 + /* Let noassign variables through with a warning */ + if (readonly_p (old_var)) +#endif + return ((SHELL_VAR *)NULL); + } + + if (old_var == 0) + new_var = make_new_variable (name, vc->table); + else + { + new_var = make_new_variable (name, vc->table); + + /* If we found this variable in one of the temporary environments, + inherit its value. Watch to see if this causes problems with + things like `x=4 local x'. XXX - see above for temporary env + variables with the same context level as variable_context */ + /* XXX - we should only do this if the variable is not an array. */ + /* If we want to change the local variable semantics to "inherit + the old variable's value" here is where to set it. And we would + need to use copy_variable (currently unused) to do it for all + possible variable values. */ + if (was_tmpvar) + var_setvalue (new_var, savestring (old_value)); + else if (localvar_inherit || (flags & MKLOC_INHERIT)) + { + /* This may not make sense for nameref variables that are shadowing + variables with the same name, but we don't know that yet. */ +#if defined (ARRAY_VARS) + if (assoc_p (old_var)) + var_setassoc (new_var, assoc_copy (assoc_cell (old_var))); + else if (array_p (old_var)) + var_setarray (new_var, array_copy (array_cell (old_var))); + else if (value_cell (old_var)) +#else + if (value_cell (old_var)) +#endif + var_setvalue (new_var, savestring (value_cell (old_var))); + else + var_setvalue (new_var, (char *)NULL); + } + + if (localvar_inherit || (flags & MKLOC_INHERIT)) + { + /* It doesn't make sense to inherit the nameref attribute */ + new_var->attributes = old_var->attributes & ~att_nameref; + new_var->dynamic_value = old_var->dynamic_value; + new_var->assign_func = old_var->assign_func; + } + else + /* We inherit the export attribute, but no others. */ + new_var->attributes = exported_p (old_var) ? att_exported : 0; + } + +set_local_var_flags: + vc->flags |= VC_HASLOCAL; + + new_var->context = variable_context; + VSETATTR (new_var, att_local); + + if (ifsname (name)) + setifs (new_var); + + /* value_cell will be 0 if localvar_inherit == 0 or there was no old variable + with the same name or the old variable was invisible */ + if (was_tmpvar == 0 && value_cell (new_var) == 0) + VSETATTR (new_var, att_invisible); /* XXX */ + return (new_var); +} + +/* Create a new shell variable with name NAME. */ +static SHELL_VAR * +new_shell_variable (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + entry->name = savestring (name); + var_setvalue (entry, (char *)NULL); + CLEAR_EXPORTSTR (entry); + + entry->dynamic_value = (sh_var_value_func_t *)NULL; + entry->assign_func = (sh_var_assign_func_t *)NULL; + + entry->attributes = 0; + + /* Always assume variables are to be made at toplevel! + make_local_variable has the responsibility of changing the + variable context. */ + entry->context = 0; + + return (entry); +} + +/* Create a new shell variable with name NAME and add it to the hash table + TABLE. */ +static SHELL_VAR * +make_new_variable (name, table) + const char *name; + HASH_TABLE *table; +{ + SHELL_VAR *entry; + BUCKET_CONTENTS *elt; + + entry = new_shell_variable (name); + + /* Make sure we have a shell_variables hash table to add to. */ + if (shell_variables == 0) + create_variable_tables (); + + elt = hash_insert (savestring (name), table, HASH_NOSRCH); + elt->data = (PTR_T)entry; + + return entry; +} + +#if defined (ARRAY_VARS) +SHELL_VAR * +make_new_array_variable (name) + char *name; +{ + SHELL_VAR *entry; + ARRAY *array; + + entry = make_new_variable (name, global_variables->table); + array = array_create (); + + var_setarray (entry, array); + VSETATTR (entry, att_array); + return entry; +} + +SHELL_VAR * +make_local_array_variable (name, flags) + char *name; + int flags; +{ + SHELL_VAR *var; + ARRAY *array; + int assoc_ok; + + assoc_ok = flags & MKLOC_ASSOCOK; + + var = make_local_variable (name, flags & MKLOC_INHERIT); /* XXX for now */ + /* If ASSOC_OK is non-zero, assume that we are ok with letting an assoc + variable return to the caller without converting it. The caller will + either flag an error or do the conversion itself. */ + if (var == 0 || array_p (var) || (assoc_ok && assoc_p (var))) + return var; + + /* Validate any value we inherited from a variable instance at a previous + scope and discard anything that's invalid. */ + if (localvar_inherit && assoc_p (var)) + { + internal_warning (_("%s: cannot inherit value from incompatible type"), name); + VUNSETATTR (var, att_assoc); + dispose_variable_value (var); + array = array_create (); + var_setarray (var, array); + } + else if (localvar_inherit) + var = convert_var_to_array (var); /* XXX */ + else + { + dispose_variable_value (var); + array = array_create (); + var_setarray (var, array); + } + + VSETATTR (var, att_array); + return var; +} + +SHELL_VAR * +make_new_assoc_variable (name) + char *name; +{ + SHELL_VAR *entry; + HASH_TABLE *hash; + + entry = make_new_variable (name, global_variables->table); + hash = assoc_create (ASSOC_HASH_BUCKETS); + + var_setassoc (entry, hash); + VSETATTR (entry, att_assoc); + return entry; +} + +SHELL_VAR * +make_local_assoc_variable (name, flags) + char *name; + int flags; +{ + SHELL_VAR *var; + HASH_TABLE *hash; + int array_ok; + + array_ok = flags & MKLOC_ARRAYOK; + + var = make_local_variable (name, flags & MKLOC_INHERIT); /* XXX for now */ + /* If ARRAY_OK is non-zero, assume that we are ok with letting an array + variable return to the caller without converting it. The caller will + either flag an error or do the conversion itself. */ + if (var == 0 || assoc_p (var) || (array_ok && array_p (var))) + return var; + + /* Validate any value we inherited from a variable instance at a previous + scope and discard anything that's invalid. */ + if (localvar_inherit && array_p (var)) + { + internal_warning (_("%s: cannot inherit value from incompatible type"), name); + VUNSETATTR (var, att_array); + dispose_variable_value (var); + hash = assoc_create (ASSOC_HASH_BUCKETS); + var_setassoc (var, hash); + } + else if (localvar_inherit) + var = convert_var_to_assoc (var); /* XXX */ + else + { + dispose_variable_value (var); + hash = assoc_create (ASSOC_HASH_BUCKETS); + var_setassoc (var, hash); + } + + VSETATTR (var, att_assoc); + return var; +} +#endif + +char * +make_variable_value (var, value, flags) + SHELL_VAR *var; + char *value; + int flags; +{ + char *retval, *oval; + intmax_t lval, rval; + int expok, olen, op; + + /* If this variable has had its type set to integer (via `declare -i'), + then do expression evaluation on it and store the result. The + functions in expr.c (evalexp()) and bind_int_variable() are responsible + for turning off the integer flag if they don't want further + evaluation done. Callers that find it inconvenient to do this can set + the ASS_NOEVAL flag. For the special case of arithmetic expression + evaluation, the caller can set ASS_NOLONGJMP to avoid jumping out to + top_level. */ + if ((flags & ASS_NOEVAL) == 0 && integer_p (var)) + { + if (flags & ASS_APPEND) + { + oval = value_cell (var); + lval = evalexp (oval, 0, &expok); /* ksh93 seems to do this */ + if (expok == 0) + { + if (flags & ASS_NOLONGJMP) + goto make_value; + else + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + } + } + rval = evalexp (value, 0, &expok); + if (expok == 0) + { + if (flags & ASS_NOLONGJMP) + goto make_value; + else + { + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + } + /* This can be fooled if the variable's value changes while evaluating + `rval'. We can change it if we move the evaluation of lval to here. */ + if (flags & ASS_APPEND) + rval += lval; + retval = itos (rval); + } +#if defined (CASEMOD_ATTRS) + else if ((flags & ASS_NOEVAL) == 0 && (capcase_p (var) || uppercase_p (var) || lowercase_p (var))) + { + if (flags & ASS_APPEND) + { + oval = get_variable_value (var); + if (oval == 0) /* paranoia */ + oval = ""; + olen = STRLEN (oval); + retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1); + strcpy (retval, oval); + if (value) + strcpy (retval+olen, value); + } + else if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + op = capcase_p (var) ? CASE_CAPITALIZE + : (uppercase_p (var) ? CASE_UPPER : CASE_LOWER); + oval = sh_modcase (retval, (char *)0, op); + free (retval); + retval = oval; + } +#endif /* CASEMOD_ATTRS */ + else if (value) + { +make_value: + if (flags & ASS_APPEND) + { + oval = get_variable_value (var); + if (oval == 0) /* paranoia */ + oval = ""; + olen = STRLEN (oval); + retval = (char *)xmalloc (olen + (value ? STRLEN (value) : 0) + 1); + strcpy (retval, oval); + if (value) + strcpy (retval+olen, value); + } + else if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + } + else + retval = (char *)NULL; + + return retval; +} + +/* If we can optimize appending to string variables, say so */ +static int +can_optimize_assignment (entry, value, aflags) + SHELL_VAR *entry; + char *value; + int aflags; +{ + if ((aflags & ASS_APPEND) == 0) + return 0; +#if defined (ARRAY_VARS) + if (array_p (entry) || assoc_p (entry)) + return 0; +#endif + if (integer_p (entry) || uppercase_p (entry) || lowercase_p (entry) || capcase_p (entry)) + return 0; + if (readonly_p (entry) || noassign_p (entry)) + return 0; + return 1; +} + +/* right now we optimize appends to string variables */ +static SHELL_VAR * +optimized_assignment (entry, value, aflags) + SHELL_VAR *entry; + char *value; + int aflags; +{ + size_t len, vlen; + char *v, *new; + + v = value_cell (entry); + len = STRLEN (v); + vlen = STRLEN (value); + + new = (char *)xrealloc (v, len + vlen + 8); /* for now */ + if (vlen == 1) + { + new[len] = *value; + new[len+1] = '\0'; + } + else + strcpy (new + len, value); + var_setvalue (entry, new); + return entry; +} + +/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the + temporary environment (but usually is not). HFLAGS controls how NAME + is looked up in TABLE; AFLAGS controls how VALUE is assigned */ +static SHELL_VAR * +bind_variable_internal (name, value, table, hflags, aflags) + const char *name; + char *value; + HASH_TABLE *table; + int hflags, aflags; +{ + char *newval, *tname; + SHELL_VAR *entry, *tentry; + + entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table); + /* Follow the nameref chain here if this is the global variables table */ + if (entry && nameref_p (entry) && (invisible_p (entry) == 0) && table == global_variables->table) + { + entry = find_global_variable (entry->name); + /* Let's see if we have a nameref referencing a variable that hasn't yet + been created. */ + if (entry == 0) + entry = find_variable_last_nameref (name, 0); /* XXX */ + if (entry == 0) /* just in case */ + return (entry); + } + + /* The first clause handles `declare -n ref; ref=x;' or `declare -n ref; + declare -n ref' */ + if (entry && invisible_p (entry) && nameref_p (entry)) + { + if ((aflags & ASS_FORCE) == 0 && value && valid_nameref_value (value, 0) == 0) + { + sh_invalidid (value); + return ((SHELL_VAR *)NULL); + } + goto assign_value; + } + else if (entry && nameref_p (entry)) + { + newval = nameref_cell (entry); /* XXX - newval can't be NULL here */ + if (valid_nameref_value (newval, 0) == 0) + { + sh_invalidid (newval); + return ((SHELL_VAR *)NULL); + } +#if defined (ARRAY_VARS) + /* declare -n foo=x[2] ; foo=bar */ + if (valid_array_reference (newval, 0)) + { + tname = array_variable_name (newval, 0, (char **)0, (int *)0); + if (tname && (tentry = find_variable_noref (tname)) && nameref_p (tentry)) + { + /* nameref variables can't be arrays */ + internal_warning (_("%s: removing nameref attribute"), name_cell (tentry)); + FREE (value_cell (tentry)); /* XXX - bash-4.3 compat */ + var_setvalue (tentry, (char *)NULL); + VUNSETATTR (tentry, att_nameref); + } + free (tname); + + /* entry == nameref variable; tentry == array variable; + newval == x[2]; value = bar + We don't need to call make_variable_value here, since + assign_array_element will eventually do it itself based on + newval and aflags. */ + + entry = assign_array_element (newval, value, aflags|ASS_NAMEREF, (array_eltstate_t *)0); + if (entry == 0) + return entry; + } + else +#endif + { + entry = make_new_variable (newval, table); + var_setvalue (entry, make_variable_value (entry, value, aflags)); + } + } + else if (entry == 0) + { + entry = make_new_variable (name, table); + var_setvalue (entry, make_variable_value (entry, value, aflags)); /* XXX */ + } + else if (entry->assign_func) /* array vars have assign functions now */ + { + if ((readonly_p (entry) && (aflags & ASS_FORCE) == 0) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name_cell (entry)); + return (entry); + } + + INVALIDATE_EXPORTSTR (entry); + newval = (aflags & ASS_APPEND) ? make_variable_value (entry, value, aflags) : value; + if (assoc_p (entry)) + entry = (*(entry->assign_func)) (entry, newval, -1, savestring ("0")); + else if (array_p (entry)) + entry = (*(entry->assign_func)) (entry, newval, 0, 0); + else + entry = (*(entry->assign_func)) (entry, newval, -1, 0); + if (newval != value) + free (newval); + return (entry); + } + else + { +assign_value: + if ((readonly_p (entry) && (aflags & ASS_FORCE) == 0) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name_cell (entry)); + return (entry); + } + + /* Variables which are bound are visible. */ + VUNSETATTR (entry, att_invisible); + + /* If we can optimize the assignment, do so and return. Right now, we + optimize appends to string variables. */ + if (can_optimize_assignment (entry, value, aflags)) + { + INVALIDATE_EXPORTSTR (entry); + optimized_assignment (entry, value, aflags); + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + if (exported_p (entry)) + array_needs_making = 1; + + return (entry); + } + +#if defined (ARRAY_VARS) + if (assoc_p (entry) || array_p (entry)) + newval = make_array_variable_value (entry, 0, "0", value, aflags); + else +#endif + newval = make_variable_value (entry, value, aflags); /* XXX */ + + /* Invalidate any cached export string */ + INVALIDATE_EXPORTSTR (entry); + +#if defined (ARRAY_VARS) + /* XXX -- this bears looking at again -- XXX */ + /* If an existing array variable x is being assigned to with x=b or + `read x' or something of that nature, silently convert it to + x[0]=b or `read x[0]'. */ + if (assoc_p (entry)) + { + assoc_insert (assoc_cell (entry), savestring ("0"), newval); + free (newval); + } + else if (array_p (entry)) + { + array_insert (array_cell (entry), 0, newval); + free (newval); + } + else +#endif + { + FREE (value_cell (entry)); + var_setvalue (entry, newval); + } + } + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + if (exported_p (entry)) + array_needs_making = 1; + + return (entry); +} + +/* Bind a variable NAME to VALUE. This conses up the name + and value strings. If we have a temporary environment, we bind there + first, then we bind into shell_variables. */ + +SHELL_VAR * +bind_variable (name, value, flags) + const char *name; + char *value; + int flags; +{ + SHELL_VAR *v, *nv; + VAR_CONTEXT *vc, *nvc; + + if (shell_variables == 0) + create_variable_tables (); + + /* If we have a temporary environment, look there first for the variable, + and, if found, modify the value there before modifying it in the + shell_variables table. This allows sourced scripts to modify values + given to them in a temporary environment while modifying the variable + value that the caller sees. */ + if (temporary_env && value) /* XXX - can value be null here? */ + bind_tempenv_variable (name, value); + + /* XXX -- handle local variables here. */ + for (vc = shell_variables; vc; vc = vc->down) + { + if (vc_isfuncenv (vc) || vc_isbltnenv (vc)) + { + v = hash_lookup (name, vc->table); + nvc = vc; + if (v && nameref_p (v)) + { + /* This starts at the context where we found the nameref. If we + want to start the name resolution over again at the original + context, this is where we need to change it */ + nv = find_variable_nameref_context (v, vc, &nvc); + if (nv == 0) + { + nv = find_variable_last_nameref_context (v, vc, &nvc); + if (nv && nameref_p (nv)) + { + /* If this nameref variable doesn't have a value yet, + set the value. Otherwise, assign using the value as + normal. */ + if (nameref_cell (nv) == 0) + return (bind_variable_internal (nv->name, value, nvc->table, 0, flags)); +#if defined (ARRAY_VARS) + else if (valid_array_reference (nameref_cell (nv), 0)) + return (assign_array_element (nameref_cell (nv), value, flags, (array_eltstate_t *)0)); + else +#endif + return (bind_variable_internal (nameref_cell (nv), value, nvc->table, 0, flags)); + } + else if (nv == &nameref_maxloop_value) + { + internal_warning (_("%s: circular name reference"), v->name); + return (bind_global_variable (v->name, value, flags)); + } + else + v = nv; + } + else if (nv == &nameref_maxloop_value) + { + internal_warning (_("%s: circular name reference"), v->name); + return (bind_global_variable (v->name, value, flags)); + } + else + v = nv; + } + if (v) + return (bind_variable_internal (v->name, value, nvc->table, 0, flags)); + } + } + /* bind_variable_internal will handle nameref resolution in this case */ + return (bind_variable_internal (name, value, global_variables->table, 0, flags)); +} + +SHELL_VAR * +bind_global_variable (name, value, flags) + const char *name; + char *value; + int flags; +{ + if (shell_variables == 0) + create_variable_tables (); + + /* bind_variable_internal will handle nameref resolution in this case */ + return (bind_variable_internal (name, value, global_variables->table, 0, flags)); +} + +static SHELL_VAR * +bind_invalid_envvar (name, value, flags) + const char *name; + char *value; + int flags; +{ + if (invalid_env == 0) + invalid_env = hash_create (64); /* XXX */ + return (bind_variable_internal (name, value, invalid_env, HASH_NOSRCH, flags)); +} + +/* Make VAR, a simple shell variable, have value VALUE. Once assigned a + value, variables are no longer invisible. This is a duplicate of part + of the internals of bind_variable. If the variable is exported, or + all modified variables should be exported, mark the variable for export + and note that the export environment needs to be recreated. */ +SHELL_VAR * +bind_variable_value (var, value, aflags) + SHELL_VAR *var; + char *value; + int aflags; +{ + char *t; + int invis; + + invis = invisible_p (var); + VUNSETATTR (var, att_invisible); + + if (var->assign_func) + { + /* If we're appending, we need the old value, so use + make_variable_value */ + t = (aflags & ASS_APPEND) ? make_variable_value (var, value, aflags) : value; + (*(var->assign_func)) (var, t, -1, 0); + if (t != value && t) + free (t); + } + else + { + t = make_variable_value (var, value, aflags); + if ((aflags & (ASS_NAMEREF|ASS_FORCE)) == ASS_NAMEREF && check_selfref (name_cell (var), t, 0)) + { + if (variable_context) + internal_warning (_("%s: circular name reference"), name_cell (var)); + else + { + internal_error (_("%s: nameref variable self references not allowed"), name_cell (var)); + free (t); + if (invis) + VSETATTR (var, att_invisible); /* XXX */ + return ((SHELL_VAR *)NULL); + } + } + if ((aflags & ASS_NAMEREF) && (valid_nameref_value (t, 0) == 0)) + { + free (t); + if (invis) + VSETATTR (var, att_invisible); /* XXX */ + return ((SHELL_VAR *)NULL); + } + FREE (value_cell (var)); + var_setvalue (var, t); + } + + INVALIDATE_EXPORTSTR (var); + + if (mark_modified_vars) + VSETATTR (var, att_exported); + + if (exported_p (var)) + array_needs_making = 1; + + return (var); +} + +/* Bind/create a shell variable with the name LHS to the RHS. + This creates or modifies a variable such that it is an integer. + + This used to be in expr.c, but it is here so that all of the + variable binding stuff is localized. Since we don't want any + recursive evaluation from bind_variable() (possible without this code, + since bind_variable() calls the evaluator for variables with the integer + attribute set), we temporarily turn off the integer attribute for each + variable we set here, then turn it back on after binding as necessary. */ + +SHELL_VAR * +bind_int_variable (lhs, rhs, flags) + char *lhs, *rhs; + int flags; +{ + register SHELL_VAR *v; + int isint, isarr, implicitarray, vflags, avflags; + + isint = isarr = implicitarray = 0; +#if defined (ARRAY_VARS) + /* Don't rely on VA_NOEXPAND being 1, set it explicitly */ + vflags = (flags & ASS_NOEXPAND) ? VA_NOEXPAND : 0; + if (flags & ASS_ONEWORD) + vflags |= VA_ONEWORD; + if (valid_array_reference (lhs, vflags)) + { + isarr = 1; + avflags = 0; + /* Common code to translate between assignment and reference flags. */ + if (flags & ASS_NOEXPAND) + avflags |= AV_NOEXPAND; + if (flags & ASS_ONEWORD) + avflags |= AV_ONEWORD; + v = array_variable_part (lhs, avflags, (char **)0, (int *)0); + } + else if (legal_identifier (lhs) == 0) + { + sh_invalidid (lhs); + return ((SHELL_VAR *)NULL); + } + else +#endif + v = find_variable (lhs); + + if (v) + { + isint = integer_p (v); + VUNSETATTR (v, att_integer); +#if defined (ARRAY_VARS) + if (array_p (v) && isarr == 0) + implicitarray = 1; +#endif + } + +#if defined (ARRAY_VARS) + if (isarr) + v = assign_array_element (lhs, rhs, flags, (array_eltstate_t *)0); + else if (implicitarray) + v = bind_array_variable (lhs, 0, rhs, 0); /* XXX - check on flags */ + else +#endif + v = bind_variable (lhs, rhs, 0); /* why not use bind_variable_value? */ + + if (v) + { + if (isint) + VSETATTR (v, att_integer); + VUNSETATTR (v, att_invisible); + } + + if (v && nameref_p (v)) + internal_warning (_("%s: assigning integer to name reference"), lhs); + + return (v); +} + +SHELL_VAR * +bind_var_to_int (var, val, flags) + char *var; + intmax_t val; + int flags; +{ + char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p; + + p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0); + return (bind_int_variable (var, p, flags)); +} + +/* Do a function binding to a variable. You pass the name and + the command to bind to. This conses the name and command. */ +SHELL_VAR * +bind_function (name, value) + const char *name; + COMMAND *value; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry == 0) + { + BUCKET_CONTENTS *elt; + + elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH); + entry = new_shell_variable (name); + elt->data = (PTR_T)entry; + } + else + INVALIDATE_EXPORTSTR (entry); + + if (var_isset (entry)) + dispose_command (function_cell (entry)); + + if (value) + var_setfunc (entry, copy_command (value)); + else + var_setfunc (entry, 0); + + VSETATTR (entry, att_function); + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + VUNSETATTR (entry, att_invisible); /* Just to be sure */ + + if (exported_p (entry)) + array_needs_making = 1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + return (entry); +} + +#if defined (DEBUGGER) +/* Bind a function definition, which includes source file and line number + information in addition to the command, into the FUNCTION_DEF hash table. + If (FLAGS & 1), overwrite any existing definition. If FLAGS == 0, leave + any existing definition alone. */ +void +bind_function_def (name, value, flags) + const char *name; + FUNCTION_DEF *value; + int flags; +{ + FUNCTION_DEF *entry; + BUCKET_CONTENTS *elt; + COMMAND *cmd; + + entry = find_function_def (name); + if (entry && (flags & 1)) + { + dispose_function_def_contents (entry); + entry = copy_function_def_contents (value, entry); + } + else if (entry) + return; + else + { + cmd = value->command; + value->command = 0; + entry = copy_function_def (value); + value->command = cmd; + + elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH); + elt->data = (PTR_T *)entry; + } +} +#endif /* DEBUGGER */ + +/* Add STRING, which is of the form foo=bar, to the temporary environment + HASH_TABLE (temporary_env). The functions in execute_cmd.c are + responsible for moving the main temporary env to one of the other + temporary environments. The expansion code in subst.c calls this. */ +int +assign_in_env (word, flags) + WORD_DESC *word; + int flags; +{ + int offset, aflags; + char *name, *temp, *value, *newname; + SHELL_VAR *var; + const char *string; + + string = word->word; + + aflags = 0; + offset = assignment (string, 0); + newname = name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + name[offset] = 0; + + /* don't ignore the `+' when assigning temporary environment */ + if (name[offset - 1] == '+') + { + name[offset - 1] = '\0'; + aflags |= ASS_APPEND; + } + + if (legal_identifier (name) == 0) + { + sh_invalidid (name); + free (name); + return (0); + } + + var = find_variable (name); + if (var == 0) + { + var = find_variable_last_nameref (name, 1); + /* If we're assigning a value to a nameref variable in the temp + environment, and the value of the nameref is valid for assignment, + but the variable does not already exist, assign to the nameref + target and add the target to the temporary environment. This is + what ksh93 does */ + /* We use 2 in the call to valid_nameref_value because we don't want + to allow array references here at all (newname will be used to + create a variable directly below) */ + if (var && nameref_p (var) && valid_nameref_value (nameref_cell (var), 2)) + { + newname = nameref_cell (var); + var = 0; /* don't use it for append */ + } + } + else + newname = name_cell (var); /* no-op if not nameref */ + + if (var && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + err_readonly (name); + free (name); + return (0); + } + temp = name + offset + 1; + + value = expand_assignment_string_to_string (temp, 0); + + if (var && (aflags & ASS_APPEND)) + { + if (value == 0) + { + value = (char *)xmalloc (1); /* like do_assignment_internal */ + value[0] = '\0'; + } + temp = make_variable_value (var, value, aflags); + FREE (value); + value = temp; + } + } + + if (temporary_env == 0) + temporary_env = hash_create (TEMPENV_HASH_BUCKETS); + + var = hash_lookup (newname, temporary_env); + if (var == 0) + var = make_new_variable (newname, temporary_env); + else + FREE (value_cell (var)); + + if (value == 0) + { + value = (char *)xmalloc (1); /* see above */ + value[0] = '\0'; + } + + var_setvalue (var, value); + var->attributes |= (att_exported|att_tempvar); + var->context = variable_context; /* XXX */ + + INVALIDATE_EXPORTSTR (var); + var->exportstr = mk_env_string (newname, value, 0); + + array_needs_making = 1; + + if (flags) + { + if (STREQ (newname, "POSIXLY_CORRECT") || STREQ (newname, "POSIX_PEDANDTIC")) + save_posix_options (); /* XXX one level of saving right now */ + stupidly_hack_special_variables (newname); + } + + if (echo_command_at_execute) + /* The Korn shell prints the `+ ' in front of assignment statements, + so we do too. */ + xtrace_print_assignment (name, value, 0, 1); + + free (name); + return 1; +} + +/* **************************************************************** */ +/* */ +/* Copying variables */ +/* */ +/* **************************************************************** */ + +#ifdef INCLUDE_UNUSED +/* Copy VAR to a new data structure and return that structure. */ +SHELL_VAR * +copy_variable (var) + SHELL_VAR *var; +{ + SHELL_VAR *copy = (SHELL_VAR *)NULL; + + if (var) + { + copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + copy->attributes = var->attributes; + copy->name = savestring (var->name); + + if (function_p (var)) + var_setfunc (copy, copy_command (function_cell (var))); +#if defined (ARRAY_VARS) + else if (array_p (var)) + var_setarray (copy, array_copy (array_cell (var))); + else if (assoc_p (var)) + var_setassoc (copy, assoc_copy (assoc_cell (var))); +#endif + else if (nameref_cell (var)) /* XXX - nameref */ + var_setref (copy, savestring (nameref_cell (var))); + else if (value_cell (var)) /* XXX - nameref */ + var_setvalue (copy, savestring (value_cell (var))); + else + var_setvalue (copy, (char *)NULL); + + copy->dynamic_value = var->dynamic_value; + copy->assign_func = var->assign_func; + + copy->exportstr = COPY_EXPORTSTR (var); + + copy->context = var->context; + } + return (copy); +} +#endif + +/* **************************************************************** */ +/* */ +/* Deleting and unsetting variables */ +/* */ +/* **************************************************************** */ + +/* Dispose of the information attached to VAR. */ +static void +dispose_variable_value (var) + SHELL_VAR *var; +{ + if (function_p (var)) + dispose_command (function_cell (var)); +#if defined (ARRAY_VARS) + else if (array_p (var)) + array_dispose (array_cell (var)); + else if (assoc_p (var)) + assoc_dispose (assoc_cell (var)); +#endif + else if (nameref_p (var)) + FREE (nameref_cell (var)); + else + FREE (value_cell (var)); +} + +void +dispose_variable (var) + SHELL_VAR *var; +{ + if (var == 0) + return; + + if (nofree_p (var) == 0) + dispose_variable_value (var); + + FREE_EXPORTSTR (var); + + free (var->name); + + if (exported_p (var)) + array_needs_making = 1; + + free (var); +} + +/* Unset the shell variable referenced by NAME. Unsetting a nameref variable + unsets the variable it resolves to but leaves the nameref alone. */ +int +unbind_variable (name) + const char *name; +{ + SHELL_VAR *v, *nv; + int r; + + v = var_lookup (name, shell_variables); + nv = (v && nameref_p (v)) ? find_variable_nameref (v) : (SHELL_VAR *)NULL; + + r = nv ? makunbound (nv->name, shell_variables) : makunbound (name, shell_variables); + return r; +} + +/* Unbind NAME, where NAME is assumed to be a nameref variable */ +int +unbind_nameref (name) + const char *name; +{ + SHELL_VAR *v; + + v = var_lookup (name, shell_variables); + if (v && nameref_p (v)) + return makunbound (name, shell_variables); + return 0; +} + +/* Unbind the first instance of NAME, whether it's a nameref or not */ +int +unbind_variable_noref (name) + const char *name; +{ + SHELL_VAR *v; + + v = var_lookup (name, shell_variables); + if (v) + return makunbound (name, shell_variables); + return 0; +} + +int +unbind_global_variable (name) + const char *name; +{ + SHELL_VAR *v, *nv; + int r; + + v = var_lookup (name, global_variables); + /* This starts at the current scope, just like find_global_variable; should we + use find_global_variable_nameref here? */ + nv = (v && nameref_p (v)) ? find_variable_nameref (v) : (SHELL_VAR *)NULL; + + r = nv ? makunbound (nv->name, shell_variables) : makunbound (name, global_variables); + return r; +} + +int +unbind_global_variable_noref (name) + const char *name; +{ + SHELL_VAR *v; + + v = var_lookup (name, global_variables); + if (v) + return makunbound (name, global_variables); + return 0; +} + +int +check_unbind_variable (name) + const char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v && readonly_p (v)) + { + internal_error (_("%s: cannot unset: readonly %s"), name, "variable"); + return -2; + } + else if (v && non_unsettable_p (v)) + { + internal_error (_("%s: cannot unset"), name); + return -2; + } + return (unbind_variable (name)); +} + +/* Unset the shell function named NAME. */ +int +unbind_func (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + SHELL_VAR *func; + + elt = hash_remove (name, shell_functions, 0); + + if (elt == 0) + return -1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + func = (SHELL_VAR *)elt->data; + if (func) + { + if (exported_p (func)) + array_needs_making++; + dispose_variable (func); + } + + free (elt->key); + free (elt); + + return 0; +} + +#if defined (DEBUGGER) +int +unbind_function_def (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + FUNCTION_DEF *funcdef; + + elt = hash_remove (name, shell_function_defs, 0); + + if (elt == 0) + return -1; + + funcdef = (FUNCTION_DEF *)elt->data; + if (funcdef) + dispose_function_def (funcdef); + + free (elt->key); + free (elt); + + return 0; +} +#endif /* DEBUGGER */ + +int +delete_var (name, vc) + const char *name; + VAR_CONTEXT *vc; +{ + BUCKET_CONTENTS *elt; + SHELL_VAR *old_var; + VAR_CONTEXT *v; + + for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) + if (elt = hash_remove (name, v->table, 0)) + break; + + if (elt == 0) + return (-1); + + old_var = (SHELL_VAR *)elt->data; + free (elt->key); + free (elt); + + dispose_variable (old_var); + return (0); +} + +/* Make the variable associated with NAME go away. HASH_LIST is the + hash table from which this variable should be deleted (either + shell_variables or shell_functions). + Returns non-zero if the variable couldn't be found. */ +int +makunbound (name, vc) + const char *name; + VAR_CONTEXT *vc; +{ + BUCKET_CONTENTS *elt, *new_elt; + SHELL_VAR *old_var; + VAR_CONTEXT *v; + char *t; + + for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) + if (elt = hash_remove (name, v->table, 0)) + break; + + if (elt == 0) + return (-1); + + old_var = (SHELL_VAR *)elt->data; + + if (old_var && exported_p (old_var)) + array_needs_making++; + + /* If we're unsetting a local variable and we're still executing inside + the function, just mark the variable as invisible. The function + eventually called by pop_var_context() will clean it up later. This + must be done so that if the variable is subsequently assigned a new + value inside the function, the `local' attribute is still present. + We also need to add it back into the correct hash table. */ + if (old_var && local_p (old_var) && + (old_var->context == variable_context || (localvar_unset && old_var->context < variable_context))) + { + if (nofree_p (old_var)) + var_setvalue (old_var, (char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (old_var)) + array_dispose (array_cell (old_var)); + else if (assoc_p (old_var)) + assoc_dispose (assoc_cell (old_var)); +#endif + else if (nameref_p (old_var)) + FREE (nameref_cell (old_var)); + else + FREE (value_cell (old_var)); + /* Reset the attributes. Preserve the export attribute if the variable + came from a temporary environment. Make sure it stays local, and + make it invisible. */ + old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0; + VSETATTR (old_var, att_local); + VSETATTR (old_var, att_invisible); + var_setvalue (old_var, (char *)NULL); + INVALIDATE_EXPORTSTR (old_var); + + new_elt = hash_insert (savestring (old_var->name), v->table, 0); + new_elt->data = (PTR_T)old_var; + stupidly_hack_special_variables (old_var->name); + + free (elt->key); + free (elt); + return (0); + } + + /* Have to save a copy of name here, because it might refer to + old_var->name. If so, stupidly_hack_special_variables will + reference freed memory. */ + t = savestring (name); + + free (elt->key); + free (elt); + + dispose_variable (old_var); + stupidly_hack_special_variables (t); + free (t); + + return (0); +} + +/* Get rid of all of the variables in the current context. */ +void +kill_all_local_variables () +{ + VAR_CONTEXT *vc; + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + if (vc == 0) + return; /* XXX */ + + if (vc->table && vc_haslocals (vc)) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + vc->table = (HASH_TABLE *)NULL; +} + +static void +free_variable_hash_data (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + dispose_variable (var); +} + +/* Delete the entire contents of the hash table. */ +void +delete_all_variables (hashed_vars) + HASH_TABLE *hashed_vars; +{ + hash_flush (hashed_vars, free_variable_hash_data); +} + +/* **************************************************************** */ +/* */ +/* Setting variable attributes */ +/* */ +/* **************************************************************** */ + +#define FIND_OR_MAKE_VARIABLE(name, entry) \ + do \ + { \ + entry = find_variable (name); \ + if (!entry) \ + { \ + entry = bind_variable (name, "", 0); \ + if (entry) entry->attributes |= att_invisible; \ + } \ + } \ + while (0) + +/* Make the variable associated with NAME be readonly. + If NAME does not exist yet, create it. */ +void +set_var_read_only (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + VSETATTR (entry, att_readonly); +} + +#ifdef INCLUDE_UNUSED +/* Make the function associated with NAME be readonly. + If NAME does not exist, we just punt, like auto_export code below. */ +void +set_func_read_only (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + VSETATTR (entry, att_readonly); +} + +/* Make the variable associated with NAME be auto-exported. + If NAME does not exist yet, create it. */ +void +set_var_auto_export (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + set_auto_export (entry); +} + +/* Make the function associated with NAME be auto-exported. */ +void +set_func_auto_export (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + set_auto_export (entry); +} +#endif + +/* **************************************************************** */ +/* */ +/* Creating lists of variables */ +/* */ +/* **************************************************************** */ + +static VARLIST * +vlist_alloc (nentries) + int nentries; +{ + VARLIST *vlist; + + vlist = (VARLIST *)xmalloc (sizeof (VARLIST)); + vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *)); + vlist->list_size = nentries; + vlist->list_len = 0; + vlist->list[0] = (SHELL_VAR *)NULL; + + return vlist; +} + +static VARLIST * +vlist_realloc (vlist, n) + VARLIST *vlist; + int n; +{ + if (vlist == 0) + return (vlist = vlist_alloc (n)); + if (n > vlist->list_size) + { + vlist->list_size = n; + vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *)); + } + return vlist; +} + +static void +vlist_add (vlist, var, flags) + VARLIST *vlist; + SHELL_VAR *var; + int flags; +{ + register int i; + + for (i = 0; i < vlist->list_len; i++) + if (STREQ (var->name, vlist->list[i]->name)) + break; + if (i < vlist->list_len) + return; + + if (i >= vlist->list_size) + vlist = vlist_realloc (vlist, vlist->list_size + 16); + + vlist->list[vlist->list_len++] = var; + vlist->list[vlist->list_len] = (SHELL_VAR *)NULL; +} + +/* Map FUNCTION over the variables in VAR_HASH_TABLE. Return an array of the + variables for which FUNCTION returns a non-zero value. A NULL value + for FUNCTION means to use all variables. */ +SHELL_VAR ** +map_over (function, vc) + sh_var_map_func_t *function; + VAR_CONTEXT *vc; +{ + VAR_CONTEXT *v; + VARLIST *vlist; + SHELL_VAR **ret; + int nentries; + + for (nentries = 0, v = vc; v; v = v->down) + nentries += HASH_ENTRIES (v->table); + + if (nentries == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (nentries); + + for (v = vc; v; v = v->down) + flatten (v->table, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +SHELL_VAR ** +map_over_funcs (function) + sh_var_map_func_t *function; +{ + VARLIST *vlist; + SHELL_VAR **ret; + + if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0) + return ((SHELL_VAR **)NULL); + + vlist = vlist_alloc (HASH_ENTRIES (shell_functions)); + + flatten (shell_functions, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those + elements for which FUNC succeeds to VLIST->list. FLAGS is reserved + for future use. Only unique names are added to VLIST. If FUNC is + NULL, each variable in VAR_HASH_TABLE is added to VLIST. If VLIST is + NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE. If VLIST + and FUNC are both NULL, nothing happens. */ +static void +flatten (var_hash_table, func, vlist, flags) + HASH_TABLE *var_hash_table; + sh_var_map_func_t *func; + VARLIST *vlist; + int flags; +{ + register int i; + register BUCKET_CONTENTS *tlist; + int r; + SHELL_VAR *var; + + if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0)) + return; + + for (i = 0; i < var_hash_table->nbuckets; i++) + { + for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next) + { + var = (SHELL_VAR *)tlist->data; + + r = func ? (*func) (var) : 1; + if (r && vlist) + vlist_add (vlist, var, flags); + } + } +} + +void +sort_variables (array) + SHELL_VAR **array; +{ + qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp); +} + +static int +qsort_var_comp (var1, var2) + SHELL_VAR **var1, **var2; +{ + int result; + + if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0) + result = strcmp ((*var1)->name, (*var2)->name); + + return (result); +} + +/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +vapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over (func, shell_variables); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +fapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over_funcs (func); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Create a NULL terminated array of all the shell variables. */ +SHELL_VAR ** +all_shell_variables () +{ + return (vapply ((sh_var_map_func_t *)NULL)); +} + +/* Create a NULL terminated array of all the shell functions. */ +SHELL_VAR ** +all_shell_functions () +{ + return (fapply ((sh_var_map_func_t *)NULL)); +} + +static int +visible_var (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0); +} + +SHELL_VAR ** +all_visible_functions () +{ + return (fapply (visible_var)); +} + +SHELL_VAR ** +all_visible_variables () +{ + return (vapply (visible_var)); +} + +/* Return non-zero if the variable VAR is visible and exported. Array + variables cannot be exported. */ +static int +visible_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && exported_p (var)); +} + +/* Candidate variables for the export environment are either valid variables + with the export attribute or invalid variables inherited from the initial + environment and simply passed through. */ +static int +export_environment_candidate (var) + SHELL_VAR *var; +{ + return (exported_p (var) && (invisible_p (var) == 0 || imported_p (var))); +} + +/* Return non-zero if VAR is a local variable in the current context and + is exported. */ +static int +local_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var)); +} + +SHELL_VAR ** +all_exported_variables () +{ + return (vapply (visible_and_exported)); +} + +SHELL_VAR ** +local_exported_variables () +{ + return (vapply (local_and_exported)); +} + +static int +variable_in_context (var) + SHELL_VAR *var; +{ + return (local_p (var) && var->context == variable_context); +} + +static int +visible_variable_in_context (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context); +} + +SHELL_VAR ** +all_local_variables (visible_only) + int visible_only; +{ + VARLIST *vlist; + SHELL_VAR **ret; + VAR_CONTEXT *vc; + + vc = shell_variables; + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("all_local_variables: no function context at current scope")); + return (SHELL_VAR **)NULL; + } + if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (HASH_ENTRIES (vc->table)); + + if (visible_only) + flatten (vc->table, visible_variable_in_context, vlist, 0); + else + flatten (vc->table, variable_in_context, vlist, 0); + + ret = vlist->list; + free (vlist); + if (ret) + sort_variables (ret); + return ret; +} + +#if defined (ARRAY_VARS) +/* Return non-zero if the variable VAR is visible and an array. */ +static int +visible_array_vars (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && (array_p (var) || assoc_p (var))); +} + +SHELL_VAR ** +all_array_variables () +{ + return (vapply (visible_array_vars)); +} +#endif /* ARRAY_VARS */ + +char ** +all_variables_matching_prefix (prefix) + const char *prefix; +{ + SHELL_VAR **varlist; + char **rlist; + int vind, rind, plen; + + plen = STRLEN (prefix); + varlist = all_visible_variables (); + for (vind = 0; varlist && varlist[vind]; vind++) + ; + if (varlist == 0 || vind == 0) + return ((char **)NULL); + rlist = strvec_create (vind + 1); + for (vind = rind = 0; varlist[vind]; vind++) + { + if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen)) + rlist[rind++] = savestring (varlist[vind]->name); + } + rlist[rind] = (char *)0; + free (varlist); + + return rlist; +} + +/* **************************************************************** */ +/* */ +/* Managing temporary variable scopes */ +/* */ +/* **************************************************************** */ + +/* Make variable NAME have VALUE in the temporary environment. */ +static SHELL_VAR * +bind_tempenv_variable (name, value) + const char *name; + char *value; +{ + SHELL_VAR *var; + + var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL; + + if (var) + { + FREE (value_cell (var)); + var_setvalue (var, savestring (value)); + INVALIDATE_EXPORTSTR (var); + } + + return (var); +} + +/* Find a variable in the temporary environment that is named NAME. + Return the SHELL_VAR *, or NULL if not found. */ +SHELL_VAR * +find_tempenv_variable (name) + const char *name; +{ + return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL); +} + +char **tempvar_list; +int tvlist_ind; + +/* Take a variable from an assignment statement preceding a posix special + builtin (including `return') and create a global variable from it. This + is called from merge_temporary_env, which is only called when in posix + mode. */ +static void +push_posix_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + HASH_TABLE *binding_table; + + var = (SHELL_VAR *)data; + + /* Just like do_assignment_internal(). This makes assignments preceding + special builtins act like standalone assignment statements when in + posix mode, satisfying the posix requirement that this affect the + "current execution environment." */ + v = bind_variable (var->name, value_cell (var), ASS_FORCE|ASS_NOLONGJMP); + + /* XXX - do we need to worry about array variables here? */ + + /* If this modifies an existing local variable, v->context will be non-zero. + If it comes back with v->context == 0, we bound at the global context. + Set binding_table appropriately. It doesn't matter whether it's correct + if the variable is local, only that it's not global_variables->table */ + binding_table = v->context ? shell_variables->table : global_variables->table; + + /* global variables are no longer temporary and don't need propagating. */ + if (v->context == 0) + var->attributes &= ~(att_tempvar|att_propagate); + + if (v) + { + v->attributes |= var->attributes; /* preserve tempvar attribute if appropriate */ + /* If we don't bind a local variable, propagate the value. If we bind a + local variable (the "current execution environment"), keep it as local + and don't propagate it to the calling environment. */ + if (v->context > 0 && local_p (v) == 0) + v->attributes |= att_propagate; + else + v->attributes &= ~att_propagate; + } + + if (find_special_var (var->name) >= 0) + tempvar_list[tvlist_ind++] = savestring (var->name); + + dispose_variable (var); +} + +/* Push the variable described by (SHELL_VAR *)DATA down to the next + variable context from the temporary environment. This can be called + from one context: + 1. propagate_temp_var: which is called to propagate variables in + assignments like `var=value declare -x var' to the surrounding + scope. + + In this case, the variable should have the att_propagate flag set and + we can create variables in the current scope. +*/ +static void +push_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + HASH_TABLE *binding_table; + + var = (SHELL_VAR *)data; + + binding_table = shell_variables->table; + if (binding_table == 0) + { + if (shell_variables == global_variables) + /* shouldn't happen */ + binding_table = shell_variables->table = global_variables->table = hash_create (VARIABLES_HASH_BUCKETS); + else + binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS); + } + + v = bind_variable_internal (var->name, value_cell (var), binding_table, 0, ASS_FORCE|ASS_NOLONGJMP); + + /* XXX - should we set the context here? It shouldn't matter because of how + assign_in_env works, but we do it anyway. */ + if (v) + v->context = shell_variables->scope; + + if (binding_table == global_variables->table) /* XXX */ + var->attributes &= ~(att_tempvar|att_propagate); + else + { + var->attributes |= att_propagate; /* XXX - propagate more than once? */ + if (binding_table == shell_variables->table) + shell_variables->flags |= VC_HASTMPVAR; + } + if (v) + v->attributes |= var->attributes; + + if (find_special_var (var->name) >= 0) + tempvar_list[tvlist_ind++] = savestring (var->name); + + dispose_variable (var); +} + +/* Take a variable described by DATA and push it to the surrounding scope if + the PROPAGATE attribute is set. That gets set by push_temp_var if we are + taking a variable like `var=value declare -x var' and propagating it to + the enclosing scope. */ +static void +propagate_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + if (tempvar_p (var) && (var->attributes & att_propagate)) + push_temp_var (data); + else + { + if (find_special_var (var->name) >= 0) + tempvar_list[tvlist_ind++] = savestring (var->name); + dispose_variable (var); + } +} + +/* Free the storage used in the hash table for temporary + environment variables. PUSHF is a function to be called + to free each hash table entry. It takes care of pushing variables + to previous scopes if appropriate. PUSHF stores names of variables + that require special handling (e.g., IFS) on tempvar_list, so this + function can call stupidly_hack_special_variables on all the + variables in the list when the temporary hash table is destroyed. */ +static void +dispose_temporary_env (pushf) + sh_free_func_t *pushf; +{ + int i; + HASH_TABLE *disposer; + + tempvar_list = strvec_create (HASH_ENTRIES (temporary_env) + 1); + tempvar_list[tvlist_ind = 0] = 0; + + disposer = temporary_env; + temporary_env = (HASH_TABLE *)NULL; + + hash_flush (disposer, pushf); + hash_dispose (disposer); + + tempvar_list[tvlist_ind] = 0; + + array_needs_making = 1; + + for (i = 0; i < tvlist_ind; i++) + stupidly_hack_special_variables (tempvar_list[i]); + + strvec_dispose (tempvar_list); + tempvar_list = 0; + tvlist_ind = 0; +} + +void +dispose_used_env_vars () +{ + if (temporary_env) + { + dispose_temporary_env (propagate_temp_var); + maybe_make_export_env (); + } +} + +/* Take all of the shell variables in the temporary environment HASH_TABLE + and make shell variables from them at the current variable context. + Right now, this is only called in Posix mode to implement the historical + accident of creating global variables from assignment statements preceding + special builtins, but we check in case this acquires another caller later. */ +void +merge_temporary_env () +{ + if (temporary_env) + dispose_temporary_env (posixly_correct ? push_posix_temp_var : push_temp_var); +} + +/* Temporary function to use if we want to separate function and special + builtin behavior. */ +void +merge_function_temporary_env () +{ + if (temporary_env) + dispose_temporary_env (push_temp_var); +} + +void +flush_temporary_env () +{ + if (temporary_env) + { + hash_flush (temporary_env, free_variable_hash_data); + hash_dispose (temporary_env); + temporary_env = (HASH_TABLE *)NULL; + } +} + +/* **************************************************************** */ +/* */ +/* Creating and manipulating the environment */ +/* */ +/* **************************************************************** */ + +static inline char * +mk_env_string (name, value, attributes) + const char *name, *value; + int attributes; +{ + size_t name_len, value_len; + char *p, *q, *t; + int isfunc, isarray; + + name_len = strlen (name); + value_len = STRLEN (value); + + isfunc = attributes & att_function; +#if defined (ARRAY_VARS) && defined (ARRAY_EXPORT) + isarray = attributes & (att_array|att_assoc); +#endif + + /* If we are exporting a shell function, construct the encoded function + name. */ + if (isfunc && value) + { + p = (char *)xmalloc (BASHFUNC_PREFLEN + name_len + BASHFUNC_SUFFLEN + value_len + 2); + q = p; + memcpy (q, BASHFUNC_PREFIX, BASHFUNC_PREFLEN); + q += BASHFUNC_PREFLEN; + memcpy (q, name, name_len); + q += name_len; + memcpy (q, BASHFUNC_SUFFIX, BASHFUNC_SUFFLEN); + q += BASHFUNC_SUFFLEN; + } +#if defined (ARRAY_VARS) && defined (ARRAY_EXPORT) + else if (isarray && value) + { + if (attributes & att_assoc) + p = (char *)xmalloc (BASHASSOC_PREFLEN + name_len + BASHASSOC_SUFFLEN + value_len + 2); + else + p = (char *)xmalloc (BASHARRAY_PREFLEN + name_len + BASHARRAY_SUFFLEN + value_len + 2); + q = p; + if (attributes & att_assoc) + { + memcpy (q, BASHASSOC_PREFIX, BASHASSOC_PREFLEN); + q += BASHASSOC_PREFLEN; + } + else + { + memcpy (q, BASHARRAY_PREFIX, BASHARRAY_PREFLEN); + q += BASHARRAY_PREFLEN; + } + memcpy (q, name, name_len); + q += name_len; + /* These are actually the same currently */ + if (attributes & att_assoc) + { + memcpy (q, BASHASSOC_SUFFIX, BASHASSOC_SUFFLEN); + q += BASHARRAY_SUFFLEN; + } + else + { + memcpy (q, BASHARRAY_SUFFIX, BASHARRAY_SUFFLEN); + q += BASHARRAY_SUFFLEN; + } + } +#endif + else + { + p = (char *)xmalloc (2 + name_len + value_len); + memcpy (p, name, name_len); + q = p + name_len; + } + + q[0] = '='; + if (value && *value) + { + if (isfunc) + { + t = dequote_escapes (value); + value_len = STRLEN (t); + memcpy (q + 1, t, value_len + 1); + free (t); + } + else + memcpy (q + 1, value, value_len + 1); + } + else + q[1] = '\0'; + + return (p); +} + +#ifdef DEBUG +/* Debugging */ +static int +valid_exportstr (v) + SHELL_VAR *v; +{ + char *s; + + s = v->exportstr; + if (s == 0) + { + internal_error (_("%s has null exportstr"), v->name); + return (0); + } + if (legal_variable_starter ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + for (s = v->exportstr + 1; s && *s; s++) + { + if (*s == '=') + break; + if (legal_variable_char ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + } + if (*s != '=') + { + internal_error (_("no `=' in exportstr for %s"), v->name); + return (0); + } + return (1); +} +#endif + +#if defined (ARRAY_VARS) +# define USE_EXPORTSTR (value == var->exportstr && array_p (var) == 0 && assoc_p (var) == 0) +#else +# define USE_EXPORTSTR (value == var->exportstr) +#endif + +static char ** +make_env_array_from_var_list (vars) + SHELL_VAR **vars; +{ + register int i, list_index; + register SHELL_VAR *var; + char **list, *value; + + list = strvec_create ((1 + strvec_len ((char **)vars))); + + for (i = 0, list_index = 0; var = vars[i]; i++) + { +#if defined (__CYGWIN__) + /* We don't use the exportstr stuff on Cygwin at all. */ + INVALIDATE_EXPORTSTR (var); +#endif + + /* If the value is generated dynamically, generate it here. */ + if (regen_p (var) && var->dynamic_value) + { + var = (*(var->dynamic_value)) (var); + INVALIDATE_EXPORTSTR (var); + } + + if (var->exportstr) + value = var->exportstr; + else if (function_p (var)) + value = named_function_string ((char *)NULL, function_cell (var), 0); +#if defined (ARRAY_VARS) + else if (array_p (var)) +# if ARRAY_EXPORT + value = array_to_assign (array_cell (var), 0); +# else + continue; /* XXX array vars cannot yet be exported */ +# endif /* ARRAY_EXPORT */ + else if (assoc_p (var)) +# if ARRAY_EXPORT + value = assoc_to_assign (assoc_cell (var), 0); +# else + continue; /* XXX associative array vars cannot yet be exported */ +# endif /* ARRAY_EXPORT */ +#endif + else + value = value_cell (var); + + if (value) + { + /* Gee, I'd like to get away with not using savestring() if we're + using the cached exportstr... */ + list[list_index] = USE_EXPORTSTR ? savestring (value) + : mk_env_string (var->name, value, var->attributes); + + if (USE_EXPORTSTR == 0) + SAVE_EXPORTSTR (var, list[list_index]); + + list_index++; +#undef USE_EXPORTSTR + +#if defined (ARRAY_VARS) && defined (ARRAY_EXPORT) + if (array_p (var) || assoc_p (var)) + free (value); +#endif + } + } + + list[list_index] = (char *)NULL; + return (list); +} + +/* Make an array of assignment statements from the hash table + HASHED_VARS which contains SHELL_VARs. Only visible, exported + variables are eligible. */ +static char ** +make_var_export_array (vcxt) + VAR_CONTEXT *vcxt; +{ + char **list; + SHELL_VAR **vars; + +#if 0 + vars = map_over (visible_and_exported, vcxt); +#else + vars = map_over (export_environment_candidate, vcxt); +#endif + + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +static char ** +make_func_export_array () +{ + char **list; + SHELL_VAR **vars; + + vars = map_over_funcs (visible_and_exported); + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */ +#define add_to_export_env(envstr,do_alloc) \ +do \ + { \ + if (export_env_index >= (export_env_size - 1)) \ + { \ + export_env_size += 16; \ + export_env = strvec_resize (export_env, export_env_size); \ + environ = export_env; \ + } \ + export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \ + export_env[export_env_index] = (char *)NULL; \ + } while (0) + +/* Add ASSIGN to EXPORT_ENV, or supersede a previous assignment in the + array with the same left-hand side. Return the new EXPORT_ENV. */ +char ** +add_or_supercede_exported_var (assign, do_alloc) + char *assign; + int do_alloc; +{ + register int i; + int equal_offset; + + equal_offset = assignment (assign, 0); + if (equal_offset == 0) + return (export_env); + + /* If this is a function, then only supersede the function definition. + We do this by including the `=() {' in the comparison, like + initialize_shell_variables does. */ + if (assign[equal_offset + 1] == '(' && + strncmp (assign + equal_offset + 2, ") {", 3) == 0) /* } */ + equal_offset += 4; + + for (i = 0; i < export_env_index; i++) + { + if (STREQN (assign, export_env[i], equal_offset + 1)) + { + free (export_env[i]); + export_env[i] = do_alloc ? savestring (assign) : assign; + return (export_env); + } + } + add_to_export_env (assign, do_alloc); + return (export_env); +} + +static void +add_temp_array_to_env (temp_array, do_alloc, do_supercede) + char **temp_array; + int do_alloc, do_supercede; +{ + register int i; + + if (temp_array == 0) + return; + + for (i = 0; temp_array[i]; i++) + { + if (do_supercede) + export_env = add_or_supercede_exported_var (temp_array[i], do_alloc); + else + add_to_export_env (temp_array[i], do_alloc); + } + + free (temp_array); +} + +/* Make the environment array for the command about to be executed, if the + array needs making. Otherwise, do nothing. If a shell action could + change the array that commands receive for their environment, then the + code should `array_needs_making++'. + + The order to add to the array is: + temporary_env + list of var contexts whose head is shell_variables + shell_functions + + This is the shell variable lookup order. We add only new variable + names at each step, which allows local variables and variables in + the temporary environments to shadow variables in the global (or + any previous) scope. +*/ + +static int +n_shell_variables () +{ + VAR_CONTEXT *vc; + int n; + + for (n = 0, vc = shell_variables; vc; vc = vc->down) + n += HASH_ENTRIES (vc->table); + return n; +} + +int +chkexport (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v && exported_p (v)) + { + array_needs_making = 1; + maybe_make_export_env (); + return 1; + } + return 0; +} + +void +maybe_make_export_env () +{ + register char **temp_array; + int new_size; + VAR_CONTEXT *tcxt, *icxt; + + if (array_needs_making) + { + if (export_env) + strvec_flush (export_env); + + /* Make a guess based on how many shell variables and functions we + have. Since there will always be array variables, and array + variables are not (yet) exported, this will always be big enough + for the exported variables and functions. */ + new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 + + HASH_ENTRIES (temporary_env) + HASH_ENTRIES (invalid_env); + if (new_size > export_env_size) + { + export_env_size = new_size; + export_env = strvec_resize (export_env, export_env_size); + environ = export_env; + } + export_env[export_env_index = 0] = (char *)NULL; + + /* Make a dummy variable context from the temporary_env, stick it on + the front of shell_variables, call make_var_export_array on the + whole thing to flatten it, and convert the list of SHELL_VAR *s + to the form needed by the environment. */ + if (temporary_env) + { + tcxt = new_var_context ((char *)NULL, 0); + tcxt->table = temporary_env; + tcxt->down = shell_variables; + } + else + tcxt = shell_variables; + + if (invalid_env) + { + icxt = new_var_context ((char *)NULL, 0); + icxt->table = invalid_env; + icxt->down = tcxt; + } + else + icxt = tcxt; + + temp_array = make_var_export_array (icxt); + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + if (icxt != tcxt) + free (icxt); + + if (tcxt != shell_variables) + free (tcxt); + +#if defined (RESTRICTED_SHELL) + /* Restricted shells may not export shell functions. */ + temp_array = restricted ? (char **)0 : make_func_export_array (); +#else + temp_array = make_func_export_array (); +#endif + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + array_needs_making = 0; + } +} + +/* This is an efficiency hack. PWD and OLDPWD are auto-exported, so + we will need to remake the exported environment every time we + change directories. `_' is always put into the environment for + every external command, so without special treatment it will always + cause the environment to be remade. + + If there is no other reason to make the exported environment, we can + just update the variables in place and mark the exported environment + as no longer needing a remake. */ +void +update_export_env_inplace (env_prefix, preflen, value) + char *env_prefix; + int preflen; + char *value; +{ + char *evar; + + evar = (char *)xmalloc (STRLEN (value) + preflen + 1); + strcpy (evar, env_prefix); + if (value) + strcpy (evar + preflen, value); + export_env = add_or_supercede_exported_var (evar, 0); +} + +/* We always put _ in the environment as the name of this command. */ +void +put_command_name_into_env (command_name) + char *command_name; +{ + update_export_env_inplace ("_=", 2, command_name); +} + +/* **************************************************************** */ +/* */ +/* Managing variable contexts */ +/* */ +/* **************************************************************** */ + +/* Allocate and return a new variable context with NAME and FLAGS. + NAME can be NULL. */ + +VAR_CONTEXT * +new_var_context (name, flags) + char *name; + int flags; +{ + VAR_CONTEXT *vc; + + vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT)); + vc->name = name ? savestring (name) : (char *)NULL; + vc->scope = variable_context; + vc->flags = flags; + + vc->up = vc->down = (VAR_CONTEXT *)NULL; + vc->table = (HASH_TABLE *)NULL; + + return vc; +} + +/* Free a variable context and its data, including the hash table. Dispose + all of the variables. */ +void +dispose_var_context (vc) + VAR_CONTEXT *vc; +{ + FREE (vc->name); + + if (vc->table) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + + free (vc); +} + +/* Set VAR's scope level to the current variable context. */ +static int +set_context (var) + SHELL_VAR *var; +{ + return (var->context = variable_context); +} + +/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of + temporary variables, and push it onto shell_variables. This is + for shell functions. */ +VAR_CONTEXT * +push_var_context (name, flags, tempvars) + char *name; + int flags; + HASH_TABLE *tempvars; +{ + VAR_CONTEXT *vc; + int posix_func_behavior; + + /* As of IEEE Std 1003.1-2017, assignment statements preceding shell + functions no longer behave like assignment statements preceding + special builtins, and do not persist in the current shell environment. + This is austin group interp #654, though nobody implements it yet. */ + posix_func_behavior = 0; + + vc = new_var_context (name, flags); + /* Posix interp 1009, temporary assignments preceding function calls modify + the current environment *before* the command is executed. */ + if (posix_func_behavior && (flags & VC_FUNCENV) && tempvars == temporary_env) + merge_temporary_env (); + else if (tempvars) + { + vc->table = tempvars; + /* Have to do this because the temp environment was created before + variable_context was incremented. */ + /* XXX - only need to do it if flags&VC_FUNCENV */ + flatten (tempvars, set_context, (VARLIST *)NULL, 0); + vc->flags |= VC_HASTMPVAR; + } + vc->down = shell_variables; + shell_variables->up = vc; + + return (shell_variables = vc); +} + +/* This can be called from one of two code paths: + 1. pop_scope, which implements the posix rules for propagating variable + assignments preceding special builtins to the surrounding scope + (push_builtin_var -- isbltin == 1); + 2. pop_var_context, which is called from pop_context and implements the + posix rules for propagating variable assignments preceding function + calls to the surrounding scope (push_func_var -- isbltin == 0) + + It takes variables out of a temporary environment hash table. We take the + variable in data. +*/ + +static inline void +push_posix_tempvar_internal (var, isbltin) + SHELL_VAR *var; + int isbltin; +{ + SHELL_VAR *v; + int posix_var_behavior; + + /* As of IEEE Std 1003.1-2017, assignment statements preceding shell + functions no longer behave like assignment statements preceding + special builtins, and do not persist in the current shell environment. + This is austin group interp #654, though nobody implements it yet. */ + posix_var_behavior = posixly_correct && isbltin; + v = 0; + + if (local_p (var) && STREQ (var->name, "-")) + { + set_current_options (value_cell (var)); + set_shellopts (); + } + /* This takes variable assignments preceding special builtins that can execute + multiple commands (source, eval, etc.) and performs the equivalent of + an assignment statement to modify the closest enclosing variable (the + posix "current execution environment"). This makes the behavior the same + as push_posix_temp_var; but the circumstances of calling are slightly + different. */ + else if (tempvar_p (var) && posix_var_behavior) + { + /* similar to push_posix_temp_var */ + v = bind_variable (var->name, value_cell (var), ASS_FORCE|ASS_NOLONGJMP); + if (v) + { + v->attributes |= var->attributes; + if (v->context == 0) + v->attributes &= ~(att_tempvar|att_propagate); + /* XXX - set att_propagate here if v->context > 0? */ + } + } + else if (tempvar_p (var) && propagate_p (var)) + { + /* Make sure we have a hash table to store the variable in while it is + being propagated down to the global variables table. Create one if + we have to */ + if ((vc_isfuncenv (shell_variables) || vc_istempenv (shell_variables)) && shell_variables->table == 0) + shell_variables->table = hash_create (VARIABLES_HASH_BUCKETS); + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0); + /* XXX - should we set v->context here? */ + if (v) + v->context = shell_variables->scope; + if (shell_variables == global_variables) + var->attributes &= ~(att_tempvar|att_propagate); + else + shell_variables->flags |= VC_HASTMPVAR; + if (v) + v->attributes |= var->attributes; + } + else + stupidly_hack_special_variables (var->name); /* XXX */ + +#if defined (ARRAY_VARS) + if (v && (array_p (var) || assoc_p (var))) + { + FREE (value_cell (v)); + if (array_p (var)) + var_setarray (v, array_copy (array_cell (var))); + else + var_setassoc (v, assoc_copy (assoc_cell (var))); + } +#endif + + dispose_variable (var); +} + +static void +push_func_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + push_posix_tempvar_internal (var, 0); +} + +static void +push_builtin_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + push_posix_tempvar_internal (var, 1); +} + +/* Pop the top context off of VCXT and dispose of it, returning the rest of + the stack. */ +void +pop_var_context () +{ + VAR_CONTEXT *ret, *vcxt; + + vcxt = shell_variables; + if (vc_isfuncenv (vcxt) == 0) + { + internal_error (_("pop_var_context: head of shell_variables not a function context")); + return; + } + + if (ret = vcxt->down) + { + ret->up = (VAR_CONTEXT *)NULL; + shell_variables = ret; + if (vcxt->table) + hash_flush (vcxt->table, push_func_var); + dispose_var_context (vcxt); + } + else + internal_error (_("pop_var_context: no global_variables context")); +} + +static void +delete_local_contexts (vcxt) + VAR_CONTEXT *vcxt; +{ + VAR_CONTEXT *v, *t; + + for (v = vcxt; v != global_variables; v = t) + { + t = v->down; + dispose_var_context (v); + } +} + +/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and + all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */ +void +delete_all_contexts (vcxt) + VAR_CONTEXT *vcxt; +{ + delete_local_contexts (vcxt); + delete_all_variables (global_variables->table); + shell_variables = global_variables; +} + +/* Reset the context so we are not executing in a shell function. Only call + this if you are getting ready to exit the shell. */ +void +reset_local_contexts () +{ + delete_local_contexts (shell_variables); + shell_variables = global_variables; + variable_context = 0; +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping temporary variable scopes */ +/* */ +/* **************************************************************** */ + +VAR_CONTEXT * +push_scope (flags, tmpvars) + int flags; + HASH_TABLE *tmpvars; +{ + return (push_var_context ((char *)NULL, flags, tmpvars)); +} + +static void +push_exported_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + /* If a temp var had its export attribute set, or it's marked to be + propagated, bind it in the previous scope before disposing it. */ + /* XXX - This isn't exactly right, because all tempenv variables have the + export attribute set. */ + if (tempvar_p (var) && exported_p (var) && (var->attributes & att_propagate)) + { + var->attributes &= ~att_tempvar; /* XXX */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0, 0); + if (shell_variables == global_variables) + var->attributes &= ~att_propagate; + if (v) + { + v->attributes |= var->attributes; + v->context = shell_variables->scope; + } + } + else + stupidly_hack_special_variables (var->name); /* XXX */ + + dispose_variable (var); +} + +/* This is called to propagate variables in the temporary environment of a + special builtin (if IS_SPECIAL != 0) or exported variables that are the + result of a builtin like `source' or `command' that can operate on the + variables in its temporary environment. In the first case, we call + push_builtin_var, which does the right thing. */ +void +pop_scope (is_special) + int is_special; +{ + VAR_CONTEXT *vcxt, *ret; + int is_bltinenv; + + vcxt = shell_variables; + if (vc_istempscope (vcxt) == 0) + { + internal_error (_("pop_scope: head of shell_variables not a temporary environment scope")); + return; + } + is_bltinenv = vc_isbltnenv (vcxt); /* XXX - for later */ + + ret = vcxt->down; + if (ret) + ret->up = (VAR_CONTEXT *)NULL; + + shell_variables = ret; + + /* Now we can take care of merging variables in VCXT into set of scopes + whose head is RET (shell_variables). */ + FREE (vcxt->name); + if (vcxt->table) + { + if (is_special) + hash_flush (vcxt->table, push_builtin_var); + else + hash_flush (vcxt->table, push_exported_var); + hash_dispose (vcxt->table); + } + free (vcxt); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping function contexts */ +/* */ +/* **************************************************************** */ + +struct saved_dollar_vars { + char **first_ten; + WORD_LIST *rest; + int count; +}; + +static struct saved_dollar_vars *dollar_arg_stack = (struct saved_dollar_vars *)NULL; +static int dollar_arg_stack_slots; +static int dollar_arg_stack_index; + +/* Functions to manipulate dollar_vars array. Need to keep these in sync with + whatever remember_args() does. */ +static char ** +save_dollar_vars () +{ + char **ret; + int i; + + ret = strvec_create (10); + for (i = 1; i < 10; i++) + { + ret[i] = dollar_vars[i]; + dollar_vars[i] = (char *)NULL; + } + return ret; +} + +static void +restore_dollar_vars (args) + char **args; +{ + int i; + + for (i = 1; i < 10; i++) + dollar_vars[i] = args[i]; +} + +static void +free_dollar_vars () +{ + int i; + + for (i = 1; i < 10; i++) + { + FREE (dollar_vars[i]); + dollar_vars[i] = (char *)NULL; + } +} + +static void +free_saved_dollar_vars (args) + char **args; +{ + int i; + + for (i = 1; i < 10; i++) + FREE (args[i]); +} + +/* Do what remember_args (xxx, 1) would have done. */ +void +clear_dollar_vars () +{ + free_dollar_vars (); + dispose_words (rest_of_args); + + rest_of_args = (WORD_LIST *)NULL; + posparam_count = 0; +} + +/* XXX - should always be followed by remember_args () */ +void +push_context (name, is_subshell, tempvars) + char *name; /* function name */ + int is_subshell; + HASH_TABLE *tempvars; +{ + if (is_subshell == 0) + push_dollar_vars (); + variable_context++; + push_var_context (name, VC_FUNCENV, tempvars); +} + +/* Only called when subshell == 0, so we don't need to check, and can + unconditionally pop the dollar vars off the stack. */ +void +pop_context () +{ + pop_dollar_vars (); + variable_context--; + pop_var_context (); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* Save the existing positional parameters on a stack. */ +void +push_dollar_vars () +{ + if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots) + { + dollar_arg_stack = (struct saved_dollar_vars *) + xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10) + * sizeof (struct saved_dollar_vars)); + } + + dollar_arg_stack[dollar_arg_stack_index].count = posparam_count; + dollar_arg_stack[dollar_arg_stack_index].first_ten = save_dollar_vars (); + dollar_arg_stack[dollar_arg_stack_index++].rest = rest_of_args; + rest_of_args = (WORD_LIST *)NULL; + posparam_count = 0; + + dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL; + dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL; +} + +/* Restore the positional parameters from our stack. */ +void +pop_dollar_vars () +{ + if (dollar_arg_stack == 0 || dollar_arg_stack_index == 0) + return; + + /* Wipe out current values */ + clear_dollar_vars (); + + rest_of_args = dollar_arg_stack[--dollar_arg_stack_index].rest; + restore_dollar_vars (dollar_arg_stack[dollar_arg_stack_index].first_ten); + free (dollar_arg_stack[dollar_arg_stack_index].first_ten); + posparam_count = dollar_arg_stack[dollar_arg_stack_index].count; + + dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL; + dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL; + dollar_arg_stack[dollar_arg_stack_index].count = 0; + + set_dollar_vars_unchanged (); + invalidate_cached_quoted_dollar_at (); +} + +void +dispose_saved_dollar_vars () +{ + if (dollar_arg_stack == 0 || dollar_arg_stack_index == 0) + return; + + dispose_words (dollar_arg_stack[--dollar_arg_stack_index].rest); + free_saved_dollar_vars (dollar_arg_stack[dollar_arg_stack_index].first_ten); + free (dollar_arg_stack[dollar_arg_stack_index].first_ten); + + dollar_arg_stack[dollar_arg_stack_index].first_ten = (char **)NULL; + dollar_arg_stack[dollar_arg_stack_index].rest = (WORD_LIST *)NULL; + dollar_arg_stack[dollar_arg_stack_index].count = 0; +} + +/* Initialize BASH_ARGV and BASH_ARGC after turning on extdebug after the + shell is initialized */ +void +init_bash_argv () +{ + if (bash_argv_initialized == 0) + { + save_bash_argv (); + bash_argv_initialized = 1; + } +} + +void +save_bash_argv () +{ + WORD_LIST *list; + + list = list_rest_of_args (); + push_args (list); + dispose_words (list); +} + +/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */ + +void +push_args (list) + WORD_LIST *list; +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + WORD_LIST *l; + arrayind_t i; + char *t; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + for (l = list, i = 0; l; l = l->next, i++) + array_push (bash_argv_a, l->word->word); + + t = itos (i); + array_push (bash_argc_a, t); + free (t); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/* Remove arguments from BASH_ARGV array. Pop top element off BASH_ARGC + array and use that value as the count of elements to remove from + BASH_ARGV. */ +void +pop_args () +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + ARRAY_ELEMENT *ce; + intmax_t i; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + ce = array_unshift_element (bash_argc_a); + if (ce == 0 || legal_number (element_value (ce), &i) == 0) + i = 0; + + for ( ; i > 0; i--) + array_pop (bash_argv_a); + array_dispose_element (ce); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/************************************************* + * * + * Functions to manage special variables * + * * + *************************************************/ + +/* Extern declarations for variables this code has to manage. */ + +/* An alist of name.function for each special variable. Most of the + functions don't do much, and in fact, this would be faster with a + switch statement, but by the end of this file, I am sick of switch + statements. */ + +#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0 + +/* This table will be sorted with qsort() the first time it's accessed. */ +struct name_and_function { + char *name; + sh_sv_func_t *function; +}; + +static struct name_and_function special_vars[] = { + { "BASH_COMPAT", sv_shcompat }, + { "BASH_XTRACEFD", sv_xtracefd }, + +#if defined (JOB_CONTROL) + { "CHILD_MAX", sv_childmax }, +#endif + +#if defined (READLINE) +# if defined (STRICT_POSIX) + { "COLUMNS", sv_winsize }, +# endif + { "COMP_WORDBREAKS", sv_comp_wordbreaks }, +#endif + + { "EXECIGNORE", sv_execignore }, + + { "FUNCNEST", sv_funcnest }, + + { "GLOBIGNORE", sv_globignore }, + +#if defined (HISTORY) + { "HISTCONTROL", sv_history_control }, + { "HISTFILESIZE", sv_histsize }, + { "HISTIGNORE", sv_histignore }, + { "HISTSIZE", sv_histsize }, + { "HISTTIMEFORMAT", sv_histtimefmt }, +#endif + +#if defined (__CYGWIN__) + { "HOME", sv_home }, +#endif + +#if defined (READLINE) + { "HOSTFILE", sv_hostfile }, +#endif + + { "IFS", sv_ifs }, + { "IGNOREEOF", sv_ignoreeof }, + + { "LANG", sv_locale }, + { "LC_ALL", sv_locale }, + { "LC_COLLATE", sv_locale }, + { "LC_CTYPE", sv_locale }, + { "LC_MESSAGES", sv_locale }, + { "LC_NUMERIC", sv_locale }, + { "LC_TIME", sv_locale }, + +#if defined (READLINE) && defined (STRICT_POSIX) + { "LINES", sv_winsize }, +#endif + + { "MAIL", sv_mail }, + { "MAILCHECK", sv_mail }, + { "MAILPATH", sv_mail }, + + { "OPTERR", sv_opterr }, + { "OPTIND", sv_optind }, + + { "PATH", sv_path }, + { "POSIXLY_CORRECT", sv_strict_posix }, + +#if defined (READLINE) + { "TERM", sv_terminal }, + { "TERMCAP", sv_terminal }, + { "TERMINFO", sv_terminal }, +#endif /* READLINE */ + + { "TEXTDOMAIN", sv_locale }, + { "TEXTDOMAINDIR", sv_locale }, + +#if defined (HAVE_TZSET) + { "TZ", sv_tz }, +#endif + +#if defined (HISTORY) && defined (BANG_HISTORY) + { "histchars", sv_histchars }, +#endif /* HISTORY && BANG_HISTORY */ + + { "ignoreeof", sv_ignoreeof }, + + { (char *)0, (sh_sv_func_t *)0 } +}; + +#define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1) + +static int +sv_compare (sv1, sv2) + struct name_and_function *sv1, *sv2; +{ + int r; + + if ((r = sv1->name[0] - sv2->name[0]) == 0) + r = strcmp (sv1->name, sv2->name); + return r; +} + +static inline int +find_special_var (name) + const char *name; +{ + register int i, r; + + for (i = 0; special_vars[i].name; i++) + { + r = special_vars[i].name[0] - name[0]; + if (r == 0) + r = strcmp (special_vars[i].name, name); + if (r == 0) + return i; + else if (r > 0) + /* Can't match any of rest of elements in sorted list. Take this out + if it causes problems in certain environments. */ + break; + } + return -1; +} + +/* The variable in NAME has just had its state changed. Check to see if it + is one of the special ones where something special happens. */ +void +stupidly_hack_special_variables (name) + char *name; +{ + static int sv_sorted = 0; + int i; + + if (sv_sorted == 0) /* shouldn't need, but it's fairly cheap. */ + { + qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]), + (QSFUNC *)sv_compare); + sv_sorted = 1; + } + + i = find_special_var (name); + if (i != -1) + (*(special_vars[i].function)) (name); +} + +/* Special variables that need hooks to be run when they are unset as part + of shell reinitialization should have their sv_ functions run here. */ +void +reinit_special_variables () +{ +#if defined (READLINE) + sv_comp_wordbreaks ("COMP_WORDBREAKS"); +#endif + sv_globignore ("GLOBIGNORE"); + sv_opterr ("OPTERR"); +} + +void +sv_ifs (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable ("IFS"); + setifs (v); +} + +/* What to do just after the PATH variable has changed. */ +void +sv_path (name) + char *name; +{ + /* hash -r */ + phash_flush (); +} + +/* What to do just after one of the MAILxxxx variables has changed. NAME + is the name of the variable. This is called with NAME set to one of + MAIL, MAILCHECK, or MAILPATH. */ +void +sv_mail (name) + char *name; +{ + /* If the time interval for checking the files has changed, then + reset the mail timer. Otherwise, one of the pathname vars + to the users mailbox has changed, so rebuild the array of + filenames. */ + if (name[4] == 'C') /* if (strcmp (name, "MAILCHECK") == 0) */ + reset_mail_timer (); + else + { + free_mail_files (); + remember_mail_dates (); + } +} + +void +sv_funcnest (name) + char *name; +{ + SHELL_VAR *v; + intmax_t num; + + v = find_variable (name); + if (v == 0) + funcnest_max = 0; + else if (legal_number (value_cell (v), &num) == 0) + funcnest_max = 0; + else + funcnest_max = num; +} + +/* What to do when EXECIGNORE changes. */ +void +sv_execignore (name) + char *name; +{ + setup_exec_ignore (name); +} + +/* What to do when GLOBIGNORE changes. */ +void +sv_globignore (name) + char *name; +{ + if (privileged_mode == 0) + setup_glob_ignore (name); +} + +#if defined (READLINE) +void +sv_comp_wordbreaks (name) + char *name; +{ + SHELL_VAR *sv; + + sv = find_variable (name); + if (sv == 0) + reset_completer_word_break_chars (); +} + +/* What to do just after one of the TERMxxx variables has changed. + If we are an interactive shell, then try to reset the terminal + information in readline. */ +void +sv_terminal (name) + char *name; +{ + if (interactive_shell && no_line_editing == 0) + rl_reset_terminal (get_string_value ("TERM")); +} + +void +sv_hostfile (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v == 0) + clear_hostname_list (); + else + hostname_list_initialized = 0; +} + +#if defined (STRICT_POSIX) +/* In strict posix mode, we allow assignments to LINES and COLUMNS (and values + found in the initial environment) to override the terminal size reported by + the kernel. */ +void +sv_winsize (name) + char *name; +{ + SHELL_VAR *v; + intmax_t xd; + int d; + + if (posixly_correct == 0 || interactive_shell == 0 || no_line_editing) + return; + + v = find_variable (name); + if (v == 0 || var_isset (v) == 0) + rl_reset_screen_size (); + else + { + if (legal_number (value_cell (v), &xd) == 0) + return; + winsize_assignment = 1; + d = xd; /* truncate */ + if (name[0] == 'L') /* LINES */ + rl_set_screen_size (d, -1); + else /* COLUMNS */ + rl_set_screen_size (-1, d); + winsize_assignment = 0; + } +} +#endif /* STRICT_POSIX */ +#endif /* READLINE */ + +/* Update the value of HOME in the export environment so tilde expansion will + work on cygwin. */ +#if defined (__CYGWIN__) +sv_home (name) + char *name; +{ + array_needs_making = 1; + maybe_make_export_env (); +} +#endif + +#if defined (HISTORY) +/* What to do after the HISTSIZE or HISTFILESIZE variables change. + If there is a value for this HISTSIZE (and it is numeric), then stifle + the history. Otherwise, if there is NO value for this variable, + unstifle the history. If name is HISTFILESIZE, and its value is + numeric, truncate the history file to hold no more than that many + lines. */ +void +sv_histsize (name) + char *name; +{ + char *temp; + intmax_t num; + int hmax; + + temp = get_string_value (name); + + if (temp && *temp) + { + if (legal_number (temp, &num)) + { + hmax = num; + if (hmax < 0 && name[4] == 'S') + unstifle_history (); /* unstifle history if HISTSIZE < 0 */ + else if (name[4] == 'S') + { + stifle_history (hmax); + hmax = where_history (); + if (history_lines_this_session > hmax) + history_lines_this_session = hmax; + } + else if (hmax >= 0) /* truncate HISTFILE if HISTFILESIZE >= 0 */ + { + history_truncate_file (get_string_value ("HISTFILE"), hmax); + /* If we just shrank the history file to fewer lines than we've + already read, make sure we adjust our idea of how many lines + we have read from the file. */ + if (hmax < history_lines_in_file) + history_lines_in_file = hmax; + } + } + } + else if (name[4] == 'S') + unstifle_history (); +} + +/* What to do after the HISTIGNORE variable changes. */ +void +sv_histignore (name) + char *name; +{ + setup_history_ignore (name); +} + +/* What to do after the HISTCONTROL variable changes. */ +void +sv_history_control (name) + char *name; +{ + char *temp; + char *val; + int tptr; + + history_control = 0; + temp = get_string_value (name); + + if (temp == 0 || *temp == 0) + return; + + tptr = 0; + while (val = extract_colon_unit (temp, &tptr)) + { + if (STREQ (val, "ignorespace")) + history_control |= HC_IGNSPACE; + else if (STREQ (val, "ignoredups")) + history_control |= HC_IGNDUPS; + else if (STREQ (val, "ignoreboth")) + history_control |= HC_IGNBOTH; + else if (STREQ (val, "erasedups")) + history_control |= HC_ERASEDUPS; + + free (val); + } +} + +#if defined (BANG_HISTORY) +/* Setting/unsetting of the history expansion character. */ +void +sv_histchars (name) + char *name; +{ + char *temp; + + temp = get_string_value (name); + if (temp) + { + history_expansion_char = *temp; + if (temp[0] && temp[1]) + { + history_subst_char = temp[1]; + if (temp[2]) + history_comment_char = temp[2]; + } + } + else + { + history_expansion_char = '!'; + history_subst_char = '^'; + history_comment_char = '#'; + } +} +#endif /* BANG_HISTORY */ + +void +sv_histtimefmt (name) + char *name; +{ + SHELL_VAR *v; + + if (v = find_variable (name)) + { + if (history_comment_char == 0) + history_comment_char = '#'; + } + history_write_timestamps = (v != 0); +} +#endif /* HISTORY */ + +#if defined (HAVE_TZSET) +void +sv_tz (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v && exported_p (v)) + array_needs_making = 1; + else if (v == 0) + array_needs_making = 1; + + if (array_needs_making) + { + maybe_make_export_env (); + tzset (); + } +} +#endif + +/* If the variable exists, then the value of it can be the number + of times we actually ignore the EOF. The default is small, + (smaller than csh, anyway). */ +void +sv_ignoreeof (name) + char *name; +{ + SHELL_VAR *tmp_var; + char *temp; + + eof_encountered = 0; + + tmp_var = find_variable (name); + ignoreeof = tmp_var && var_isset (tmp_var); + temp = tmp_var ? value_cell (tmp_var) : (char *)NULL; + if (temp) + eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10; + set_shellopts (); /* make sure `ignoreeof' is/is not in $SHELLOPTS */ +} + +void +sv_optind (name) + char *name; +{ + SHELL_VAR *var; + char *tt; + int s; + + var = find_variable ("OPTIND"); + tt = var ? get_variable_value (var) : (char *)NULL; + + /* Assume that if var->context < variable_context and variable_context > 0 + then we are restoring the variables's previous state while returning + from a function. */ + if (tt && *tt) + { + s = atoi (tt); + + /* According to POSIX, setting OPTIND=1 resets the internal state + of getopt (). */ + if (s < 0 || s == 1) + s = 0; + } + else + s = 0; + getopts_reset (s); +} + +void +sv_opterr (name) + char *name; +{ + char *tt; + + tt = get_string_value ("OPTERR"); + sh_opterr = (tt && *tt) ? atoi (tt) : 1; +} + +void +sv_strict_posix (name) + char *name; +{ + SHELL_VAR *var; + + var = find_variable (name); + posixly_correct = var && var_isset (var); + posix_initialize (posixly_correct); +#if defined (READLINE) + if (interactive_shell) + posix_readline_initialize (posixly_correct); +#endif /* READLINE */ + set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */ +} + +void +sv_locale (name) + char *name; +{ + char *v; + int r; + + v = get_string_value (name); + if (name[0] == 'L' && name[1] == 'A') /* LANG */ + r = set_lang (name, v); + else + r = set_locale_var (name, v); /* LC_*, TEXTDOMAIN* */ + +#if 1 + if (r == 0 && posixly_correct) + set_exit_status (EXECUTION_FAILURE); +#endif +} + +#if defined (ARRAY_VARS) +void +set_pipestatus_array (ps, nproc) + int *ps; + int nproc; +{ + SHELL_VAR *v; + ARRAY *a; + ARRAY_ELEMENT *ae; + register int i; + char *t, tbuf[INT_STRLEN_BOUND(int) + 1]; + + v = find_variable ("PIPESTATUS"); + if (v == 0) + v = make_new_array_variable ("PIPESTATUS"); + if (array_p (v) == 0) + return; /* Do nothing if not an array variable. */ + a = array_cell (v); + + if (a == 0 || array_num_elements (a) == 0) + { + for (i = 0; i < nproc; i++) /* was ps[i] != -1, not i < nproc */ + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + return; + } + + /* Fast case */ + if (array_num_elements (a) == nproc && nproc == 1) + { +#ifndef ALT_ARRAY_IMPLEMENTATION + ae = element_forw (a->head); +#else + ae = a->elements[0]; +#endif + ARRAY_ELEMENT_REPLACE (ae, itos (ps[0])); + } + else if (array_num_elements (a) <= nproc) + { + /* modify in array_num_elements members in place, then add */ +#ifndef ALT_ARRAY_IMPLEMENTATION + ae = a->head; +#endif + for (i = 0; i < array_num_elements (a); i++) + { +#ifndef ALT_ARRAY_IMPLEMENTATION + ae = element_forw (ae); +#else + ae = a->elements[i]; +#endif + ARRAY_ELEMENT_REPLACE (ae, itos (ps[i])); + } + /* add any more */ + for ( ; i < nproc; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } + else + { +#ifndef ALT_ARRAY_IMPLEMENTATION + /* deleting elements. it's faster to rebuild the array. */ + array_flush (a); + for (i = 0; i < nproc; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } +#else + /* deleting elements. replace the first NPROC, free the rest */ + for (i = 0; i < nproc; i++) + { + ae = a->elements[i]; + ARRAY_ELEMENT_REPLACE (ae, itos (ps[i])); + } + for ( ; i <= array_max_index (a); i++) + { + array_dispose_element (a->elements[i]); + a->elements[i] = (ARRAY_ELEMENT *)NULL; + } + + /* bookkeeping usually taken care of by array_insert */ + set_max_index (a, nproc - 1); + set_first_index (a, 0); + set_num_elements (a, nproc); +#endif /* ALT_ARRAY_IMPLEMENTATION */ + } +} + +ARRAY * +save_pipestatus_array () +{ + SHELL_VAR *v; + ARRAY *a; + + v = find_variable ("PIPESTATUS"); + if (v == 0 || array_p (v) == 0 || array_cell (v) == 0) + return ((ARRAY *)NULL); + + a = array_copy (array_cell (v)); + + return a; +} + +void +restore_pipestatus_array (a) + ARRAY *a; +{ + SHELL_VAR *v; + ARRAY *a2; + + v = find_variable ("PIPESTATUS"); + /* XXX - should we still assign even if existing value is NULL? */ + if (v == 0 || array_p (v) == 0 || array_cell (v) == 0) + return; + + a2 = array_cell (v); + var_setarray (v, a); + + array_dispose (a2); +} +#endif + +void +set_pipestatus_from_exit (s) + int s; +{ +#if defined (ARRAY_VARS) + static int v[2] = { 0, -1 }; + + v[0] = s; + set_pipestatus_array (v, 1); +#endif +} + +void +sv_xtracefd (name) + char *name; +{ + SHELL_VAR *v; + char *t, *e; + int fd; + FILE *fp; + + v = find_variable (name); + if (v == 0) + { + xtrace_reset (); + return; + } + + t = value_cell (v); + if (t == 0 || *t == 0) + xtrace_reset (); + else + { + fd = (int)strtol (t, &e, 10); + if (e != t && *e == '\0' && sh_validfd (fd)) + { + fp = fdopen (fd, "w"); + if (fp == 0) + internal_error (_("%s: %s: cannot open as FILE"), name, value_cell (v)); + else + xtrace_set (fd, fp); + } + else + internal_error (_("%s: %s: invalid value for trace file descriptor"), name, value_cell (v)); + } +} + +#define MIN_COMPAT_LEVEL 31 + +void +sv_shcompat (name) + char *name; +{ + SHELL_VAR *v; + char *val; + int tens, ones, compatval; + + v = find_variable (name); + if (v == 0) + { + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + set_compatibility_opts (); + return; + } + val = value_cell (v); + if (val == 0 || *val == '\0') + { + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + set_compatibility_opts (); + return; + } + /* Handle decimal-like compatibility version specifications: 4.2 */ + if (ISDIGIT (val[0]) && val[1] == '.' && ISDIGIT (val[2]) && val[3] == 0) + { + tens = val[0] - '0'; + ones = val[2] - '0'; + compatval = tens*10 + ones; + } + /* Handle integer-like compatibility version specifications: 42 */ + else if (ISDIGIT (val[0]) && ISDIGIT (val[1]) && val[2] == 0) + { + tens = val[0] - '0'; + ones = val[1] - '0'; + compatval = tens*10 + ones; + } + else + { +compat_error: + internal_error (_("%s: %s: compatibility value out of range"), name, val); + shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + set_compatibility_opts (); + return; + } + + if (compatval < MIN_COMPAT_LEVEL || compatval > DEFAULT_COMPAT_LEVEL) + goto compat_error; + + shell_compatibility_level = compatval; + set_compatibility_opts (); +} + +#if defined (JOB_CONTROL) +void +sv_childmax (name) + char *name; +{ + char *tt; + int s; + + tt = get_string_value (name); + s = (tt && *tt) ? atoi (tt) : 0; + set_maxchild (s); +} +#endif diff --git a/third_party/bash/variables.h b/third_party/bash/variables.h new file mode 100644 index 000000000..55f497de0 --- /dev/null +++ b/third_party/bash/variables.h @@ -0,0 +1,462 @@ +/* variables.h -- data structures for shell variables. */ + +/* Copyright (C) 1987-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_VARIABLES_H_) +#define _VARIABLES_H_ + +#include "stdc.h" +#include "array.h" +#include "assoc.h" + +/* Shell variables and functions are stored in hash tables. */ +#include "hashlib.h" + +#include "conftypes.h" + +/* A variable context. */ +typedef struct var_context { + char *name; /* empty or NULL means global context */ + int scope; /* 0 means global context */ + int flags; + struct var_context *up; /* previous function calls */ + struct var_context *down; /* down towards global context */ + HASH_TABLE *table; /* variables at this scope */ +} VAR_CONTEXT; + +/* Flags for var_context->flags */ +#define VC_HASLOCAL 0x01 +#define VC_HASTMPVAR 0x02 +#define VC_FUNCENV 0x04 /* also function if name != NULL */ +#define VC_BLTNENV 0x08 /* builtin_env */ +#define VC_TEMPENV 0x10 /* temporary_env */ + +#define VC_TEMPFLAGS (VC_FUNCENV|VC_BLTNENV|VC_TEMPENV) + +/* Accessing macros */ +#define vc_isfuncenv(vc) (((vc)->flags & VC_FUNCENV) != 0) +#define vc_isbltnenv(vc) (((vc)->flags & VC_BLTNENV) != 0) +#define vc_istempenv(vc) (((vc)->flags & (VC_TEMPFLAGS)) == VC_TEMPENV) + +#define vc_istempscope(vc) (((vc)->flags & (VC_TEMPENV|VC_BLTNENV)) != 0) + +#define vc_haslocals(vc) (((vc)->flags & VC_HASLOCAL) != 0) +#define vc_hastmpvars(vc) (((vc)->flags & VC_HASTMPVAR) != 0) + +/* What a shell variable looks like. */ + +typedef struct variable *sh_var_value_func_t PARAMS((struct variable *)); +typedef struct variable *sh_var_assign_func_t PARAMS((struct variable *, char *, arrayind_t, char *)); + +/* For the future */ +union _value { + char *s; /* string value */ + intmax_t i; /* int value */ + COMMAND *f; /* function */ + ARRAY *a; /* array */ + HASH_TABLE *h; /* associative array */ + double d; /* floating point number */ +#if defined (HAVE_LONG_DOUBLE) + long double ld; /* long double */ +#endif + struct variable *v; /* possible indirect variable use */ + void *opaque; /* opaque data for future use */ +}; + +typedef struct variable { + char *name; /* Symbol that the user types. */ + char *value; /* Value that is returned. */ + char *exportstr; /* String for the environment. */ + sh_var_value_func_t *dynamic_value; /* Function called to return a `dynamic' + value for a variable, like $SECONDS + or $RANDOM. */ + sh_var_assign_func_t *assign_func; /* Function called when this `special + variable' is assigned a value in + bind_variable. */ + int attributes; /* export, readonly, array, invisible... */ + int context; /* Which context this variable belongs to. */ +} SHELL_VAR; + +typedef struct _vlist { + SHELL_VAR **list; + int list_size; /* allocated size */ + int list_len; /* current number of entries */ +} VARLIST; + +/* The various attributes that a given variable can have. */ +/* First, the user-visible attributes */ +#define att_exported 0x0000001 /* export to environment */ +#define att_readonly 0x0000002 /* cannot change */ +#define att_array 0x0000004 /* value is an array */ +#define att_function 0x0000008 /* value is a function */ +#define att_integer 0x0000010 /* internal representation is int */ +#define att_local 0x0000020 /* variable is local to a function */ +#define att_assoc 0x0000040 /* variable is an associative array */ +#define att_trace 0x0000080 /* function is traced with DEBUG trap */ +#define att_uppercase 0x0000100 /* word converted to uppercase on assignment */ +#define att_lowercase 0x0000200 /* word converted to lowercase on assignment */ +#define att_capcase 0x0000400 /* word capitalized on assignment */ +#define att_nameref 0x0000800 /* word is a name reference */ + +#define user_attrs (att_exported|att_readonly|att_integer|att_local|att_trace|att_uppercase|att_lowercase|att_capcase|att_nameref) + +#define attmask_user 0x0000fff + +/* Internal attributes used for bookkeeping */ +#define att_invisible 0x0001000 /* cannot see */ +#define att_nounset 0x0002000 /* cannot unset */ +#define att_noassign 0x0004000 /* assignment not allowed */ +#define att_imported 0x0008000 /* came from environment */ +#define att_special 0x0010000 /* requires special handling */ +#define att_nofree 0x0020000 /* do not free value on unset */ +#define att_regenerate 0x0040000 /* regenerate when exported */ + +#define attmask_int 0x00ff000 + +/* Internal attributes used for variable scoping. */ +#define att_tempvar 0x0100000 /* variable came from the temp environment */ +#define att_propagate 0x0200000 /* propagate to previous scope */ + +#define attmask_scope 0x0f00000 + +#define exported_p(var) ((((var)->attributes) & (att_exported))) +#define readonly_p(var) ((((var)->attributes) & (att_readonly))) +#define array_p(var) ((((var)->attributes) & (att_array))) +#define function_p(var) ((((var)->attributes) & (att_function))) +#define integer_p(var) ((((var)->attributes) & (att_integer))) +#define local_p(var) ((((var)->attributes) & (att_local))) +#define assoc_p(var) ((((var)->attributes) & (att_assoc))) +#define trace_p(var) ((((var)->attributes) & (att_trace))) +#define uppercase_p(var) ((((var)->attributes) & (att_uppercase))) +#define lowercase_p(var) ((((var)->attributes) & (att_lowercase))) +#define capcase_p(var) ((((var)->attributes) & (att_capcase))) +#define nameref_p(var) ((((var)->attributes) & (att_nameref))) + +#define invisible_p(var) ((((var)->attributes) & (att_invisible))) +#define non_unsettable_p(var) ((((var)->attributes) & (att_nounset))) +#define noassign_p(var) ((((var)->attributes) & (att_noassign))) +#define imported_p(var) ((((var)->attributes) & (att_imported))) +#define specialvar_p(var) ((((var)->attributes) & (att_special))) +#define nofree_p(var) ((((var)->attributes) & (att_nofree))) +#define regen_p(var) ((((var)->attributes) & (att_regenerate))) + +#define tempvar_p(var) ((((var)->attributes) & (att_tempvar))) +#define propagate_p(var) ((((var)->attributes) & (att_propagate))) + +/* Variable names: lvalues */ +#define name_cell(var) ((var)->name) + +/* Accessing variable values: rvalues */ +#define value_cell(var) ((var)->value) +#define function_cell(var) (COMMAND *)((var)->value) +#define array_cell(var) (ARRAY *)((var)->value) +#define assoc_cell(var) (HASH_TABLE *)((var)->value) +#define nameref_cell(var) ((var)->value) /* so it can change later */ + +#define NAMEREF_MAX 8 /* only 8 levels of nameref indirection */ + +#define var_isset(var) ((var)->value != 0) +#define var_isunset(var) ((var)->value == 0) +#define var_isnull(var) ((var)->value && *(var)->value == 0) + +/* Assigning variable values: lvalues */ +#define var_setvalue(var, str) ((var)->value = (str)) +#define var_setfunc(var, func) ((var)->value = (char *)(func)) +#define var_setarray(var, arr) ((var)->value = (char *)(arr)) +#define var_setassoc(var, arr) ((var)->value = (char *)(arr)) +#define var_setref(var, str) ((var)->value = (str)) + +/* Make VAR be auto-exported. */ +#define set_auto_export(var) \ + do { (var)->attributes |= att_exported; array_needs_making = 1; } while (0) + +#define SETVARATTR(var, attr, undo) \ + ((undo == 0) ? ((var)->attributes |= (attr)) \ + : ((var)->attributes &= ~(attr))) + +#define VSETATTR(var, attr) ((var)->attributes |= (attr)) +#define VUNSETATTR(var, attr) ((var)->attributes &= ~(attr)) + +#define VGETFLAGS(var) ((var)->attributes) + +#define VSETFLAGS(var, flags) ((var)->attributes = (flags)) +#define VCLRFLAGS(var) ((var)->attributes = 0) + +/* Macros to perform various operations on `exportstr' member of a SHELL_VAR. */ +#define CLEAR_EXPORTSTR(var) (var)->exportstr = (char *)NULL +#define COPY_EXPORTSTR(var) ((var)->exportstr) ? savestring ((var)->exportstr) : (char *)NULL +#define SET_EXPORTSTR(var, value) (var)->exportstr = (value) +#define SAVE_EXPORTSTR(var, value) (var)->exportstr = (value) ? savestring (value) : (char *)NULL + +#define FREE_EXPORTSTR(var) \ + do { if ((var)->exportstr) free ((var)->exportstr); } while (0) + +#define CACHE_IMPORTSTR(var, value) \ + (var)->exportstr = savestring (value) + +#define INVALIDATE_EXPORTSTR(var) \ + do { \ + if ((var)->exportstr) \ + { \ + free ((var)->exportstr); \ + (var)->exportstr = (char *)NULL; \ + } \ + } while (0) + +#define ifsname(s) ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0') + +/* Flag values for make_local_variable and its array counterparts */ +#define MKLOC_ASSOCOK 0x01 +#define MKLOC_ARRAYOK 0x02 +#define MKLOC_INHERIT 0x04 + +/* Special value for nameref with invalid value for creation or assignment */ +extern SHELL_VAR nameref_invalid_value; +#define INVALID_NAMEREF_VALUE (void *)&nameref_invalid_value + +/* Stuff for hacking variables. */ +typedef int sh_var_map_func_t PARAMS((SHELL_VAR *)); + +/* Where we keep the variables and functions */ +extern VAR_CONTEXT *global_variables; +extern VAR_CONTEXT *shell_variables; + +extern HASH_TABLE *shell_functions; +extern HASH_TABLE *temporary_env; + +extern int variable_context; +extern char *dollar_vars[]; +extern char **export_env; + +extern int tempenv_assign_error; +extern int array_needs_making; +extern int shell_level; + +/* XXX */ +extern WORD_LIST *rest_of_args; +extern int posparam_count; +extern pid_t dollar_dollar_pid; + +extern int localvar_inherit; /* declared in variables.c */ + +extern void initialize_shell_variables PARAMS((char **, int)); + +extern int validate_inherited_value PARAMS((SHELL_VAR *, int)); + +extern SHELL_VAR *set_if_not PARAMS((char *, char *)); + +extern void sh_set_lines_and_columns PARAMS((int, int)); +extern void set_pwd PARAMS((void)); +extern void set_ppid PARAMS((void)); +extern void make_funcname_visible PARAMS((int)); + +extern SHELL_VAR *var_lookup PARAMS((const char *, VAR_CONTEXT *)); + +extern SHELL_VAR *find_function PARAMS((const char *)); +extern FUNCTION_DEF *find_function_def PARAMS((const char *)); +extern SHELL_VAR *find_variable PARAMS((const char *)); +extern SHELL_VAR *find_variable_noref PARAMS((const char *)); +extern SHELL_VAR *find_variable_last_nameref PARAMS((const char *, int)); +extern SHELL_VAR *find_global_variable_last_nameref PARAMS((const char *, int)); +extern SHELL_VAR *find_variable_nameref PARAMS((SHELL_VAR *)); +extern SHELL_VAR *find_variable_nameref_for_create PARAMS((const char *, int)); +extern SHELL_VAR *find_variable_nameref_for_assignment PARAMS((const char *, int)); +/*extern SHELL_VAR *find_variable_internal PARAMS((const char *, int));*/ +extern SHELL_VAR *find_variable_tempenv PARAMS((const char *)); +extern SHELL_VAR *find_variable_notempenv PARAMS((const char *)); +extern SHELL_VAR *find_global_variable PARAMS((const char *)); +extern SHELL_VAR *find_global_variable_noref PARAMS((const char *)); +extern SHELL_VAR *find_shell_variable PARAMS((const char *)); +extern SHELL_VAR *find_tempenv_variable PARAMS((const char *)); +extern SHELL_VAR *find_variable_no_invisible PARAMS((const char *)); +extern SHELL_VAR *find_variable_for_assignment PARAMS((const char *)); +extern char *nameref_transform_name PARAMS((char *, int)); +extern SHELL_VAR *copy_variable PARAMS((SHELL_VAR *)); +extern SHELL_VAR *make_local_variable PARAMS((const char *, int)); +extern SHELL_VAR *bind_variable PARAMS((const char *, char *, int)); +extern SHELL_VAR *bind_global_variable PARAMS((const char *, char *, int)); +extern SHELL_VAR *bind_function PARAMS((const char *, COMMAND *)); + +extern void bind_function_def PARAMS((const char *, FUNCTION_DEF *, int)); + +extern SHELL_VAR **map_over PARAMS((sh_var_map_func_t *, VAR_CONTEXT *)); +SHELL_VAR **map_over_funcs PARAMS((sh_var_map_func_t *)); + +extern SHELL_VAR **all_shell_variables PARAMS((void)); +extern SHELL_VAR **all_shell_functions PARAMS((void)); +extern SHELL_VAR **all_visible_variables PARAMS((void)); +extern SHELL_VAR **all_visible_functions PARAMS((void)); +extern SHELL_VAR **all_exported_variables PARAMS((void)); +extern SHELL_VAR **local_exported_variables PARAMS((void)); +extern SHELL_VAR **all_local_variables PARAMS((int)); +#if defined (ARRAY_VARS) +extern SHELL_VAR **all_array_variables PARAMS((void)); +#endif +extern char **all_variables_matching_prefix PARAMS((const char *)); + +extern char **make_var_array PARAMS((HASH_TABLE *)); +extern char **add_or_supercede_exported_var PARAMS((char *, int)); + +extern char *get_variable_value PARAMS((SHELL_VAR *)); +extern char *get_string_value PARAMS((const char *)); +extern char *sh_get_env_value PARAMS((const char *)); +extern char *make_variable_value PARAMS((SHELL_VAR *, char *, int)); + +extern SHELL_VAR *bind_variable_value PARAMS((SHELL_VAR *, char *, int)); +extern SHELL_VAR *bind_int_variable PARAMS((char *, char *, int)); +extern SHELL_VAR *bind_var_to_int PARAMS((char *, intmax_t, int)); + +extern int assign_in_env PARAMS((WORD_DESC *, int)); + +extern int unbind_variable PARAMS((const char *)); +extern int check_unbind_variable PARAMS((const char *)); +extern int unbind_nameref PARAMS((const char *)); +extern int unbind_variable_noref PARAMS((const char *)); +extern int unbind_global_variable PARAMS((const char *)); +extern int unbind_global_variable_noref PARAMS((const char *)); +extern int unbind_func PARAMS((const char *)); +extern int unbind_function_def PARAMS((const char *)); +extern int delete_var PARAMS((const char *, VAR_CONTEXT *)); +extern int makunbound PARAMS((const char *, VAR_CONTEXT *)); +extern int kill_local_variable PARAMS((const char *)); + +extern void delete_all_variables PARAMS((HASH_TABLE *)); +extern void delete_all_contexts PARAMS((VAR_CONTEXT *)); +extern void reset_local_contexts PARAMS((void)); + +extern VAR_CONTEXT *new_var_context PARAMS((char *, int)); +extern void dispose_var_context PARAMS((VAR_CONTEXT *)); +extern VAR_CONTEXT *push_var_context PARAMS((char *, int, HASH_TABLE *)); +extern void pop_var_context PARAMS((void)); +extern VAR_CONTEXT *push_scope PARAMS((int, HASH_TABLE *)); +extern void pop_scope PARAMS((int)); + +extern void clear_dollar_vars PARAMS((void)); + +extern void push_context PARAMS((char *, int, HASH_TABLE *)); +extern void pop_context PARAMS((void)); +extern void push_dollar_vars PARAMS((void)); +extern void pop_dollar_vars PARAMS((void)); +extern void dispose_saved_dollar_vars PARAMS((void)); + +extern void init_bash_argv PARAMS((void)); +extern void save_bash_argv PARAMS((void)); +extern void push_args PARAMS((WORD_LIST *)); +extern void pop_args PARAMS((void)); + +extern void adjust_shell_level PARAMS((int)); +extern void non_unsettable PARAMS((char *)); +extern void dispose_variable PARAMS((SHELL_VAR *)); +extern void dispose_used_env_vars PARAMS((void)); +extern void dispose_function_env PARAMS((void)); +extern void dispose_builtin_env PARAMS((void)); +extern void merge_temporary_env PARAMS((void)); +extern void flush_temporary_env PARAMS((void)); +extern void merge_builtin_env PARAMS((void)); +extern void kill_all_local_variables PARAMS((void)); + +extern void set_var_read_only PARAMS((char *)); +extern void set_func_read_only PARAMS((const char *)); +extern void set_var_auto_export PARAMS((char *)); +extern void set_func_auto_export PARAMS((const char *)); + +extern void sort_variables PARAMS((SHELL_VAR **)); + +extern int chkexport PARAMS((char *)); +extern void maybe_make_export_env PARAMS((void)); +extern void update_export_env_inplace PARAMS((char *, int, char *)); +extern void put_command_name_into_env PARAMS((char *)); +extern void put_gnu_argv_flags_into_env PARAMS((intmax_t, char *)); + +extern void print_var_list PARAMS((SHELL_VAR **)); +extern void print_func_list PARAMS((SHELL_VAR **)); +extern void print_assignment PARAMS((SHELL_VAR *)); +extern void print_var_value PARAMS((SHELL_VAR *, int)); +extern void print_var_function PARAMS((SHELL_VAR *)); + +#if defined (ARRAY_VARS) +extern SHELL_VAR *make_new_array_variable PARAMS((char *)); +extern SHELL_VAR *make_local_array_variable PARAMS((char *, int)); + +extern SHELL_VAR *make_new_assoc_variable PARAMS((char *)); +extern SHELL_VAR *make_local_assoc_variable PARAMS((char *, int)); + +extern void set_pipestatus_array PARAMS((int *, int)); +extern ARRAY *save_pipestatus_array PARAMS((void)); +extern void restore_pipestatus_array PARAMS((ARRAY *)); +#endif + +extern void set_pipestatus_from_exit PARAMS((int)); + +/* The variable in NAME has just had its state changed. Check to see if it + is one of the special ones where something special happens. */ +extern void stupidly_hack_special_variables PARAMS((char *)); + +/* Reinitialize some special variables that have external effects upon unset + when the shell reinitializes itself. */ +extern void reinit_special_variables PARAMS((void)); + +extern int get_random_number PARAMS((void)); + +/* The `special variable' functions that get called when a particular + variable is set. */ +extern void sv_ifs PARAMS((char *)); +extern void sv_path PARAMS((char *)); +extern void sv_mail PARAMS((char *)); +extern void sv_funcnest PARAMS((char *)); +extern void sv_execignore PARAMS((char *)); +extern void sv_globignore PARAMS((char *)); +extern void sv_ignoreeof PARAMS((char *)); +extern void sv_strict_posix PARAMS((char *)); +extern void sv_optind PARAMS((char *)); +extern void sv_opterr PARAMS((char *)); +extern void sv_locale PARAMS((char *)); +extern void sv_xtracefd PARAMS((char *)); +extern void sv_shcompat PARAMS((char *)); + +#if defined (READLINE) +extern void sv_comp_wordbreaks PARAMS((char *)); +extern void sv_terminal PARAMS((char *)); +extern void sv_hostfile PARAMS((char *)); +extern void sv_winsize PARAMS((char *)); +#endif + +#if defined (__CYGWIN__) +extern void sv_home PARAMS((char *)); +#endif + +#if defined (HISTORY) +extern void sv_histsize PARAMS((char *)); +extern void sv_histignore PARAMS((char *)); +extern void sv_history_control PARAMS((char *)); +# if defined (BANG_HISTORY) +extern void sv_histchars PARAMS((char *)); +# endif +extern void sv_histtimefmt PARAMS((char *)); +#endif /* HISTORY */ + +#if defined (HAVE_TZSET) +extern void sv_tz PARAMS((char *)); +#endif + +#if defined (JOB_CONTROL) +extern void sv_childmax PARAMS((char *)); +#endif + +#endif /* !_VARIABLES_H_ */ diff --git a/third_party/bash/version.c b/third_party/bash/version.c new file mode 100644 index 000000000..38af8edca --- /dev/null +++ b/third_party/bash/version.c @@ -0,0 +1,94 @@ +/* version.c -- distribution and version numbers. */ + +/* Copyright (C) 1989-2022 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include + +#include "stdc.h" + +#include "version.h" +#include "patchlevel.h" +#include "conftypes.h" + +#include "bashintl.h" + +extern char *shell_name; + +/* Defines from version.h */ +const char * const dist_version = DISTVERSION; +const int patch_level = PATCHLEVEL; +const int build_version = BUILDVERSION; +#ifdef RELSTATUS +const char * const release_status = RELSTATUS; +#else +const char * const release_status = (char *)0; +#endif +const char * const sccs_version = SCCSVERSION; + +const char * const bash_copyright = N_("Copyright (C) 2022 Free Software Foundation, Inc."); +const char * const bash_license = N_("License GPLv3+: GNU GPL version 3 or later \n"); + +/* If == 31, shell compatible with bash-3.1, == 32 with bash-3.2, and so on */ +int shell_compatibility_level = DEFAULT_COMPAT_LEVEL; + +/* Functions for getting, setting, and displaying the shell version. */ + +/* Forward declarations so we don't have to include externs.h */ +extern char *shell_version_string PARAMS((void)); +extern void show_shell_version PARAMS((int)); + +/* Give version information about this shell. */ +char * +shell_version_string () +{ + static char tt[32] = { '\0' }; + + if (tt[0] == '\0') + { + if (release_status) +#if HAVE_SNPRINTF + snprintf (tt, sizeof (tt), "%s.%d(%d)-%s", dist_version, patch_level, build_version, release_status); +#else + sprintf (tt, "%s.%d(%d)-%s", dist_version, patch_level, build_version, release_status); +#endif + else +#if HAVE_SNPRINTF + snprintf (tt, sizeof (tt), "%s.%d(%d)", dist_version, patch_level, build_version); +#else + sprintf (tt, "%s.%d(%d)", dist_version, patch_level, build_version); +#endif + } + return tt; +} + +void +show_shell_version (extended) + int extended; +{ + printf (_("GNU bash, version %s (%s)\n"), shell_version_string (), MACHTYPE); + if (extended) + { + printf ("%s\n", _(bash_copyright)); + printf ("%s\n", _(bash_license)); + printf ("%s\n", _("This is free software; you are free to change and redistribute it.")); + printf ("%s\n", _("There is NO WARRANTY, to the extent permitted by law.")); + } +} diff --git a/third_party/bash/version.h b/third_party/bash/version.h new file mode 100644 index 000000000..1a749cdbe --- /dev/null +++ b/third_party/bash/version.h @@ -0,0 +1,17 @@ +/* Version control for the shell. This file gets changed when you say + `make version.h' to the Makefile. It is created by mkversion. */ + +/* The distribution version number of this shell. */ +#define DISTVERSION "5.2" + +/* The last built version of this shell. */ +#define BUILDVERSION 1 + +/* The release status of this shell. */ +#define RELSTATUS "release" + +/* The default shell compatibility-level (the current version) */ +#define DEFAULT_COMPAT_LEVEL 52 + +/* A version string for use by sccs and the what command. */ +#define SCCSVERSION "@(#)Bash version 5.2.0(1) release GNU" diff --git a/third_party/bash/winsize.c b/third_party/bash/winsize.c new file mode 100644 index 000000000..e672a324d --- /dev/null +++ b/third_party/bash/winsize.c @@ -0,0 +1,104 @@ +/* winsize.c - handle window size changes and information. */ + +/* Copyright (C) 2005-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include "stdc.h" + +#include "bashtypes.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +/* Try to find the definitions of `struct winsize' and TIOGCWINSZ */ + +#if 0 +#if defined (GWINSZ_IN_SYS_IOCTL) && !defined (TIOCGWINSZ) +# include +#endif /* GWINSZ_IN_SYS_IOCTL && !TIOCGWINSZ */ +#endif + +#if defined (STRUCT_WINSIZE_IN_TERMIOS) && !defined (STRUCT_WINSIZE_IN_SYS_IOCTL) +# include +#endif /* STRUCT_WINSIZE_IN_TERMIOS && !STRUCT_WINSIZE_IN_SYS_IOCTL */ + +/* Not in either of the standard places, look around. */ +#if !defined (STRUCT_WINSIZE_IN_TERMIOS) && !defined (STRUCT_WINSIZE_IN_SYS_IOCTL) +# if defined (HAVE_SYS_STREAM_H) +# include +# endif /* HAVE_SYS_STREAM_H */ +# if defined (HAVE_SYS_PTEM_H) /* SVR4.2, at least, has it here */ +# include +# define _IO_PTEM_H /* work around SVR4.2 1.1.4 bug */ +# endif /* HAVE_SYS_PTEM_H */ +# if defined (HAVE_SYS_PTE_H) /* ??? */ +# include +# endif /* HAVE_SYS_PTE_H */ +#endif /* !STRUCT_WINSIZE_IN_TERMIOS && !STRUCT_WINSIZE_IN_SYS_IOCTL */ + +#include + +/* Return the fd from which we are actually getting input. */ +#define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr) + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern int shell_tty; + +#if defined (READLINE) +/* Let's not call readline, forcing readline to initialize the termcap/terminfo + variables it needs, unless we have to. */ +extern int interactive_shell; +extern int no_line_editing; +extern int bash_readline_initialized; +extern void rl_set_screen_size PARAMS((int, int)); +#endif +extern void sh_set_lines_and_columns PARAMS((int, int)); + +void +get_new_window_size (from_sig, rp, cp) + int from_sig; + int *rp, *cp; +{ +#if defined (TIOCGWINSZ) + struct winsize win; + int tty; + + tty = input_tty (); + if (tty >= 0 && (ioctl (tty, TIOCGWINSZ, &win) == 0) && + win.ws_row > 0 && win.ws_col > 0) + { + sh_set_lines_and_columns (win.ws_row, win.ws_col); +#if defined (READLINE) + if ((interactive_shell && no_line_editing == 0) || bash_readline_initialized) + rl_set_screen_size (win.ws_row, win.ws_col); +#endif + if (rp) + *rp = win.ws_row; + if (cp) + *cp = win.ws_col; + } +#endif +} diff --git a/third_party/bash/xmalloc.c b/third_party/bash/xmalloc.c new file mode 100644 index 000000000..21b598d59 --- /dev/null +++ b/third_party/bash/xmalloc.c @@ -0,0 +1,225 @@ +/* xmalloc.c -- safe versions of malloc and realloc */ + +/* Copyright (C) 1991-2016 Free Software Foundation, Inc. + + This file is part of GNU Bash, the GNU Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if defined (HAVE_CONFIG_H) +#include "config.h" +#endif + +#include "bashtypes.h" +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_STDLIB_H) +# include +#else +# include "ansi_stdlib.h" +#endif /* HAVE_STDLIB_H */ + +#include "error.h" + +#include "bashintl.h" + +#if !defined (PTR_T) +# if defined (__STDC__) +# define PTR_T void * +# else +# define PTR_T char * +# endif /* !__STDC__ */ +#endif /* !PTR_T */ + +#if HAVE_SBRK && !HAVE_DECL_SBRK +extern char *sbrk(); +#endif + +#if HAVE_SBRK && defined (USING_BASH_MALLOC) +static PTR_T lbreak; +static int brkfound; +static size_t allocated; +#endif + +/* **************************************************************** */ +/* */ +/* Memory Allocation and Deallocation. */ +/* */ +/* **************************************************************** */ + +#if HAVE_SBRK && defined (USING_BASH_MALLOC) +#define FINDBRK() \ +do { \ + if (brkfound == 0) \ + { \ + lbreak = (PTR_T)sbrk (0); \ + brkfound++; \ + } \ +} while (0) + +static size_t +findbrk () +{ + FINDBRK(); + return (char *)sbrk (0) - (char *)lbreak; +} +#else +#define FINDBRK() +#endif + +static void +allocerr (func, bytes) + const char *func; + size_t bytes; +{ +#if HAVE_SBRK && defined (USING_BASH_MALLOC) + allocated = findbrk (); + fatal_error (_("%s: cannot allocate %lu bytes (%lu bytes allocated)"), func, (unsigned long)bytes, (unsigned long)allocated); +#else + fatal_error (_("%s: cannot allocate %lu bytes"), func, (unsigned long)bytes); +#endif /* !HAVE_SBRK */ +} + +/* Return a pointer to free()able block of memory large enough + to hold BYTES number of bytes. If the memory cannot be allocated, + print an error message and abort. */ +PTR_T +xmalloc (bytes) + size_t bytes; +{ + PTR_T temp; + +#if defined (DEBUG) + if (bytes == 0) + internal_warning("xmalloc: size argument is 0"); +#endif + + FINDBRK(); + temp = malloc (bytes); + + if (temp == 0) + allocerr ("xmalloc", bytes); + + return (temp); +} + +PTR_T +xrealloc (pointer, bytes) + PTR_T pointer; + size_t bytes; +{ + PTR_T temp; + +#if defined (DEBUG) + if (bytes == 0) + internal_warning("xrealloc: size argument is 0"); +#endif + + FINDBRK(); + temp = pointer ? realloc (pointer, bytes) : malloc (bytes); + + if (temp == 0) + allocerr ("xrealloc", bytes); + + return (temp); +} + +/* Use this as the function to call when adding unwind protects so we + don't need to know what free() returns. */ +void +xfree (string) + PTR_T string; +{ + if (string) + free (string); +} + +#ifdef USING_BASH_MALLOC +#include + +static void +sh_allocerr (func, bytes, file, line) + const char *func; + size_t bytes; + char *file; + int line; +{ +#if HAVE_SBRK + allocated = findbrk (); + fatal_error (_("%s: %s:%d: cannot allocate %lu bytes (%lu bytes allocated)"), func, file, line, (unsigned long)bytes, (unsigned long)allocated); +#else + fatal_error (_("%s: %s:%d: cannot allocate %lu bytes"), func, file, line, (unsigned long)bytes); +#endif /* !HAVE_SBRK */ +} + +PTR_T +sh_xmalloc (bytes, file, line) + size_t bytes; + char *file; + int line; +{ + PTR_T temp; + +#if defined (DEBUG) + if (bytes == 0) + internal_warning("xmalloc: %s:%d: size argument is 0", file, line); +#endif + + FINDBRK(); + temp = sh_malloc (bytes, file, line); + + if (temp == 0) + sh_allocerr ("xmalloc", bytes, file, line); + + return (temp); +} + +PTR_T +sh_xrealloc (pointer, bytes, file, line) + PTR_T pointer; + size_t bytes; + char *file; + int line; +{ + PTR_T temp; + +#if defined (DEBUG) + if (bytes == 0) + internal_warning("xrealloc: %s:%d: size argument is 0", file, line); +#endif + + FINDBRK(); + temp = pointer ? sh_realloc (pointer, bytes, file, line) : sh_malloc (bytes, file, line); + + if (temp == 0) + sh_allocerr ("xrealloc", bytes, file, line); + + return (temp); +} + +void +sh_xfree (string, file, line) + PTR_T string; + char *file; + int line; +{ + if (string) + sh_free (string, file, line); +} +#endif diff --git a/third_party/bash/xmalloc.h b/third_party/bash/xmalloc.h new file mode 100644 index 000000000..55d2e3d04 --- /dev/null +++ b/third_party/bash/xmalloc.h @@ -0,0 +1,66 @@ +/* xmalloc.h -- defines for the `x' memory allocation functions */ + +/* Copyright (C) 2001-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#if !defined (_XMALLOC_H_) +#define _XMALLOC_H_ + +#include "stdc.h" +#include "bashansi.h" + +/* Generic pointer type. */ +#ifndef PTR_T + +#if defined (__STDC__) +# define PTR_T void * +#else +# define PTR_T char * +#endif + +#endif /* PTR_T */ + +/* Allocation functions in xmalloc.c */ +extern PTR_T xmalloc PARAMS((size_t)); +extern PTR_T xrealloc PARAMS((void *, size_t)); +extern void xfree PARAMS((void *)); + +#if defined(USING_BASH_MALLOC) && !defined (DISABLE_MALLOC_WRAPPERS) +extern PTR_T sh_xmalloc PARAMS((size_t, const char *, int)); +extern PTR_T sh_xrealloc PARAMS((void *, size_t, const char *, int)); +extern void sh_xfree PARAMS((void *, const char *, int)); + +#define xmalloc(x) sh_xmalloc((x), __FILE__, __LINE__) +#define xrealloc(x, n) sh_xrealloc((x), (n), __FILE__, __LINE__) +#define xfree(x) sh_xfree((x), __FILE__, __LINE__) + +#ifdef free +#undef free +#endif +#define free(x) sh_xfree((x), __FILE__, __LINE__) + +extern PTR_T sh_malloc PARAMS((size_t, const char *, int)); + +#ifdef malloc +#undef malloc +#endif +#define malloc(x) sh_malloc((x), __FILE__, __LINE__) + +#endif /* USING_BASH_MALLOC */ + +#endif /* _XMALLOC_H_ */ diff --git a/third_party/bash/xmbsrtowcs.c b/third_party/bash/xmbsrtowcs.c new file mode 100644 index 000000000..688117885 --- /dev/null +++ b/third_party/bash/xmbsrtowcs.c @@ -0,0 +1,523 @@ +/* xmbsrtowcs.c -- replacement function for mbsrtowcs */ + +/* Copyright (C) 2002-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* Ask for GNU extensions to get extern declaration for mbsnrtowcs if + available via glibc. */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE 1 +#endif + +#include "config.h" + +#include "bashansi.h" + +/* , and are included in "shmbutil.h". + If , , mbsrtowcs(), exist, HANDLE_MULTIBYTE + is defined as 1. */ +#include "shmbutil.h" + +#if HANDLE_MULTIBYTE + +#include +#if !defined (errno) +extern int errno; +#endif + +#define WSBUF_INC 32 + +#ifndef FREE +# define FREE(x) do { if (x) free (x); } while (0) +#endif + +#if ! HAVE_STRCHRNUL +extern char *strchrnul PARAMS((const char *, int)); +#endif + +/* On some locales (ex. ja_JP.sjis), mbsrtowc doesn't convert 0x5c to U<0x5c>. + So, this function is made for converting 0x5c to U<0x5c>. */ + +static mbstate_t local_state; +static int local_state_use = 0; + +size_t +xmbsrtowcs (dest, src, len, pstate) + wchar_t *dest; + const char **src; + size_t len; + mbstate_t *pstate; +{ + mbstate_t *ps; + size_t mblength, wclength, n; + + ps = pstate; + if (pstate == NULL) + { + if (!local_state_use) + { + memset (&local_state, '\0', sizeof(mbstate_t)); + local_state_use = 1; + } + ps = &local_state; + } + + n = strlen (*src); + + if (dest == NULL) + { + wchar_t *wsbuf; + const char *mbs; + mbstate_t psbuf; + + /* It doesn't matter if malloc fails here, since mbsrtowcs should do + the right thing with a NULL first argument. */ + wsbuf = (wchar_t *) malloc ((n + 1) * sizeof(wchar_t)); + mbs = *src; + psbuf = *ps; + + wclength = mbsrtowcs (wsbuf, &mbs, n, &psbuf); + + if (wsbuf) + free (wsbuf); + return wclength; + } + + for (wclength = 0; wclength < len; wclength++, dest++) + { + if (mbsinit(ps)) + { + if (**src == '\0') + { + *dest = L'\0'; + *src = NULL; + return (wclength); + } + else if (**src == '\\') + { + *dest = L'\\'; + mblength = 1; + } + else + mblength = mbrtowc(dest, *src, n, ps); + } + else + mblength = mbrtowc(dest, *src, n, ps); + + /* Cannot convert multibyte character to wide character. */ + if (mblength == (size_t)-1 || mblength == (size_t)-2) + return (size_t)-1; + + *src += mblength; + n -= mblength; + + /* The multibyte string has been completely converted, + including the terminating '\0'. */ + if (*dest == L'\0') + { + *src = NULL; + break; + } + } + + return (wclength); +} + +#if HAVE_MBSNRTOWCS +/* Convert a multibyte string to a wide character string. Memory for the + new wide character string is obtained with malloc. + + Fast multiple-character version of xdupmbstowcs used when the indices are + not required and mbsnrtowcs is available. */ + +static size_t +xdupmbstowcs2 (destp, src) + wchar_t **destp; /* Store the pointer to the wide character string */ + const char *src; /* Multibyte character string */ +{ + const char *p; /* Conversion start position of src */ + wchar_t *wsbuf; /* Buffer for wide characters. */ + size_t wsbuf_size; /* Size of WSBUF */ + size_t wcnum; /* Number of wide characters in WSBUF */ + mbstate_t state; /* Conversion State */ + size_t n, wcslength; /* Number of wide characters produced by the conversion. */ + const char *end_or_backslash; + size_t nms; /* Number of multibyte characters to convert at one time. */ + mbstate_t tmp_state; + const char *tmp_p; + + memset (&state, '\0', sizeof(mbstate_t)); + + wsbuf_size = 0; + wsbuf = NULL; + + p = src; + wcnum = 0; + do + { + end_or_backslash = strchrnul(p, '\\'); + nms = end_or_backslash - p; + if (*end_or_backslash == '\0') + nms++; + + /* Compute the number of produced wide-characters. */ + tmp_p = p; + tmp_state = state; + + if (nms == 0 && *p == '\\') /* special initial case */ + nms = wcslength = 1; + else + wcslength = mbsnrtowcs (NULL, &tmp_p, nms, 0, &tmp_state); + + if (wcslength == 0) + { + tmp_p = p; /* will need below */ + tmp_state = state; + wcslength = 1; /* take a single byte */ + } + + /* Conversion failed. */ + if (wcslength == (size_t)-1) + { + free (wsbuf); + *destp = NULL; + return (size_t)-1; + } + + /* Resize the buffer if it is not large enough. */ + if (wsbuf_size < wcnum+wcslength+1) /* 1 for the L'\0' or the potential L'\\' */ + { + wchar_t *wstmp; + + while (wsbuf_size < wcnum+wcslength+1) /* 1 for the L'\0' or the potential L'\\' */ + wsbuf_size += WSBUF_INC; + + wstmp = (wchar_t *) realloc (wsbuf, wsbuf_size * sizeof (wchar_t)); + if (wstmp == NULL) + { + free (wsbuf); + *destp = NULL; + return (size_t)-1; + } + wsbuf = wstmp; + } + + /* Perform the conversion. This is assumed to return 'wcslength'. + It may set 'p' to NULL. */ + n = mbsnrtowcs(wsbuf+wcnum, &p, nms, wsbuf_size-wcnum, &state); + + if (n == 0 && p == 0) + { + wsbuf[wcnum] = L'\0'; + break; + } + + /* Compensate for taking single byte on wcs conversion failure above. */ + if (wcslength == 1 && (n == 0 || n == (size_t)-1)) + { + state = tmp_state; + p = tmp_p; + wsbuf[wcnum] = *p; + if (*p == 0) + break; + else + { + wcnum++; p++; + } + } + else + wcnum += wcslength; + + if (mbsinit (&state) && (p != NULL) && (*p == '\\')) + { + wsbuf[wcnum++] = L'\\'; + p++; + } + } + while (p != NULL); + + *destp = wsbuf; + + /* Return the length of the wide character string, not including `\0'. */ + return wcnum; +} +#endif /* HAVE_MBSNRTOWCS */ + +/* Convert a multibyte string to a wide character string. Memory for the + new wide character string is obtained with malloc. + + The return value is the length of the wide character string. Returns a + pointer to the wide character string in DESTP. If INDICESP is not NULL, + INDICESP stores the pointer to the pointer array. Each pointer is to + the first byte of each multibyte character. Memory for the pointer array + is obtained with malloc, too. + If conversion is failed, the return value is (size_t)-1 and the values + of DESTP and INDICESP are NULL. */ + +size_t +xdupmbstowcs (destp, indicesp, src) + wchar_t **destp; /* Store the pointer to the wide character string */ + char ***indicesp; /* Store the pointer to the pointer array. */ + const char *src; /* Multibyte character string */ +{ + const char *p; /* Conversion start position of src */ + wchar_t wc; /* Created wide character by conversion */ + wchar_t *wsbuf; /* Buffer for wide characters. */ + char **indices; /* Buffer for indices. */ + size_t wsbuf_size; /* Size of WSBUF */ + size_t wcnum; /* Number of wide characters in WSBUF */ + mbstate_t state; /* Conversion State */ + + /* In case SRC or DESP is NULL, conversion doesn't take place. */ + if (src == NULL || destp == NULL) + { + if (destp) + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + +#if HAVE_MBSNRTOWCS + if (indicesp == NULL) + return (xdupmbstowcs2 (destp, src)); +#endif + + memset (&state, '\0', sizeof(mbstate_t)); + wsbuf_size = WSBUF_INC; + + wsbuf = (wchar_t *) malloc (wsbuf_size * sizeof(wchar_t)); + if (wsbuf == NULL) + { + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + + indices = NULL; + if (indicesp) + { + indices = (char **) malloc (wsbuf_size * sizeof(char *)); + if (indices == NULL) + { + free (wsbuf); + *destp = NULL; + *indicesp = NULL; + return (size_t)-1; + } + } + + p = src; + wcnum = 0; + do + { + size_t mblength; /* Byte length of one multibyte character. */ + + if (mbsinit (&state)) + { + if (*p == '\0') + { + wc = L'\0'; + mblength = 1; + } + else if (*p == '\\') + { + wc = L'\\'; + mblength = 1; + } + else + mblength = mbrtowc(&wc, p, MB_LEN_MAX, &state); + } + else + mblength = mbrtowc(&wc, p, MB_LEN_MAX, &state); + + /* Conversion failed. */ + if (MB_INVALIDCH (mblength)) + { + free (wsbuf); + FREE (indices); + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + + ++wcnum; + + /* Resize buffers when they are not large enough. */ + if (wsbuf_size < wcnum) + { + wchar_t *wstmp; + char **idxtmp; + + wsbuf_size += WSBUF_INC; + + wstmp = (wchar_t *) realloc (wsbuf, wsbuf_size * sizeof (wchar_t)); + if (wstmp == NULL) + { + free (wsbuf); + FREE (indices); + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + wsbuf = wstmp; + + if (indicesp) + { + idxtmp = (char **) realloc (indices, wsbuf_size * sizeof (char *)); + if (idxtmp == NULL) + { + free (wsbuf); + free (indices); + *destp = NULL; + if (indicesp) + *indicesp = NULL; + return (size_t)-1; + } + indices = idxtmp; + } + } + + wsbuf[wcnum - 1] = wc; + if (indices) + indices[wcnum - 1] = (char *)p; + p += mblength; + } + while (MB_NULLWCH (wc) == 0); + + /* Return the length of the wide character string, not including `\0'. */ + *destp = wsbuf; + if (indicesp != NULL) + *indicesp = indices; + + return (wcnum - 1); +} + +/* Convert wide character string to multibyte character string. Treat invalid + wide characters as bytes. Used only in unusual circumstances. + + Written by Bruno Haible , 2008, adapted by Chet Ramey + for use in Bash. */ + +/* Convert wide character string *SRCP to a multibyte character string and + store the result in DEST. Store at most LEN bytes in DEST. */ +size_t +xwcsrtombs (char *dest, const wchar_t **srcp, size_t len, mbstate_t *ps) +{ + const wchar_t *src; + size_t cur_max; /* XXX - locale_cur_max */ + char buf[64], *destptr, *tmp_dest; + unsigned char uc; + mbstate_t prev_state; + + cur_max = MB_CUR_MAX; + if (cur_max > sizeof (buf)) /* Holy cow. */ + return (size_t)-1; + + src = *srcp; + + if (dest != NULL) + { + destptr = dest; + + for (; len > 0; src++) + { + wchar_t wc; + size_t ret; + + wc = *src; + /* If we have room, store directly into DEST. */ + tmp_dest = destptr; + ret = wcrtomb (len >= cur_max ? destptr : buf, wc, ps); + + if (ret == (size_t)(-1)) /* XXX */ + { + /* Since this is used for globbing and other uses of filenames, + treat invalid wide character sequences as bytes. This is + intended to be symmetric with xdupmbstowcs2. */ +handle_byte: + destptr = tmp_dest; /* in case wcrtomb modified it */ + uc = wc; + ret = 1; + if (len >= cur_max) + *destptr = uc; + else + buf[0] = uc; + if (ps) + memset (ps, 0, sizeof (mbstate_t)); + } + + if (ret > cur_max) /* Holy cow */ + goto bad_input; + + if (len < ret) + break; + + if (len < cur_max) + memcpy (destptr, buf, ret); + + if (wc == 0) + { + src = NULL; + /* Here mbsinit (ps). */ + break; + } + destptr += ret; + len -= ret; + } + *srcp = src; + return destptr - dest; + } + else + { + /* Ignore dest and len, don't store *srcp at the end, and + don't clobber *ps. */ + mbstate_t state = *ps; + size_t totalcount = 0; + + for (;; src++) + { + wchar_t wc; + size_t ret; + + wc = *src; + ret = wcrtomb (buf, wc, &state); + + if (ret == (size_t)(-1)) + goto bad_input2; + if (wc == 0) + { + /* Here mbsinit (&state). */ + break; + } + totalcount += ret; + } + return totalcount; + } + +bad_input: + *srcp = src; +bad_input2: + errno = EILSEQ; + return (size_t)(-1); +} + +#endif /* HANDLE_MULTIBYTE */ diff --git a/third_party/bash/y.tab.c b/third_party/bash/y.tab.c new file mode 100644 index 000000000..968b4efc3 --- /dev/null +++ b/third_party/bash/y.tab.c @@ -0,0 +1,9133 @@ +/* A Bison parser, made by GNU Bison 3.8.2. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output, and Bison version. */ +#define YYBISON 30802 + +/* Bison version string. */ +#define YYBISON_VERSION "3.8.2" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + + + + +/* First part of user prologue. */ +#line 21 "/usr/local/src/chet/src/bash/src/parse.y" + +#include "config.h" + +#include "bashtypes.h" +#include "bashansi.h" + +#include "filecntl.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#if defined (HAVE_LOCALE_H) +# include +#endif + +#include +#include "chartypes.h" +#include + +#include "memalloc.h" + +#include "bashintl.h" + +#define NEED_STRFTIME_DECL /* used in externs.h */ + +#include "shell.h" +#include "execute_cmd.h" +#include "typemax.h" /* SIZE_MAX if needed */ +#include "trap.h" +#include "flags.h" +#include "parser.h" +#include "mailcheck.h" +#include "test.h" +#include "builtins.h" +#include "common.h" +#include "builtext.h" + +#include "shmbutil.h" + +#if defined (READLINE) +# include "bashline.h" +# include "third_party/readline/readline.h" +#endif /* READLINE */ + +#if defined (HISTORY) +# include "bashhist.h" +# include "third_party/readline/history.h" +#endif /* HISTORY */ + +#if defined (JOB_CONTROL) +# include "jobs.h" +#else +extern int cleanup_dead_jobs PARAMS((void)); +#endif /* JOB_CONTROL */ + +#if defined (ALIAS) +# include "alias.h" +#else +typedef void *alias_t; +#endif /* ALIAS */ + +#if defined (PROMPT_STRING_DECODE) +# ifndef _MINIX +# include +# endif +# include +# if defined (TM_IN_SYS_TIME) +# include +# include +# endif /* TM_IN_SYS_TIME */ +# include "maxpath.h" +#endif /* PROMPT_STRING_DECODE */ + +#define RE_READ_TOKEN -99 +#define NO_EXPANSION -100 + +#define END_ALIAS -2 + +#ifdef DEBUG +# define YYDEBUG 1 +#else +# define YYDEBUG 0 +#endif + +#if defined (HANDLE_MULTIBYTE) +# define last_shell_getc_is_singlebyte \ + ((shell_input_line_index > 1) \ + ? shell_input_line_property[shell_input_line_index - 1] \ + : 1) +# define MBTEST(x) ((x) && last_shell_getc_is_singlebyte) +#else +# define last_shell_getc_is_singlebyte 1 +# define MBTEST(x) ((x)) +#endif + +#define EXTEND_SHELL_INPUT_LINE_PROPERTY() \ +do { \ + if (shell_input_line_len + 2 > shell_input_line_propsize) \ + { \ + shell_input_line_propsize = shell_input_line_len + 2; \ + shell_input_line_property = (char *)xrealloc (shell_input_line_property, \ + shell_input_line_propsize); \ + } \ +} while (0) + +#if defined (EXTENDED_GLOB) +extern int extended_glob; +#endif + +#if defined (TRANSLATABLE_STRINGS) +extern int dump_translatable_strings, dump_po_strings; +extern int singlequote_translations; +#endif /* TRANSLATABLE_STRINGS */ + +#if !defined (errno) +extern int errno; +#endif + +/* **************************************************************** */ +/* */ +/* "Forward" declarations */ +/* */ +/* **************************************************************** */ + +#ifdef DEBUG +static void debug_parser PARAMS((int)); +#endif + +static int yy_getc PARAMS((void)); +static int yy_ungetc PARAMS((int)); + +#if defined (READLINE) +static int yy_readline_get PARAMS((void)); +static int yy_readline_unget PARAMS((int)); +#endif + +static int yy_string_get PARAMS((void)); +static int yy_string_unget PARAMS((int)); +static int yy_stream_get PARAMS((void)); +static int yy_stream_unget PARAMS((int)); + +static int shell_getc PARAMS((int)); +static void shell_ungetc PARAMS((int)); +static void discard_until PARAMS((int)); + +static void push_string PARAMS((char *, int, alias_t *)); +static void pop_string PARAMS((void)); +static void free_string_list PARAMS((void)); + +static char *read_a_line PARAMS((int)); + +static int reserved_word_acceptable PARAMS((int)); +static int yylex PARAMS((void)); + +static void push_heredoc PARAMS((REDIRECT *)); +static char *mk_alexpansion PARAMS((char *)); +static int alias_expand_token PARAMS((char *)); +static int time_command_acceptable PARAMS((void)); +static int special_case_tokens PARAMS((char *)); +static int read_token PARAMS((int)); +static char *parse_matched_pair PARAMS((int, int, int, int *, int)); +static char *parse_comsub PARAMS((int, int, int, int *, int)); +#if defined (ARRAY_VARS) +static char *parse_compound_assignment PARAMS((int *)); +#endif +#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) +static int parse_dparen PARAMS((int)); +static int parse_arith_cmd PARAMS((char **, int)); +#endif +#if defined (COND_COMMAND) +static void cond_error PARAMS((void)); +static COND_COM *cond_expr PARAMS((void)); +static COND_COM *cond_or PARAMS((void)); +static COND_COM *cond_and PARAMS((void)); +static COND_COM *cond_term PARAMS((void)); +static int cond_skip_newlines PARAMS((void)); +static COMMAND *parse_cond_command PARAMS((void)); +#endif +#if defined (ARRAY_VARS) +static int token_is_assignment PARAMS((char *, int)); +static int token_is_ident PARAMS((char *, int)); +#endif +static int read_token_word PARAMS((int)); +static void discard_parser_constructs PARAMS((int)); + +static char *error_token_from_token PARAMS((int)); +static char *error_token_from_text PARAMS((void)); +static void print_offending_line PARAMS((void)); +static void report_syntax_error PARAMS((char *)); + +static void handle_eof_input_unit PARAMS((void)); +static void prompt_again PARAMS((int)); +#if 0 +static void reset_readline_prompt PARAMS((void)); +#endif +static void print_prompt PARAMS((void)); + +#if defined (HANDLE_MULTIBYTE) +static void set_line_mbstate PARAMS((void)); +static char *shell_input_line_property = NULL; +static size_t shell_input_line_propsize = 0; +#else +# define set_line_mbstate() +#endif + +extern int yyerror PARAMS((const char *)); + +#ifdef DEBUG +extern int yydebug; +#endif + +/* Default prompt strings */ +char *primary_prompt = PPROMPT; +char *secondary_prompt = SPROMPT; + +/* PROMPT_STRING_POINTER points to one of these, never to an actual string. */ +char *ps1_prompt, *ps2_prompt; + +/* Displayed after reading a command but before executing it in an interactive shell */ +char *ps0_prompt; + +/* Handle on the current prompt string. Indirectly points through + ps1_ or ps2_prompt. */ +char **prompt_string_pointer = (char **)NULL; +char *current_prompt_string; + +/* Non-zero means we expand aliases in commands. */ +int expand_aliases = 0; + +/* If non-zero, the decoded prompt string undergoes parameter and + variable substitution, command substitution, arithmetic substitution, + string expansion, process substitution, and quote removal in + decode_prompt_string. */ +int promptvars = 1; + +/* If non-zero, $'...' and $"..." are expanded when they appear within + a ${...} expansion, even when the expansion appears within double + quotes. */ +int extended_quote = 1; + +/* The number of lines read from input while creating the current command. */ +int current_command_line_count; + +/* The number of lines in a command saved while we run parse_and_execute */ +int saved_command_line_count; + +/* The token that currently denotes the end of parse. */ +int shell_eof_token; + +/* The token currently being read. */ +int current_token; + +/* The current parser state. */ +int parser_state; + +/* Variables to manage the task of reading here documents, because we need to + defer the reading until after a complete command has been collected. */ +static REDIRECT *redir_stack[HEREDOC_MAX]; +int need_here_doc; + +/* Where shell input comes from. History expansion is performed on each + line when the shell is interactive. */ +static char *shell_input_line = (char *)NULL; +static size_t shell_input_line_index; +static size_t shell_input_line_size; /* Amount allocated for shell_input_line. */ +static size_t shell_input_line_len; /* strlen (shell_input_line) */ + +/* Either zero or EOF. */ +static int shell_input_line_terminator; + +/* The line number in a script on which a function definition starts. */ +static int function_dstart; + +/* The line number in a script on which a function body starts. */ +static int function_bstart; + +/* The line number in a script at which an arithmetic for command starts. */ +static int arith_for_lineno; + +/* The decoded prompt string. Used if READLINE is not defined or if + editing is turned off. Analogous to current_readline_prompt. */ +static char *current_decoded_prompt; + +/* The last read token, or NULL. read_token () uses this for context + checking. */ +static int last_read_token; + +/* The token read prior to last_read_token. */ +static int token_before_that; + +/* The token read prior to token_before_that. */ +static int two_tokens_ago; + +static int global_extglob; + +/* The line number in a script where the word in a `case WORD', `select WORD' + or `for WORD' begins. This is a nested command maximum, since the array + index is decremented after a case, select, or for command is parsed. */ +#define MAX_CASE_NEST 128 +static int word_lineno[MAX_CASE_NEST+1]; +static int word_top = -1; + +/* If non-zero, it is the token that we want read_token to return + regardless of what text is (or isn't) present to be read. This + is reset by read_token. If token_to_read == WORD or + ASSIGNMENT_WORD, yylval.word should be set to word_desc_to_read. */ +static int token_to_read; +static WORD_DESC *word_desc_to_read; + +static REDIRECTEE source; +static REDIRECTEE redir; + +static FILE *yyoutstream; +static FILE *yyerrstream; + +#line 388 "y.tab.c" + +# ifndef YY_CAST +# ifdef __cplusplus +# define YY_CAST(Type, Val) static_cast (Val) +# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast (Val) +# else +# define YY_CAST(Type, Val) ((Type) (Val)) +# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val)) +# endif +# endif +# ifndef YY_NULLPTR +# if defined __cplusplus +# if 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# else +# define YY_NULLPTR ((void*)0) +# endif +# endif + +/* Use api.header.include to #include this header + instead of duplicating it here. */ +#ifndef YY_YY_Y_TAB_H_INCLUDED +# define YY_YY_Y_TAB_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int yydebug; +#endif + +/* Token kinds. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + YYEMPTY = -2, + YYEOF = 0, /* "end of file" */ + YYerror = 256, /* error */ + YYUNDEF = 257, /* "invalid token" */ + IF = 258, /* IF */ + THEN = 259, /* THEN */ + ELSE = 260, /* ELSE */ + ELIF = 261, /* ELIF */ + FI = 262, /* FI */ + CASE = 263, /* CASE */ + ESAC = 264, /* ESAC */ + FOR = 265, /* FOR */ + SELECT = 266, /* SELECT */ + WHILE = 267, /* WHILE */ + UNTIL = 268, /* UNTIL */ + DO = 269, /* DO */ + DONE = 270, /* DONE */ + FUNCTION = 271, /* FUNCTION */ + COPROC = 272, /* COPROC */ + COND_START = 273, /* COND_START */ + COND_END = 274, /* COND_END */ + COND_ERROR = 275, /* COND_ERROR */ + IN = 276, /* IN */ + BANG = 277, /* BANG */ + TIME = 278, /* TIME */ + TIMEOPT = 279, /* TIMEOPT */ + TIMEIGN = 280, /* TIMEIGN */ + WORD = 281, /* WORD */ + ASSIGNMENT_WORD = 282, /* ASSIGNMENT_WORD */ + REDIR_WORD = 283, /* REDIR_WORD */ + NUMBER = 284, /* NUMBER */ + ARITH_CMD = 285, /* ARITH_CMD */ + ARITH_FOR_EXPRS = 286, /* ARITH_FOR_EXPRS */ + COND_CMD = 287, /* COND_CMD */ + AND_AND = 288, /* AND_AND */ + OR_OR = 289, /* OR_OR */ + GREATER_GREATER = 290, /* GREATER_GREATER */ + LESS_LESS = 291, /* LESS_LESS */ + LESS_AND = 292, /* LESS_AND */ + LESS_LESS_LESS = 293, /* LESS_LESS_LESS */ + GREATER_AND = 294, /* GREATER_AND */ + SEMI_SEMI = 295, /* SEMI_SEMI */ + SEMI_AND = 296, /* SEMI_AND */ + SEMI_SEMI_AND = 297, /* SEMI_SEMI_AND */ + LESS_LESS_MINUS = 298, /* LESS_LESS_MINUS */ + AND_GREATER = 299, /* AND_GREATER */ + AND_GREATER_GREATER = 300, /* AND_GREATER_GREATER */ + LESS_GREATER = 301, /* LESS_GREATER */ + GREATER_BAR = 302, /* GREATER_BAR */ + BAR_AND = 303, /* BAR_AND */ + DOLPAREN = 304, /* DOLPAREN */ + yacc_EOF = 305 /* yacc_EOF */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif +/* Token kinds. */ +#define YYEMPTY -2 +#define YYEOF 0 +#define YYerror 256 +#define YYUNDEF 257 +#define IF 258 +#define THEN 259 +#define ELSE 260 +#define ELIF 261 +#define FI 262 +#define CASE 263 +#define ESAC 264 +#define FOR 265 +#define SELECT 266 +#define WHILE 267 +#define UNTIL 268 +#define DO 269 +#define DONE 270 +#define FUNCTION 271 +#define COPROC 272 +#define COND_START 273 +#define COND_END 274 +#define COND_ERROR 275 +#define IN 276 +#define BANG 277 +#define TIME 278 +#define TIMEOPT 279 +#define TIMEIGN 280 +#define WORD 281 +#define ASSIGNMENT_WORD 282 +#define REDIR_WORD 283 +#define NUMBER 284 +#define ARITH_CMD 285 +#define ARITH_FOR_EXPRS 286 +#define COND_CMD 287 +#define AND_AND 288 +#define OR_OR 289 +#define GREATER_GREATER 290 +#define LESS_LESS 291 +#define LESS_AND 292 +#define LESS_LESS_LESS 293 +#define GREATER_AND 294 +#define SEMI_SEMI 295 +#define SEMI_AND 296 +#define SEMI_SEMI_AND 297 +#define LESS_LESS_MINUS 298 +#define AND_GREATER 299 +#define AND_GREATER_GREATER 300 +#define LESS_GREATER 301 +#define GREATER_BAR 302 +#define BAR_AND 303 +#define DOLPAREN 304 +#define yacc_EOF 305 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 338 "/usr/local/src/chet/src/bash/src/parse.y" + + WORD_DESC *word; /* the word that we read. */ + int number; /* the number that we read. */ + WORD_LIST *word_list; + COMMAND *command; + REDIRECT *redirect; + ELEMENT element; + PATTERN_LIST *pattern; + +#line 551 "y.tab.c" + +}; +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + +extern YYSTYPE yylval; + + +int yyparse (void); + + +#endif /* !YY_YY_Y_TAB_H_INCLUDED */ +/* Symbol kind. */ +enum yysymbol_kind_t +{ + YYSYMBOL_YYEMPTY = -2, + YYSYMBOL_YYEOF = 0, /* "end of file" */ + YYSYMBOL_YYerror = 1, /* error */ + YYSYMBOL_YYUNDEF = 2, /* "invalid token" */ + YYSYMBOL_IF = 3, /* IF */ + YYSYMBOL_THEN = 4, /* THEN */ + YYSYMBOL_ELSE = 5, /* ELSE */ + YYSYMBOL_ELIF = 6, /* ELIF */ + YYSYMBOL_FI = 7, /* FI */ + YYSYMBOL_CASE = 8, /* CASE */ + YYSYMBOL_ESAC = 9, /* ESAC */ + YYSYMBOL_FOR = 10, /* FOR */ + YYSYMBOL_SELECT = 11, /* SELECT */ + YYSYMBOL_WHILE = 12, /* WHILE */ + YYSYMBOL_UNTIL = 13, /* UNTIL */ + YYSYMBOL_DO = 14, /* DO */ + YYSYMBOL_DONE = 15, /* DONE */ + YYSYMBOL_FUNCTION = 16, /* FUNCTION */ + YYSYMBOL_COPROC = 17, /* COPROC */ + YYSYMBOL_COND_START = 18, /* COND_START */ + YYSYMBOL_COND_END = 19, /* COND_END */ + YYSYMBOL_COND_ERROR = 20, /* COND_ERROR */ + YYSYMBOL_IN = 21, /* IN */ + YYSYMBOL_BANG = 22, /* BANG */ + YYSYMBOL_TIME = 23, /* TIME */ + YYSYMBOL_TIMEOPT = 24, /* TIMEOPT */ + YYSYMBOL_TIMEIGN = 25, /* TIMEIGN */ + YYSYMBOL_WORD = 26, /* WORD */ + YYSYMBOL_ASSIGNMENT_WORD = 27, /* ASSIGNMENT_WORD */ + YYSYMBOL_REDIR_WORD = 28, /* REDIR_WORD */ + YYSYMBOL_NUMBER = 29, /* NUMBER */ + YYSYMBOL_ARITH_CMD = 30, /* ARITH_CMD */ + YYSYMBOL_ARITH_FOR_EXPRS = 31, /* ARITH_FOR_EXPRS */ + YYSYMBOL_COND_CMD = 32, /* COND_CMD */ + YYSYMBOL_AND_AND = 33, /* AND_AND */ + YYSYMBOL_OR_OR = 34, /* OR_OR */ + YYSYMBOL_GREATER_GREATER = 35, /* GREATER_GREATER */ + YYSYMBOL_LESS_LESS = 36, /* LESS_LESS */ + YYSYMBOL_LESS_AND = 37, /* LESS_AND */ + YYSYMBOL_LESS_LESS_LESS = 38, /* LESS_LESS_LESS */ + YYSYMBOL_GREATER_AND = 39, /* GREATER_AND */ + YYSYMBOL_SEMI_SEMI = 40, /* SEMI_SEMI */ + YYSYMBOL_SEMI_AND = 41, /* SEMI_AND */ + YYSYMBOL_SEMI_SEMI_AND = 42, /* SEMI_SEMI_AND */ + YYSYMBOL_LESS_LESS_MINUS = 43, /* LESS_LESS_MINUS */ + YYSYMBOL_AND_GREATER = 44, /* AND_GREATER */ + YYSYMBOL_AND_GREATER_GREATER = 45, /* AND_GREATER_GREATER */ + YYSYMBOL_LESS_GREATER = 46, /* LESS_GREATER */ + YYSYMBOL_GREATER_BAR = 47, /* GREATER_BAR */ + YYSYMBOL_BAR_AND = 48, /* BAR_AND */ + YYSYMBOL_DOLPAREN = 49, /* DOLPAREN */ + YYSYMBOL_50_ = 50, /* '&' */ + YYSYMBOL_51_ = 51, /* ';' */ + YYSYMBOL_52_n_ = 52, /* '\n' */ + YYSYMBOL_yacc_EOF = 53, /* yacc_EOF */ + YYSYMBOL_54_ = 54, /* '|' */ + YYSYMBOL_55_ = 55, /* '>' */ + YYSYMBOL_56_ = 56, /* '<' */ + YYSYMBOL_57_ = 57, /* '-' */ + YYSYMBOL_58_ = 58, /* '{' */ + YYSYMBOL_59_ = 59, /* '}' */ + YYSYMBOL_60_ = 60, /* '(' */ + YYSYMBOL_61_ = 61, /* ')' */ + YYSYMBOL_YYACCEPT = 62, /* $accept */ + YYSYMBOL_inputunit = 63, /* inputunit */ + YYSYMBOL_word_list = 64, /* word_list */ + YYSYMBOL_redirection = 65, /* redirection */ + YYSYMBOL_simple_command_element = 66, /* simple_command_element */ + YYSYMBOL_redirection_list = 67, /* redirection_list */ + YYSYMBOL_simple_command = 68, /* simple_command */ + YYSYMBOL_command = 69, /* command */ + YYSYMBOL_shell_command = 70, /* shell_command */ + YYSYMBOL_for_command = 71, /* for_command */ + YYSYMBOL_arith_for_command = 72, /* arith_for_command */ + YYSYMBOL_select_command = 73, /* select_command */ + YYSYMBOL_case_command = 74, /* case_command */ + YYSYMBOL_function_def = 75, /* function_def */ + YYSYMBOL_function_body = 76, /* function_body */ + YYSYMBOL_subshell = 77, /* subshell */ + YYSYMBOL_comsub = 78, /* comsub */ + YYSYMBOL_coproc = 79, /* coproc */ + YYSYMBOL_if_command = 80, /* if_command */ + YYSYMBOL_group_command = 81, /* group_command */ + YYSYMBOL_arith_command = 82, /* arith_command */ + YYSYMBOL_cond_command = 83, /* cond_command */ + YYSYMBOL_elif_clause = 84, /* elif_clause */ + YYSYMBOL_case_clause = 85, /* case_clause */ + YYSYMBOL_pattern_list = 86, /* pattern_list */ + YYSYMBOL_case_clause_sequence = 87, /* case_clause_sequence */ + YYSYMBOL_pattern = 88, /* pattern */ + YYSYMBOL_compound_list = 89, /* compound_list */ + YYSYMBOL_list0 = 90, /* list0 */ + YYSYMBOL_list1 = 91, /* list1 */ + YYSYMBOL_simple_list_terminator = 92, /* simple_list_terminator */ + YYSYMBOL_list_terminator = 93, /* list_terminator */ + YYSYMBOL_newline_list = 94, /* newline_list */ + YYSYMBOL_simple_list = 95, /* simple_list */ + YYSYMBOL_simple_list1 = 96, /* simple_list1 */ + YYSYMBOL_pipeline_command = 97, /* pipeline_command */ + YYSYMBOL_pipeline = 98, /* pipeline */ + YYSYMBOL_timespec = 99 /* timespec */ +}; +typedef enum yysymbol_kind_t yysymbol_kind_t; + + + + +#ifdef short +# undef short +#endif + +/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure + and (if available) are included + so that the code can choose integer types of a good width. */ + +#ifndef __PTRDIFF_MAX__ +# include /* INFRINGES ON USER NAME SPACE */ +# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_STDINT_H +# endif +#endif + +/* Narrow types that promote to a signed type and that can represent a + signed or unsigned integer of at least N bits. In tables they can + save space and decrease cache pressure. Promoting to a signed type + helps avoid bugs in integer arithmetic. */ + +#ifdef __INT_LEAST8_MAX__ +typedef __INT_LEAST8_TYPE__ yytype_int8; +#elif defined YY_STDINT_H +typedef int_least8_t yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef __INT_LEAST16_MAX__ +typedef __INT_LEAST16_TYPE__ yytype_int16; +#elif defined YY_STDINT_H +typedef int_least16_t yytype_int16; +#else +typedef short yytype_int16; +#endif + +/* Work around bug in HP-UX 11.23, which defines these macros + incorrectly for preprocessor constants. This workaround can likely + be removed in 2023, as HPE has promised support for HP-UX 11.23 + (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of + . */ +#ifdef __hpux +# undef UINT_LEAST8_MAX +# undef UINT_LEAST16_MAX +# define UINT_LEAST8_MAX 255 +# define UINT_LEAST16_MAX 65535 +#endif + +#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST8_TYPE__ yytype_uint8; +#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST8_MAX <= INT_MAX) +typedef uint_least8_t yytype_uint8; +#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX +typedef unsigned char yytype_uint8; +#else +typedef short yytype_uint8; +#endif + +#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__ +typedef __UINT_LEAST16_TYPE__ yytype_uint16; +#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \ + && UINT_LEAST16_MAX <= INT_MAX) +typedef uint_least16_t yytype_uint16; +#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX +typedef unsigned short yytype_uint16; +#else +typedef int yytype_uint16; +#endif + +#ifndef YYPTRDIFF_T +# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__ +# define YYPTRDIFF_T __PTRDIFF_TYPE__ +# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__ +# elif defined PTRDIFF_MAX +# ifndef ptrdiff_t +# include /* INFRINGES ON USER NAME SPACE */ +# endif +# define YYPTRDIFF_T ptrdiff_t +# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX +# else +# define YYPTRDIFF_T long +# define YYPTRDIFF_MAXIMUM LONG_MAX +# endif +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__ +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned +# endif +#endif + +#define YYSIZE_MAXIMUM \ + YY_CAST (YYPTRDIFF_T, \ + (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \ + ? YYPTRDIFF_MAXIMUM \ + : YY_CAST (YYSIZE_T, -1))) + +#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X)) + + +/* Stored state numbers (used for stacks). */ +typedef yytype_int16 yy_state_t; + +/* State numbers in computations. */ +typedef int yy_state_fast_t; + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + + +#ifndef YY_ATTRIBUTE_PURE +# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__)) +# else +# define YY_ATTRIBUTE_PURE +# endif +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__) +# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__)) +# else +# define YY_ATTRIBUTE_UNUSED +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YY_USE(E) ((void) (E)) +#else +# define YY_USE(E) /* empty */ +#endif + +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +#if defined __GNUC__ && ! defined __ICC && 406 <= __GNUC__ * 100 + __GNUC_MINOR__ +# if __GNUC__ * 100 + __GNUC_MINOR__ < 407 +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") +# else +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# endif +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + +#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__ +# define YY_IGNORE_USELESS_CAST_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"") +# define YY_IGNORE_USELESS_CAST_END \ + _Pragma ("GCC diagnostic pop") +#endif +#ifndef YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_BEGIN +# define YY_IGNORE_USELESS_CAST_END +#endif + + +#define YY_ASSERT(E) ((void) (0 && (E))) + +#if !defined yyoverflow + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* !defined yyoverflow */ + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yy_state_t yyss_alloc; + YYSTYPE yyvs_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (YYSIZEOF (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (YYSIZEOF (yy_state_t) + YYSIZEOF (YYSTYPE)) \ + + YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYPTRDIFF_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / YYSIZEOF (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYPTRDIFF_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 121 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 740 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 62 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 38 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 174 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 349 + +/* YYMAXUTOK -- Last valid token kind. */ +#define YYMAXUTOK 305 + + +/* YYTRANSLATE(TOKEN-NUM) -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, with out-of-bounds checking. */ +#define YYTRANSLATE(YYX) \ + (0 <= (YYX) && (YYX) <= YYMAXUTOK \ + ? YY_CAST (yysymbol_kind_t, yytranslate[YYX]) \ + : YYSYMBOL_YYUNDEF) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex. */ +static const yytype_int8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 52, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 50, 2, + 60, 61, 2, 2, 2, 57, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 51, + 56, 2, 55, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 58, 54, 59, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + 45, 46, 47, 48, 49, 53 +}; + +#if YYDEBUG +/* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_int16 yyrline[] = +{ + 0, 395, 395, 406, 414, 423, 438, 455, 465, 467, + 471, 477, 483, 489, 495, 501, 507, 513, 519, 525, + 531, 537, 543, 549, 555, 561, 568, 575, 582, 589, + 596, 603, 609, 615, 621, 627, 633, 639, 645, 651, + 657, 663, 669, 675, 681, 687, 693, 699, 705, 711, + 717, 723, 729, 735, 743, 745, 747, 751, 755, 766, + 768, 772, 774, 776, 792, 794, 798, 800, 802, 804, + 806, 808, 810, 812, 814, 816, 818, 822, 827, 832, + 837, 842, 847, 852, 857, 864, 870, 876, 882, 890, + 895, 900, 905, 910, 915, 920, 925, 932, 937, 942, + 949, 951, 953, 955, 959, 961, 992, 999, 1003, 1009, + 1014, 1031, 1036, 1053, 1060, 1062, 1064, 1069, 1073, 1077, + 1081, 1083, 1085, 1089, 1090, 1094, 1096, 1098, 1100, 1104, + 1106, 1108, 1110, 1112, 1114, 1118, 1120, 1129, 1135, 1141, + 1142, 1149, 1153, 1155, 1157, 1164, 1166, 1173, 1177, 1178, + 1181, 1183, 1185, 1189, 1190, 1199, 1214, 1232, 1249, 1251, + 1253, 1260, 1263, 1267, 1269, 1275, 1281, 1301, 1324, 1326, + 1349, 1353, 1355, 1357, 1359 +}; +#endif + +/** Accessing symbol of state STATE. */ +#define YY_ACCESSING_SYMBOL(State) YY_CAST (yysymbol_kind_t, yystos[State]) + +#if YYDEBUG || 0 +/* The user-facing name of the symbol whose (internal) number is + YYSYMBOL. No bounds checking. */ +static const char *yysymbol_name (yysymbol_kind_t yysymbol) YY_ATTRIBUTE_UNUSED; + +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "\"end of file\"", "error", "\"invalid token\"", "IF", "THEN", "ELSE", + "ELIF", "FI", "CASE", "ESAC", "FOR", "SELECT", "WHILE", "UNTIL", "DO", + "DONE", "FUNCTION", "COPROC", "COND_START", "COND_END", "COND_ERROR", + "IN", "BANG", "TIME", "TIMEOPT", "TIMEIGN", "WORD", "ASSIGNMENT_WORD", + "REDIR_WORD", "NUMBER", "ARITH_CMD", "ARITH_FOR_EXPRS", "COND_CMD", + "AND_AND", "OR_OR", "GREATER_GREATER", "LESS_LESS", "LESS_AND", + "LESS_LESS_LESS", "GREATER_AND", "SEMI_SEMI", "SEMI_AND", + "SEMI_SEMI_AND", "LESS_LESS_MINUS", "AND_GREATER", "AND_GREATER_GREATER", + "LESS_GREATER", "GREATER_BAR", "BAR_AND", "DOLPAREN", "'&'", "';'", + "'\\n'", "yacc_EOF", "'|'", "'>'", "'<'", "'-'", "'{'", "'}'", "'('", + "')'", "$accept", "inputunit", "word_list", "redirection", + "simple_command_element", "redirection_list", "simple_command", + "command", "shell_command", "for_command", "arith_for_command", + "select_command", "case_command", "function_def", "function_body", + "subshell", "comsub", "coproc", "if_command", "group_command", + "arith_command", "cond_command", "elif_clause", "case_clause", + "pattern_list", "case_clause_sequence", "pattern", "compound_list", + "list0", "list1", "simple_list_terminator", "list_terminator", + "newline_list", "simple_list", "simple_list1", "pipeline_command", + "pipeline", "timespec", YY_NULLPTR +}; + +static const char * +yysymbol_name (yysymbol_kind_t yysymbol) +{ + return yytname[yysymbol]; +} +#endif + +#define YYPACT_NINF (-152) + +#define yypact_value_is_default(Yyn) \ + ((Yyn) == YYPACT_NINF) + +#define YYTABLE_NINF (-1) + +#define yytable_value_is_error(Yyn) \ + 0 + +/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int16 yypact[] = +{ + 328, 80, -152, -11, -1, 3, -152, -152, 15, 637, + -5, 433, 149, -28, -152, 187, 684, -152, 18, 28, + 130, 38, 139, 50, 52, 60, 65, 74, -152, -152, + -152, 89, 104, -152, -152, 97, -152, -152, 246, -152, + 670, -152, -152, -152, -152, -152, -152, -152, -152, -152, + -152, -152, -152, 146, 211, -152, 1, 433, -152, -152, + 135, 484, -152, 59, 61, 90, 167, 171, 10, 71, + 246, 670, 144, -152, -152, -152, -152, -152, 165, -152, + 142, 179, 192, 140, 194, 160, 227, 245, 252, 253, + 260, 261, 262, 162, 269, 178, 270, 272, 273, 274, + 277, -152, -152, -152, -152, -152, -152, -152, -152, -152, + -152, -152, -152, -152, -152, 168, 379, -152, -152, 173, + 244, -152, -152, -152, -152, 670, -152, -152, -152, -152, + -152, 535, 535, -152, -152, -152, -152, -152, -152, -152, + 205, -152, 14, -152, 36, -152, -152, -152, -152, 84, + -152, -152, -152, 249, 670, -152, 670, 670, -152, -152, + -152, -152, -152, -152, -152, -152, -152, -152, -152, -152, + -152, -152, -152, -152, -152, -152, -152, -152, -152, -152, + -152, -152, -152, -152, -152, -152, -152, -152, -152, -152, + -152, -152, -152, -152, 484, 484, 203, 203, 586, 586, + 145, -152, -152, -152, -152, -152, -152, 0, -152, 119, + -152, 291, 248, 66, 88, -152, 119, -152, 296, 297, + 35, -152, 670, 670, 35, -152, -152, 1, 1, -152, + -152, -152, 306, 484, 484, 484, 484, 484, 305, 169, + -152, 7, -152, -152, 302, -152, 131, -152, 265, -152, + -152, -152, -152, -152, -152, 304, 131, -152, 266, -152, + -152, -152, 35, -152, 313, 317, -152, -152, -152, 225, + 225, 225, -152, -152, -152, -152, 206, 25, -152, -152, + 307, -42, 319, 276, -152, -152, -152, 95, -152, 322, + 283, 332, 284, -152, -152, 102, -152, -152, -152, -152, + -152, -152, -152, -152, 45, 323, -152, -152, -152, 106, + -152, -152, -152, -152, -152, -152, 109, -152, -152, 264, + -152, -152, -152, 484, -152, -152, 333, 293, -152, -152, + 338, 300, -152, -152, -152, 484, 345, 303, -152, -152, + 346, 309, -152, -152, -152, -152, -152, -152, -152 +}; + +/* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 0, 153, 0, 0, 0, 153, 153, 0, 0, + 0, 0, 171, 54, 55, 0, 0, 118, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 153, 4, + 7, 0, 0, 153, 153, 0, 56, 59, 61, 170, + 62, 66, 76, 70, 67, 64, 72, 3, 65, 71, + 73, 74, 75, 0, 155, 162, 163, 0, 5, 6, + 0, 0, 153, 153, 0, 153, 0, 0, 0, 54, + 113, 109, 0, 151, 150, 152, 167, 164, 172, 173, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 16, 25, 40, 34, 49, 31, 43, 37, 46, + 28, 52, 53, 22, 19, 0, 0, 10, 11, 0, + 0, 1, 54, 60, 57, 63, 148, 149, 2, 153, + 153, 156, 157, 153, 153, 166, 165, 153, 154, 137, + 138, 147, 0, 153, 0, 153, 153, 153, 153, 0, + 153, 153, 153, 153, 104, 102, 111, 110, 119, 174, + 153, 18, 27, 42, 36, 51, 33, 45, 39, 48, + 30, 24, 21, 14, 15, 17, 26, 41, 35, 50, + 32, 44, 38, 47, 29, 23, 20, 12, 13, 107, + 108, 117, 106, 58, 0, 0, 160, 161, 0, 0, + 0, 153, 153, 153, 153, 153, 153, 0, 153, 0, + 153, 0, 0, 0, 0, 153, 0, 153, 0, 0, + 0, 153, 105, 112, 0, 158, 159, 169, 168, 153, + 153, 114, 0, 0, 0, 140, 141, 139, 0, 123, + 153, 0, 153, 153, 0, 8, 0, 153, 0, 87, + 88, 153, 153, 153, 153, 0, 0, 153, 0, 68, + 69, 103, 0, 100, 0, 0, 116, 142, 143, 144, + 145, 146, 99, 129, 131, 133, 124, 0, 97, 135, + 0, 0, 0, 0, 77, 9, 153, 0, 78, 0, + 0, 0, 0, 89, 153, 0, 90, 101, 115, 153, + 130, 132, 134, 98, 0, 0, 153, 79, 80, 0, + 153, 153, 85, 86, 91, 92, 0, 153, 153, 120, + 153, 136, 125, 126, 153, 153, 0, 0, 153, 153, + 0, 0, 153, 122, 127, 128, 0, 0, 83, 84, + 0, 0, 95, 96, 121, 81, 82, 93, 94 +}; + +/* YYPGOTO[NTERM-NUM]. */ +static const yytype_int16 yypgoto[] = +{ + -152, -152, 112, -29, -14, -64, 360, -152, -8, -152, + -152, -152, -152, -152, -151, -152, -152, -152, -152, -152, + -152, -152, 13, -152, 136, -152, 98, -2, -152, 30, + -152, -54, -26, -152, -123, 6, 78, -152 +}; + +/* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int16 yydefgoto[] = +{ + 0, 35, 246, 36, 37, 125, 38, 39, 40, 41, + 42, 43, 44, 45, 155, 46, 47, 48, 49, 50, + 51, 52, 232, 238, 239, 240, 281, 120, 139, 140, + 128, 76, 61, 53, 54, 141, 56, 57 +}; + +/* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_int16 yytable[] = +{ + 60, 71, 116, 135, 66, 67, 55, 157, 196, 197, + 147, 124, 305, 2, 242, 62, 278, 77, 3, 306, + 4, 5, 6, 7, 123, 63, 115, 72, 10, 65, + 64, 119, 80, 279, 303, 206, 142, 144, 2, 149, + 17, 68, 124, 3, 101, 4, 5, 6, 7, 133, + 208, 279, 138, 10, 102, 134, 123, 209, 243, 138, + 154, 156, 152, 136, 106, 17, 138, 280, 33, 261, + 153, 225, 226, 263, 2, 145, 110, 138, 111, 3, + 251, 4, 5, 6, 7, 280, 112, 138, 138, 10, + 222, 113, 223, 33, 210, 34, 193, 121, 215, 305, + 114, 17, 253, 194, 195, 216, 320, 198, 199, 310, + 143, 297, 73, 74, 75, 117, 317, 207, 138, 146, + 324, 213, 214, 328, 252, 124, 220, 124, 193, 33, + 118, 34, 58, 59, 224, 200, 138, 55, 55, 137, + 138, 148, 217, 211, 212, 245, 254, 138, 218, 219, + 229, 230, 231, 311, 138, 247, 103, 285, 138, 104, + 318, 138, 257, 158, 325, 107, 163, 329, 108, 164, + 73, 74, 75, 78, 79, 233, 234, 235, 236, 237, + 241, 150, 73, 74, 75, 151, 167, 105, 177, 168, + 159, 178, 286, 193, 193, 262, 109, 165, 126, 127, + 55, 55, 294, 160, 181, 161, 244, 182, 248, 273, + 274, 275, 154, 255, 277, 258, 154, 169, 162, 179, + 166, 287, 81, 82, 83, 84, 85, 264, 265, 189, + 86, 295, 191, 87, 88, 183, 129, 130, 201, 202, + 282, 283, 89, 90, 129, 130, 300, 301, 302, 289, + 290, 291, 292, 170, 154, 203, 204, 205, 201, 202, + 309, 131, 132, 267, 268, 269, 270, 271, 316, 332, + 230, 171, 122, 14, 15, 16, 227, 228, 172, 173, + 323, 18, 19, 20, 21, 22, 174, 175, 176, 23, + 24, 25, 26, 27, 335, 180, 184, 319, 185, 186, + 187, 31, 32, 188, 322, 192, 249, 250, 326, 327, + 221, 259, 260, 266, 272, 330, 331, 284, 334, 293, + 298, 299, 336, 337, 288, 296, 340, 341, 256, 1, + 344, 2, 333, 279, 307, 308, 3, 312, 4, 5, + 6, 7, 313, 315, 8, 9, 10, 314, 338, 321, + 11, 12, 339, 342, 13, 14, 15, 16, 17, 343, + 345, 347, 346, 18, 19, 20, 21, 22, 348, 70, + 0, 23, 24, 25, 26, 27, 276, 28, 304, 0, + 29, 30, 2, 31, 32, 0, 33, 3, 34, 4, + 5, 6, 7, 0, 0, 8, 9, 10, 0, 0, + 0, 11, 12, 0, 0, 13, 14, 15, 16, 17, + 0, 0, 0, 0, 18, 19, 20, 21, 22, 0, + 0, 0, 23, 24, 25, 26, 27, 0, 0, 0, + 0, 138, 0, 0, 31, 32, 2, 33, 0, 34, + 190, 3, 0, 4, 5, 6, 7, 0, 0, 8, + 9, 10, 0, 0, 0, 11, 12, 0, 0, 13, + 14, 15, 16, 17, 0, 0, 0, 0, 18, 19, + 20, 21, 22, 0, 0, 0, 23, 24, 25, 26, + 27, 0, 0, 0, 73, 74, 75, 2, 31, 32, + 0, 33, 3, 34, 4, 5, 6, 7, 0, 0, + 8, 9, 10, 0, 0, 0, 11, 12, 0, 0, + 13, 14, 15, 16, 17, 0, 0, 0, 0, 18, + 19, 20, 21, 22, 0, 0, 0, 23, 24, 25, + 26, 27, 0, 0, 0, 0, 138, 0, 2, 31, + 32, 0, 33, 3, 34, 4, 5, 6, 7, 0, + 0, 8, 9, 10, 0, 0, 0, 11, 12, 0, + 0, 13, 14, 15, 16, 17, 0, 0, 0, 0, + 18, 19, 20, 21, 22, 0, 0, 0, 23, 24, + 25, 26, 27, 0, 0, 0, 0, 0, 0, 2, + 31, 32, 0, 33, 3, 34, 4, 5, 6, 7, + 0, 0, 8, 9, 10, 0, 0, 0, 0, 0, + 0, 0, 13, 14, 15, 16, 17, 0, 0, 0, + 0, 18, 19, 20, 21, 22, 0, 0, 0, 23, + 24, 25, 26, 27, 0, 0, 0, 0, 138, 0, + 2, 31, 32, 0, 33, 3, 34, 4, 5, 6, + 7, 0, 0, 0, 0, 10, 0, 0, 0, 0, + 0, 0, 0, 69, 14, 15, 16, 17, 0, 0, + 0, 0, 18, 19, 20, 21, 22, 0, 0, 0, + 23, 24, 25, 26, 27, 0, 0, 0, 0, 0, + 0, 0, 31, 32, 0, 33, 0, 34, 15, 16, + 0, 0, 0, 0, 0, 18, 19, 20, 21, 22, + 0, 0, 0, 23, 24, 25, 26, 27, 0, 91, + 92, 93, 94, 95, 0, 31, 32, 96, 0, 0, + 97, 98, 0, 0, 0, 0, 0, 0, 0, 99, + 100 +}; + +static const yytype_int16 yycheck[] = +{ + 2, 9, 28, 57, 6, 7, 0, 71, 131, 132, + 64, 40, 54, 3, 14, 26, 9, 11, 8, 61, + 10, 11, 12, 13, 38, 26, 28, 32, 18, 26, + 31, 33, 60, 26, 9, 21, 62, 63, 3, 65, + 30, 26, 71, 8, 26, 10, 11, 12, 13, 48, + 14, 26, 52, 18, 26, 54, 70, 21, 58, 52, + 68, 69, 52, 57, 26, 30, 52, 60, 58, 220, + 60, 194, 195, 224, 3, 14, 26, 52, 26, 8, + 14, 10, 11, 12, 13, 60, 26, 52, 52, 18, + 154, 26, 156, 58, 58, 60, 125, 0, 14, 54, + 26, 30, 14, 129, 130, 21, 61, 133, 134, 14, + 51, 262, 51, 52, 53, 26, 14, 143, 52, 58, + 14, 147, 148, 14, 58, 154, 152, 156, 157, 58, + 26, 60, 52, 53, 160, 137, 52, 131, 132, 4, + 52, 51, 58, 145, 146, 26, 58, 52, 150, 151, + 5, 6, 7, 58, 52, 209, 26, 26, 52, 29, + 58, 52, 216, 19, 58, 26, 26, 58, 29, 29, + 51, 52, 53, 24, 25, 201, 202, 203, 204, 205, + 206, 14, 51, 52, 53, 14, 26, 57, 26, 29, + 25, 29, 246, 222, 223, 221, 57, 57, 52, 53, + 194, 195, 256, 61, 26, 26, 208, 29, 210, 40, + 41, 42, 220, 215, 240, 217, 224, 57, 26, 57, + 26, 247, 35, 36, 37, 38, 39, 229, 230, 61, + 43, 257, 59, 46, 47, 57, 33, 34, 33, 34, + 242, 243, 55, 56, 33, 34, 40, 41, 42, 251, + 252, 253, 254, 26, 262, 50, 51, 52, 33, 34, + 286, 50, 51, 233, 234, 235, 236, 237, 294, 5, + 6, 26, 26, 27, 28, 29, 198, 199, 26, 26, + 306, 35, 36, 37, 38, 39, 26, 26, 26, 43, + 44, 45, 46, 47, 320, 26, 26, 299, 26, 26, + 26, 55, 56, 26, 306, 61, 15, 59, 310, 311, + 61, 15, 15, 7, 9, 317, 318, 15, 320, 15, + 7, 4, 324, 325, 59, 59, 328, 329, 216, 1, + 332, 3, 319, 26, 15, 59, 8, 15, 10, 11, + 12, 13, 59, 59, 16, 17, 18, 15, 15, 26, + 22, 23, 59, 15, 26, 27, 28, 29, 30, 59, + 15, 15, 59, 35, 36, 37, 38, 39, 59, 9, + -1, 43, 44, 45, 46, 47, 240, 49, 280, -1, + 52, 53, 3, 55, 56, -1, 58, 8, 60, 10, + 11, 12, 13, -1, -1, 16, 17, 18, -1, -1, + -1, 22, 23, -1, -1, 26, 27, 28, 29, 30, + -1, -1, -1, -1, 35, 36, 37, 38, 39, -1, + -1, -1, 43, 44, 45, 46, 47, -1, -1, -1, + -1, 52, -1, -1, 55, 56, 3, 58, -1, 60, + 61, 8, -1, 10, 11, 12, 13, -1, -1, 16, + 17, 18, -1, -1, -1, 22, 23, -1, -1, 26, + 27, 28, 29, 30, -1, -1, -1, -1, 35, 36, + 37, 38, 39, -1, -1, -1, 43, 44, 45, 46, + 47, -1, -1, -1, 51, 52, 53, 3, 55, 56, + -1, 58, 8, 60, 10, 11, 12, 13, -1, -1, + 16, 17, 18, -1, -1, -1, 22, 23, -1, -1, + 26, 27, 28, 29, 30, -1, -1, -1, -1, 35, + 36, 37, 38, 39, -1, -1, -1, 43, 44, 45, + 46, 47, -1, -1, -1, -1, 52, -1, 3, 55, + 56, -1, 58, 8, 60, 10, 11, 12, 13, -1, + -1, 16, 17, 18, -1, -1, -1, 22, 23, -1, + -1, 26, 27, 28, 29, 30, -1, -1, -1, -1, + 35, 36, 37, 38, 39, -1, -1, -1, 43, 44, + 45, 46, 47, -1, -1, -1, -1, -1, -1, 3, + 55, 56, -1, 58, 8, 60, 10, 11, 12, 13, + -1, -1, 16, 17, 18, -1, -1, -1, -1, -1, + -1, -1, 26, 27, 28, 29, 30, -1, -1, -1, + -1, 35, 36, 37, 38, 39, -1, -1, -1, 43, + 44, 45, 46, 47, -1, -1, -1, -1, 52, -1, + 3, 55, 56, -1, 58, 8, 60, 10, 11, 12, + 13, -1, -1, -1, -1, 18, -1, -1, -1, -1, + -1, -1, -1, 26, 27, 28, 29, 30, -1, -1, + -1, -1, 35, 36, 37, 38, 39, -1, -1, -1, + 43, 44, 45, 46, 47, -1, -1, -1, -1, -1, + -1, -1, 55, 56, -1, 58, -1, 60, 28, 29, + -1, -1, -1, -1, -1, 35, 36, 37, 38, 39, + -1, -1, -1, 43, 44, 45, 46, 47, -1, 35, + 36, 37, 38, 39, -1, 55, 56, 43, -1, -1, + 46, 47, -1, -1, -1, -1, -1, -1, -1, 55, + 56 +}; + +/* YYSTOS[STATE-NUM] -- The symbol kind of the accessing symbol of + state STATE-NUM. */ +static const yytype_int8 yystos[] = +{ + 0, 1, 3, 8, 10, 11, 12, 13, 16, 17, + 18, 22, 23, 26, 27, 28, 29, 30, 35, 36, + 37, 38, 39, 43, 44, 45, 46, 47, 49, 52, + 53, 55, 56, 58, 60, 63, 65, 66, 68, 69, + 70, 71, 72, 73, 74, 75, 77, 78, 79, 80, + 81, 82, 83, 95, 96, 97, 98, 99, 52, 53, + 89, 94, 26, 26, 31, 26, 89, 89, 26, 26, + 68, 70, 32, 51, 52, 53, 93, 97, 24, 25, + 60, 35, 36, 37, 38, 39, 43, 46, 47, 55, + 56, 35, 36, 37, 38, 39, 43, 46, 47, 55, + 56, 26, 26, 26, 29, 57, 26, 26, 29, 57, + 26, 26, 26, 26, 26, 89, 94, 26, 26, 89, + 89, 0, 26, 66, 65, 67, 52, 53, 92, 33, + 34, 50, 51, 48, 54, 93, 97, 4, 52, 90, + 91, 97, 94, 51, 94, 14, 58, 93, 51, 94, + 14, 14, 52, 60, 70, 76, 70, 67, 19, 25, + 61, 26, 26, 26, 29, 57, 26, 26, 29, 57, + 26, 26, 26, 26, 26, 26, 26, 26, 29, 57, + 26, 26, 29, 57, 26, 26, 26, 26, 26, 61, + 61, 59, 61, 65, 94, 94, 96, 96, 94, 94, + 89, 33, 34, 50, 51, 52, 21, 94, 14, 21, + 58, 89, 89, 94, 94, 14, 21, 58, 89, 89, + 94, 61, 67, 67, 94, 96, 96, 98, 98, 5, + 6, 7, 84, 94, 94, 94, 94, 94, 85, 86, + 87, 94, 14, 58, 89, 26, 64, 93, 89, 15, + 59, 14, 58, 14, 58, 89, 64, 93, 89, 15, + 15, 76, 94, 76, 89, 89, 7, 91, 91, 91, + 91, 91, 9, 40, 41, 42, 86, 94, 9, 26, + 60, 88, 89, 89, 15, 26, 93, 94, 59, 89, + 89, 89, 89, 15, 93, 94, 59, 76, 7, 4, + 40, 41, 42, 9, 88, 54, 61, 15, 59, 94, + 14, 58, 15, 59, 15, 59, 94, 14, 58, 89, + 61, 26, 89, 94, 14, 58, 89, 89, 14, 58, + 89, 89, 5, 84, 89, 94, 89, 89, 15, 59, + 89, 89, 15, 59, 89, 15, 59, 15, 59 +}; + +/* YYR1[RULE-NUM] -- Symbol kind of the left-hand side of rule RULE-NUM. */ +static const yytype_int8 yyr1[] = +{ + 0, 62, 63, 63, 63, 63, 63, 63, 64, 64, + 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, + 65, 65, 65, 65, 66, 66, 66, 67, 67, 68, + 68, 69, 69, 69, 69, 69, 70, 70, 70, 70, + 70, 70, 70, 70, 70, 70, 70, 71, 71, 71, + 71, 71, 71, 71, 71, 72, 72, 72, 72, 73, + 73, 73, 73, 73, 73, 73, 73, 74, 74, 74, + 75, 75, 75, 75, 76, 76, 77, 78, 78, 79, + 79, 79, 79, 79, 80, 80, 80, 81, 82, 83, + 84, 84, 84, 85, 85, 86, 86, 86, 86, 87, + 87, 87, 87, 87, 87, 88, 88, 89, 89, 90, + 90, 90, 91, 91, 91, 91, 91, 91, 92, 92, + 93, 93, 93, 94, 94, 95, 95, 95, 96, 96, + 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, + 98, 99, 99, 99, 99 +}; + +/* YYR2[RULE-NUM] -- Number of symbols on the right-hand side of rule RULE-NUM. */ +static const yytype_int8 yyr2[] = +{ + 0, 2, 2, 1, 1, 2, 2, 1, 1, 2, + 2, 2, 3, 3, 3, 3, 2, 3, 3, 2, + 3, 3, 2, 3, 3, 2, 3, 3, 2, 3, + 3, 2, 3, 3, 2, 3, 3, 2, 3, 3, + 2, 3, 3, 2, 3, 3, 2, 3, 3, 2, + 3, 3, 2, 2, 1, 1, 1, 1, 2, 1, + 2, 1, 1, 2, 1, 1, 1, 1, 5, 5, + 1, 1, 1, 1, 1, 1, 1, 6, 6, 7, + 7, 10, 10, 9, 9, 7, 7, 5, 5, 6, + 6, 7, 7, 10, 10, 9, 9, 6, 7, 6, + 5, 6, 3, 5, 1, 2, 3, 3, 3, 2, + 3, 3, 4, 2, 5, 7, 6, 3, 1, 3, + 4, 6, 5, 1, 2, 4, 4, 5, 5, 2, + 3, 2, 3, 2, 3, 1, 3, 2, 2, 3, + 3, 3, 4, 4, 4, 4, 4, 1, 1, 1, + 1, 1, 1, 0, 2, 1, 2, 2, 4, 4, + 3, 3, 1, 1, 2, 2, 2, 2, 4, 4, + 1, 1, 2, 2, 3 +}; + + +enum { YYENOMEM = -2 }; + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab +#define YYNOMEM goto yyexhaustedlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ + do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ + while (0) + +/* Backward compatibility with an undocumented macro. + Use YYerror or YYUNDEF. */ +#define YYERRCODE YYUNDEF + + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + + + + +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Kind, Value); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*-----------------------------------. +| Print this symbol's value on YYO. | +`-----------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep) +{ + FILE *yyoutput = yyo; + YY_USE (yyoutput); + if (!yyvaluep) + return; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + +/*---------------------------. +| Print this symbol on YYO. | +`---------------------------*/ + +static void +yy_symbol_print (FILE *yyo, + yysymbol_kind_t yykind, YYSTYPE const * const yyvaluep) +{ + YYFPRINTF (yyo, "%s %s (", + yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind)); + + yy_symbol_value_print (yyo, yykind, yyvaluep); + YYFPRINTF (yyo, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yy_state_t *yybottom, yy_state_t *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yy_state_t *yyssp, YYSTYPE *yyvsp, + int yyrule) +{ + int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]), + &yyvsp[(yyi + 1) - (yynrhs)]); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, Rule); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) ((void) 0) +# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + + + + + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, + yysymbol_kind_t yykind, YYSTYPE *yyvaluep) +{ + YY_USE (yyvaluep); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yykind, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + YY_USE (yykind); + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + +/* Lookahead token kind. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; +/* Number of syntax errors so far. */ +int yynerrs; + + + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (void) +{ + yy_state_fast_t yystate = 0; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus = 0; + + /* Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* Their size. */ + YYPTRDIFF_T yystacksize = YYINITDEPTH; + + /* The state stack: array, bottom, top. */ + yy_state_t yyssa[YYINITDEPTH]; + yy_state_t *yyss = yyssa; + yy_state_t *yyssp = yyss; + + /* The semantic value stack: array, bottom, top. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + YYSTYPE *yyvsp = yyvs; + + int yyn; + /* The return value of yyparse. */ + int yyresult; + /* Lookahead symbol kind. */ + yysymbol_kind_t yytoken = YYSYMBOL_YYEMPTY; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + + + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yychar = YYEMPTY; /* Cause a token to be read. */ + + goto yysetstate; + + +/*------------------------------------------------------------. +| yynewstate -- push a new state, which is found in yystate. | +`------------------------------------------------------------*/ +yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + +/*--------------------------------------------------------------------. +| yysetstate -- set current state (the top of the stack) to yystate. | +`--------------------------------------------------------------------*/ +yysetstate: + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + YY_ASSERT (0 <= yystate && yystate < YYNSTATES); + YY_IGNORE_USELESS_CAST_BEGIN + *yyssp = YY_CAST (yy_state_t, yystate); + YY_IGNORE_USELESS_CAST_END + YY_STACK_PRINT (yyss, yyssp); + + if (yyss + yystacksize - 1 <= yyssp) +#if !defined yyoverflow && !defined YYSTACK_RELOCATE + YYNOMEM; +#else + { + /* Get the current used size of the three stacks, in elements. */ + YYPTRDIFF_T yysize = yyssp - yyss + 1; + +# if defined yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + yy_state_t *yyss1 = yyss; + YYSTYPE *yyvs1 = yyvs; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * YYSIZEOF (*yyssp), + &yyvs1, yysize * YYSIZEOF (*yyvsp), + &yystacksize); + yyss = yyss1; + yyvs = yyvs1; + } +# else /* defined YYSTACK_RELOCATE */ + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + YYNOMEM; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yy_state_t *yyss1 = yyss; + union yyalloc *yyptr = + YY_CAST (union yyalloc *, + YYSTACK_ALLOC (YY_CAST (YYSIZE_T, YYSTACK_BYTES (yystacksize)))); + if (! yyptr) + YYNOMEM; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + + YY_IGNORE_USELESS_CAST_BEGIN + YYDPRINTF ((stderr, "Stack size increased to %ld\n", + YY_CAST (long, yystacksize))); + YY_IGNORE_USELESS_CAST_END + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } +#endif /* !defined yyoverflow && !defined YYSTACK_RELOCATE */ + + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either empty, or end-of-input, or a valid lookahead. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token\n")); + yychar = yylex (); + } + + if (yychar <= YYEOF) + { + yychar = YYEOF; + yytoken = YYSYMBOL_YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else if (yychar == YYerror) + { + /* The scanner already issued an error message, process directly + to error recovery. But do not keep the error token as + lookahead, it is too special and may lead us to an endless + loop in error recovery. */ + yychar = YYUNDEF; + yytoken = YYSYMBOL_YYerror; + goto yyerrlab1; + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + /* Discard the shifted token. */ + yychar = YYEMPTY; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: /* inputunit: simple_list simple_list_terminator */ +#line 396 "/usr/local/src/chet/src/bash/src/parse.y" + { + /* Case of regular command. Discard the error + safety net,and return the command just parsed. */ + global_command = (yyvsp[-1].command); + eof_encountered = 0; + /* discard_parser_constructs (0); */ + if (parser_state & PST_CMDSUBST) + parser_state |= PST_EOFTOKEN; + YYACCEPT; + } +#line 1954 "y.tab.c" + break; + + case 3: /* inputunit: comsub */ +#line 407 "/usr/local/src/chet/src/bash/src/parse.y" + { + /* This is special; look at the production and how + parse_comsub sets token_to_read */ + global_command = (yyvsp[0].command); + eof_encountered = 0; + YYACCEPT; + } +#line 1966 "y.tab.c" + break; + + case 4: /* inputunit: '\n' */ +#line 415 "/usr/local/src/chet/src/bash/src/parse.y" + { + /* Case of regular command, but not a very + interesting one. Return a NULL command. */ + global_command = (COMMAND *)NULL; + if (parser_state & PST_CMDSUBST) + parser_state |= PST_EOFTOKEN; + YYACCEPT; + } +#line 1979 "y.tab.c" + break; + + case 5: /* inputunit: error '\n' */ +#line 424 "/usr/local/src/chet/src/bash/src/parse.y" + { + /* Error during parsing. Return NULL command. */ + global_command = (COMMAND *)NULL; + eof_encountered = 0; + /* discard_parser_constructs (1); */ + if (interactive && parse_and_execute_level == 0) + { + YYACCEPT; + } + else + { + YYABORT; + } + } +#line 1998 "y.tab.c" + break; + + case 6: /* inputunit: error yacc_EOF */ +#line 439 "/usr/local/src/chet/src/bash/src/parse.y" + { + /* EOF after an error. Do ignoreeof or not. Really + only interesting in non-interactive shells */ + global_command = (COMMAND *)NULL; + if (last_command_exit_value == 0) + last_command_exit_value = EX_BADUSAGE; /* force error return */ + if (interactive && parse_and_execute_level == 0) + { + handle_eof_input_unit (); + YYACCEPT; + } + else + { + YYABORT; + } + } +#line 2019 "y.tab.c" + break; + + case 7: /* inputunit: yacc_EOF */ +#line 456 "/usr/local/src/chet/src/bash/src/parse.y" + { + /* Case of EOF seen by itself. Do ignoreeof or + not. */ + global_command = (COMMAND *)NULL; + handle_eof_input_unit (); + YYACCEPT; + } +#line 2031 "y.tab.c" + break; + + case 8: /* word_list: WORD */ +#line 466 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.word_list) = make_word_list ((yyvsp[0].word), (WORD_LIST *)NULL); } +#line 2037 "y.tab.c" + break; + + case 9: /* word_list: word_list WORD */ +#line 468 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.word_list) = make_word_list ((yyvsp[0].word), (yyvsp[-1].word_list)); } +#line 2043 "y.tab.c" + break; + + case 10: /* redirection: '>' WORD */ +#line 472 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 1; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_output_direction, redir, 0); + } +#line 2053 "y.tab.c" + break; + + case 11: /* redirection: '<' WORD */ +#line 478 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 0; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_input_direction, redir, 0); + } +#line 2063 "y.tab.c" + break; + + case 12: /* redirection: NUMBER '>' WORD */ +#line 484 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_output_direction, redir, 0); + } +#line 2073 "y.tab.c" + break; + + case 13: /* redirection: NUMBER '<' WORD */ +#line 490 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_input_direction, redir, 0); + } +#line 2083 "y.tab.c" + break; + + case 14: /* redirection: REDIR_WORD '>' WORD */ +#line 496 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_output_direction, redir, REDIR_VARASSIGN); + } +#line 2093 "y.tab.c" + break; + + case 15: /* redirection: REDIR_WORD '<' WORD */ +#line 502 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_input_direction, redir, REDIR_VARASSIGN); + } +#line 2103 "y.tab.c" + break; + + case 16: /* redirection: GREATER_GREATER WORD */ +#line 508 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 1; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_appending_to, redir, 0); + } +#line 2113 "y.tab.c" + break; + + case 17: /* redirection: NUMBER GREATER_GREATER WORD */ +#line 514 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_appending_to, redir, 0); + } +#line 2123 "y.tab.c" + break; + + case 18: /* redirection: REDIR_WORD GREATER_GREATER WORD */ +#line 520 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_appending_to, redir, REDIR_VARASSIGN); + } +#line 2133 "y.tab.c" + break; + + case 19: /* redirection: GREATER_BAR WORD */ +#line 526 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 1; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_output_force, redir, 0); + } +#line 2143 "y.tab.c" + break; + + case 20: /* redirection: NUMBER GREATER_BAR WORD */ +#line 532 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_output_force, redir, 0); + } +#line 2153 "y.tab.c" + break; + + case 21: /* redirection: REDIR_WORD GREATER_BAR WORD */ +#line 538 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_output_force, redir, REDIR_VARASSIGN); + } +#line 2163 "y.tab.c" + break; + + case 22: /* redirection: LESS_GREATER WORD */ +#line 544 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 0; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_input_output, redir, 0); + } +#line 2173 "y.tab.c" + break; + + case 23: /* redirection: NUMBER LESS_GREATER WORD */ +#line 550 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_input_output, redir, 0); + } +#line 2183 "y.tab.c" + break; + + case 24: /* redirection: REDIR_WORD LESS_GREATER WORD */ +#line 556 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_input_output, redir, REDIR_VARASSIGN); + } +#line 2193 "y.tab.c" + break; + + case 25: /* redirection: LESS_LESS WORD */ +#line 562 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 0; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_reading_until, redir, 0); + push_heredoc ((yyval.redirect)); + } +#line 2204 "y.tab.c" + break; + + case 26: /* redirection: NUMBER LESS_LESS WORD */ +#line 569 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_reading_until, redir, 0); + push_heredoc ((yyval.redirect)); + } +#line 2215 "y.tab.c" + break; + + case 27: /* redirection: REDIR_WORD LESS_LESS WORD */ +#line 576 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_reading_until, redir, REDIR_VARASSIGN); + push_heredoc ((yyval.redirect)); + } +#line 2226 "y.tab.c" + break; + + case 28: /* redirection: LESS_LESS_MINUS WORD */ +#line 583 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 0; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_deblank_reading_until, redir, 0); + push_heredoc ((yyval.redirect)); + } +#line 2237 "y.tab.c" + break; + + case 29: /* redirection: NUMBER LESS_LESS_MINUS WORD */ +#line 590 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_deblank_reading_until, redir, 0); + push_heredoc ((yyval.redirect)); + } +#line 2248 "y.tab.c" + break; + + case 30: /* redirection: REDIR_WORD LESS_LESS_MINUS WORD */ +#line 597 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_deblank_reading_until, redir, REDIR_VARASSIGN); + push_heredoc ((yyval.redirect)); + } +#line 2259 "y.tab.c" + break; + + case 31: /* redirection: LESS_LESS_LESS WORD */ +#line 604 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 0; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_reading_string, redir, 0); + } +#line 2269 "y.tab.c" + break; + + case 32: /* redirection: NUMBER LESS_LESS_LESS WORD */ +#line 610 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_reading_string, redir, 0); + } +#line 2279 "y.tab.c" + break; + + case 33: /* redirection: REDIR_WORD LESS_LESS_LESS WORD */ +#line 616 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_reading_string, redir, REDIR_VARASSIGN); + } +#line 2289 "y.tab.c" + break; + + case 34: /* redirection: LESS_AND NUMBER */ +#line 622 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 0; + redir.dest = (yyvsp[0].number); + (yyval.redirect) = make_redirection (source, r_duplicating_input, redir, 0); + } +#line 2299 "y.tab.c" + break; + + case 35: /* redirection: NUMBER LESS_AND NUMBER */ +#line 628 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.dest = (yyvsp[0].number); + (yyval.redirect) = make_redirection (source, r_duplicating_input, redir, 0); + } +#line 2309 "y.tab.c" + break; + + case 36: /* redirection: REDIR_WORD LESS_AND NUMBER */ +#line 634 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.dest = (yyvsp[0].number); + (yyval.redirect) = make_redirection (source, r_duplicating_input, redir, REDIR_VARASSIGN); + } +#line 2319 "y.tab.c" + break; + + case 37: /* redirection: GREATER_AND NUMBER */ +#line 640 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 1; + redir.dest = (yyvsp[0].number); + (yyval.redirect) = make_redirection (source, r_duplicating_output, redir, 0); + } +#line 2329 "y.tab.c" + break; + + case 38: /* redirection: NUMBER GREATER_AND NUMBER */ +#line 646 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.dest = (yyvsp[0].number); + (yyval.redirect) = make_redirection (source, r_duplicating_output, redir, 0); + } +#line 2339 "y.tab.c" + break; + + case 39: /* redirection: REDIR_WORD GREATER_AND NUMBER */ +#line 652 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.dest = (yyvsp[0].number); + (yyval.redirect) = make_redirection (source, r_duplicating_output, redir, REDIR_VARASSIGN); + } +#line 2349 "y.tab.c" + break; + + case 40: /* redirection: LESS_AND WORD */ +#line 658 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 0; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_duplicating_input_word, redir, 0); + } +#line 2359 "y.tab.c" + break; + + case 41: /* redirection: NUMBER LESS_AND WORD */ +#line 664 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_duplicating_input_word, redir, 0); + } +#line 2369 "y.tab.c" + break; + + case 42: /* redirection: REDIR_WORD LESS_AND WORD */ +#line 670 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_duplicating_input_word, redir, REDIR_VARASSIGN); + } +#line 2379 "y.tab.c" + break; + + case 43: /* redirection: GREATER_AND WORD */ +#line 676 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 1; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_duplicating_output_word, redir, 0); + } +#line 2389 "y.tab.c" + break; + + case 44: /* redirection: NUMBER GREATER_AND WORD */ +#line 682 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_duplicating_output_word, redir, 0); + } +#line 2399 "y.tab.c" + break; + + case 45: /* redirection: REDIR_WORD GREATER_AND WORD */ +#line 688 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_duplicating_output_word, redir, REDIR_VARASSIGN); + } +#line 2409 "y.tab.c" + break; + + case 46: /* redirection: GREATER_AND '-' */ +#line 694 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 1; + redir.dest = 0; + (yyval.redirect) = make_redirection (source, r_close_this, redir, 0); + } +#line 2419 "y.tab.c" + break; + + case 47: /* redirection: NUMBER GREATER_AND '-' */ +#line 700 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.dest = 0; + (yyval.redirect) = make_redirection (source, r_close_this, redir, 0); + } +#line 2429 "y.tab.c" + break; + + case 48: /* redirection: REDIR_WORD GREATER_AND '-' */ +#line 706 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.dest = 0; + (yyval.redirect) = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN); + } +#line 2439 "y.tab.c" + break; + + case 49: /* redirection: LESS_AND '-' */ +#line 712 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 0; + redir.dest = 0; + (yyval.redirect) = make_redirection (source, r_close_this, redir, 0); + } +#line 2449 "y.tab.c" + break; + + case 50: /* redirection: NUMBER LESS_AND '-' */ +#line 718 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = (yyvsp[-2].number); + redir.dest = 0; + (yyval.redirect) = make_redirection (source, r_close_this, redir, 0); + } +#line 2459 "y.tab.c" + break; + + case 51: /* redirection: REDIR_WORD LESS_AND '-' */ +#line 724 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.filename = (yyvsp[-2].word); + redir.dest = 0; + (yyval.redirect) = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN); + } +#line 2469 "y.tab.c" + break; + + case 52: /* redirection: AND_GREATER WORD */ +#line 730 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 1; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_err_and_out, redir, 0); + } +#line 2479 "y.tab.c" + break; + + case 53: /* redirection: AND_GREATER_GREATER WORD */ +#line 736 "/usr/local/src/chet/src/bash/src/parse.y" + { + source.dest = 1; + redir.filename = (yyvsp[0].word); + (yyval.redirect) = make_redirection (source, r_append_err_and_out, redir, 0); + } +#line 2489 "y.tab.c" + break; + + case 54: /* simple_command_element: WORD */ +#line 744 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.element).word = (yyvsp[0].word); (yyval.element).redirect = 0; } +#line 2495 "y.tab.c" + break; + + case 55: /* simple_command_element: ASSIGNMENT_WORD */ +#line 746 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.element).word = (yyvsp[0].word); (yyval.element).redirect = 0; } +#line 2501 "y.tab.c" + break; + + case 56: /* simple_command_element: redirection */ +#line 748 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.element).redirect = (yyvsp[0].redirect); (yyval.element).word = 0; } +#line 2507 "y.tab.c" + break; + + case 57: /* redirection_list: redirection */ +#line 752 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.redirect) = (yyvsp[0].redirect); + } +#line 2515 "y.tab.c" + break; + + case 58: /* redirection_list: redirection_list redirection */ +#line 756 "/usr/local/src/chet/src/bash/src/parse.y" + { + register REDIRECT *t; + + for (t = (yyvsp[-1].redirect); t->next; t = t->next) + ; + t->next = (yyvsp[0].redirect); + (yyval.redirect) = (yyvsp[-1].redirect); + } +#line 2528 "y.tab.c" + break; + + case 59: /* simple_command: simple_command_element */ +#line 767 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_simple_command ((yyvsp[0].element), (COMMAND *)NULL); } +#line 2534 "y.tab.c" + break; + + case 60: /* simple_command: simple_command simple_command_element */ +#line 769 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_simple_command ((yyvsp[0].element), (yyvsp[-1].command)); } +#line 2540 "y.tab.c" + break; + + case 61: /* command: simple_command */ +#line 773 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = clean_simple_command ((yyvsp[0].command)); } +#line 2546 "y.tab.c" + break; + + case 62: /* command: shell_command */ +#line 775 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2552 "y.tab.c" + break; + + case 63: /* command: shell_command redirection_list */ +#line 777 "/usr/local/src/chet/src/bash/src/parse.y" + { + COMMAND *tc; + + tc = (yyvsp[-1].command); + if (tc && tc->redirects) + { + register REDIRECT *t; + for (t = tc->redirects; t->next; t = t->next) + ; + t->next = (yyvsp[0].redirect); + } + else if (tc) + tc->redirects = (yyvsp[0].redirect); + (yyval.command) = (yyvsp[-1].command); + } +#line 2572 "y.tab.c" + break; + + case 64: /* command: function_def */ +#line 793 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2578 "y.tab.c" + break; + + case 65: /* command: coproc */ +#line 795 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2584 "y.tab.c" + break; + + case 66: /* shell_command: for_command */ +#line 799 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2590 "y.tab.c" + break; + + case 67: /* shell_command: case_command */ +#line 801 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2596 "y.tab.c" + break; + + case 68: /* shell_command: WHILE compound_list DO compound_list DONE */ +#line 803 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_while_command ((yyvsp[-3].command), (yyvsp[-1].command)); } +#line 2602 "y.tab.c" + break; + + case 69: /* shell_command: UNTIL compound_list DO compound_list DONE */ +#line 805 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_until_command ((yyvsp[-3].command), (yyvsp[-1].command)); } +#line 2608 "y.tab.c" + break; + + case 70: /* shell_command: select_command */ +#line 807 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2614 "y.tab.c" + break; + + case 71: /* shell_command: if_command */ +#line 809 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2620 "y.tab.c" + break; + + case 72: /* shell_command: subshell */ +#line 811 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2626 "y.tab.c" + break; + + case 73: /* shell_command: group_command */ +#line 813 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2632 "y.tab.c" + break; + + case 74: /* shell_command: arith_command */ +#line 815 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2638 "y.tab.c" + break; + + case 75: /* shell_command: cond_command */ +#line 817 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2644 "y.tab.c" + break; + + case 76: /* shell_command: arith_for_command */ +#line 819 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2650 "y.tab.c" + break; + + case 77: /* for_command: FOR WORD newline_list DO compound_list DONE */ +#line 823 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_for_command ((yyvsp[-4].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2659 "y.tab.c" + break; + + case 78: /* for_command: FOR WORD newline_list '{' compound_list '}' */ +#line 828 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_for_command ((yyvsp[-4].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2668 "y.tab.c" + break; + + case 79: /* for_command: FOR WORD ';' newline_list DO compound_list DONE */ +#line 833 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_for_command ((yyvsp[-5].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2677 "y.tab.c" + break; + + case 80: /* for_command: FOR WORD ';' newline_list '{' compound_list '}' */ +#line 838 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_for_command ((yyvsp[-5].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2686 "y.tab.c" + break; + + case 81: /* for_command: FOR WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE */ +#line 843 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_for_command ((yyvsp[-8].word), REVERSE_LIST ((yyvsp[-5].word_list), WORD_LIST *), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2695 "y.tab.c" + break; + + case 82: /* for_command: FOR WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}' */ +#line 848 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_for_command ((yyvsp[-8].word), REVERSE_LIST ((yyvsp[-5].word_list), WORD_LIST *), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2704 "y.tab.c" + break; + + case 83: /* for_command: FOR WORD newline_list IN list_terminator newline_list DO compound_list DONE */ +#line 853 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_for_command ((yyvsp[-7].word), (WORD_LIST *)NULL, (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2713 "y.tab.c" + break; + + case 84: /* for_command: FOR WORD newline_list IN list_terminator newline_list '{' compound_list '}' */ +#line 858 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_for_command ((yyvsp[-7].word), (WORD_LIST *)NULL, (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2722 "y.tab.c" + break; + + case 85: /* arith_for_command: FOR ARITH_FOR_EXPRS list_terminator newline_list DO compound_list DONE */ +#line 865 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_arith_for_command ((yyvsp[-5].word_list), (yyvsp[-1].command), arith_for_lineno); + if ((yyval.command) == 0) YYERROR; + if (word_top > 0) word_top--; + } +#line 2732 "y.tab.c" + break; + + case 86: /* arith_for_command: FOR ARITH_FOR_EXPRS list_terminator newline_list '{' compound_list '}' */ +#line 871 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_arith_for_command ((yyvsp[-5].word_list), (yyvsp[-1].command), arith_for_lineno); + if ((yyval.command) == 0) YYERROR; + if (word_top > 0) word_top--; + } +#line 2742 "y.tab.c" + break; + + case 87: /* arith_for_command: FOR ARITH_FOR_EXPRS DO compound_list DONE */ +#line 877 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_arith_for_command ((yyvsp[-3].word_list), (yyvsp[-1].command), arith_for_lineno); + if ((yyval.command) == 0) YYERROR; + if (word_top > 0) word_top--; + } +#line 2752 "y.tab.c" + break; + + case 88: /* arith_for_command: FOR ARITH_FOR_EXPRS '{' compound_list '}' */ +#line 883 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_arith_for_command ((yyvsp[-3].word_list), (yyvsp[-1].command), arith_for_lineno); + if ((yyval.command) == 0) YYERROR; + if (word_top > 0) word_top--; + } +#line 2762 "y.tab.c" + break; + + case 89: /* select_command: SELECT WORD newline_list DO compound_list DONE */ +#line 891 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_select_command ((yyvsp[-4].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2771 "y.tab.c" + break; + + case 90: /* select_command: SELECT WORD newline_list '{' compound_list '}' */ +#line 896 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_select_command ((yyvsp[-4].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2780 "y.tab.c" + break; + + case 91: /* select_command: SELECT WORD ';' newline_list DO compound_list DONE */ +#line 901 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_select_command ((yyvsp[-5].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2789 "y.tab.c" + break; + + case 92: /* select_command: SELECT WORD ';' newline_list '{' compound_list '}' */ +#line 906 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_select_command ((yyvsp[-5].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2798 "y.tab.c" + break; + + case 93: /* select_command: SELECT WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE */ +#line 911 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_select_command ((yyvsp[-8].word), REVERSE_LIST ((yyvsp[-5].word_list), WORD_LIST *), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2807 "y.tab.c" + break; + + case 94: /* select_command: SELECT WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}' */ +#line 916 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_select_command ((yyvsp[-8].word), REVERSE_LIST ((yyvsp[-5].word_list), WORD_LIST *), (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2816 "y.tab.c" + break; + + case 95: /* select_command: SELECT WORD newline_list IN list_terminator newline_list DO compound_list DONE */ +#line 921 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_select_command ((yyvsp[-7].word), (WORD_LIST *)NULL, (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2825 "y.tab.c" + break; + + case 96: /* select_command: SELECT WORD newline_list IN list_terminator newline_list '{' compound_list '}' */ +#line 926 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_select_command ((yyvsp[-7].word), (WORD_LIST *)NULL, (yyvsp[-1].command), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2834 "y.tab.c" + break; + + case 97: /* case_command: CASE WORD newline_list IN newline_list ESAC */ +#line 933 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_case_command ((yyvsp[-4].word), (PATTERN_LIST *)NULL, word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2843 "y.tab.c" + break; + + case 98: /* case_command: CASE WORD newline_list IN case_clause_sequence newline_list ESAC */ +#line 938 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_case_command ((yyvsp[-5].word), (yyvsp[-2].pattern), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2852 "y.tab.c" + break; + + case 99: /* case_command: CASE WORD newline_list IN case_clause ESAC */ +#line 943 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_case_command ((yyvsp[-4].word), (yyvsp[-1].pattern), word_lineno[word_top]); + if (word_top > 0) word_top--; + } +#line 2861 "y.tab.c" + break; + + case 100: /* function_def: WORD '(' ')' newline_list function_body */ +#line 950 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_function_def ((yyvsp[-4].word), (yyvsp[0].command), function_dstart, function_bstart); } +#line 2867 "y.tab.c" + break; + + case 101: /* function_def: FUNCTION WORD '(' ')' newline_list function_body */ +#line 952 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_function_def ((yyvsp[-4].word), (yyvsp[0].command), function_dstart, function_bstart); } +#line 2873 "y.tab.c" + break; + + case 102: /* function_def: FUNCTION WORD function_body */ +#line 954 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_function_def ((yyvsp[-1].word), (yyvsp[0].command), function_dstart, function_bstart); } +#line 2879 "y.tab.c" + break; + + case 103: /* function_def: FUNCTION WORD '\n' newline_list function_body */ +#line 956 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_function_def ((yyvsp[-3].word), (yyvsp[0].command), function_dstart, function_bstart); } +#line 2885 "y.tab.c" + break; + + case 104: /* function_body: shell_command */ +#line 960 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 2891 "y.tab.c" + break; + + case 105: /* function_body: shell_command redirection_list */ +#line 962 "/usr/local/src/chet/src/bash/src/parse.y" + { + COMMAND *tc; + + tc = (yyvsp[-1].command); + /* According to Posix.2 3.9.5, redirections + specified after the body of a function should + be attached to the function and performed when + the function is executed, not as part of the + function definition command. */ + /* XXX - I don't think it matters, but we might + want to change this in the future to avoid + problems differentiating between a function + definition with a redirection and a function + definition containing a single command with a + redirection. The two are semantically equivalent, + though -- the only difference is in how the + command printing code displays the redirections. */ + if (tc && tc->redirects) + { + register REDIRECT *t; + for (t = tc->redirects; t->next; t = t->next) + ; + t->next = (yyvsp[0].redirect); + } + else if (tc) + tc->redirects = (yyvsp[0].redirect); + (yyval.command) = (yyvsp[-1].command); + } +#line 2924 "y.tab.c" + break; + + case 106: /* subshell: '(' compound_list ')' */ +#line 993 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_subshell_command ((yyvsp[-1].command)); + (yyval.command)->flags |= CMD_WANT_SUBSHELL; + } +#line 2933 "y.tab.c" + break; + + case 107: /* comsub: DOLPAREN compound_list ')' */ +#line 1000 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = (yyvsp[-1].command); + } +#line 2941 "y.tab.c" + break; + + case 108: /* comsub: DOLPAREN newline_list ')' */ +#line 1004 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = (COMMAND *)NULL; + } +#line 2949 "y.tab.c" + break; + + case 109: /* coproc: COPROC shell_command */ +#line 1010 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_coproc_command ("COPROC", (yyvsp[0].command)); + (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL; + } +#line 2958 "y.tab.c" + break; + + case 110: /* coproc: COPROC shell_command redirection_list */ +#line 1015 "/usr/local/src/chet/src/bash/src/parse.y" + { + COMMAND *tc; + + tc = (yyvsp[-1].command); + if (tc && tc->redirects) + { + register REDIRECT *t; + for (t = tc->redirects; t->next; t = t->next) + ; + t->next = (yyvsp[0].redirect); + } + else if (tc) + tc->redirects = (yyvsp[0].redirect); + (yyval.command) = make_coproc_command ("COPROC", (yyvsp[-1].command)); + (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL; + } +#line 2979 "y.tab.c" + break; + + case 111: /* coproc: COPROC WORD shell_command */ +#line 1032 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_coproc_command ((yyvsp[-1].word)->word, (yyvsp[0].command)); + (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL; + } +#line 2988 "y.tab.c" + break; + + case 112: /* coproc: COPROC WORD shell_command redirection_list */ +#line 1037 "/usr/local/src/chet/src/bash/src/parse.y" + { + COMMAND *tc; + + tc = (yyvsp[-1].command); + if (tc && tc->redirects) + { + register REDIRECT *t; + for (t = tc->redirects; t->next; t = t->next) + ; + t->next = (yyvsp[0].redirect); + } + else if (tc) + tc->redirects = (yyvsp[0].redirect); + (yyval.command) = make_coproc_command ((yyvsp[-2].word)->word, (yyvsp[-1].command)); + (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL; + } +#line 3009 "y.tab.c" + break; + + case 113: /* coproc: COPROC simple_command */ +#line 1054 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = make_coproc_command ("COPROC", clean_simple_command ((yyvsp[0].command))); + (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL; + } +#line 3018 "y.tab.c" + break; + + case 114: /* if_command: IF compound_list THEN compound_list FI */ +#line 1061 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_if_command ((yyvsp[-3].command), (yyvsp[-1].command), (COMMAND *)NULL); } +#line 3024 "y.tab.c" + break; + + case 115: /* if_command: IF compound_list THEN compound_list ELSE compound_list FI */ +#line 1063 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_if_command ((yyvsp[-5].command), (yyvsp[-3].command), (yyvsp[-1].command)); } +#line 3030 "y.tab.c" + break; + + case 116: /* if_command: IF compound_list THEN compound_list elif_clause FI */ +#line 1065 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_if_command ((yyvsp[-4].command), (yyvsp[-2].command), (yyvsp[-1].command)); } +#line 3036 "y.tab.c" + break; + + case 117: /* group_command: '{' compound_list '}' */ +#line 1070 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_group_command ((yyvsp[-1].command)); } +#line 3042 "y.tab.c" + break; + + case 118: /* arith_command: ARITH_CMD */ +#line 1074 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_arith_command ((yyvsp[0].word_list)); } +#line 3048 "y.tab.c" + break; + + case 119: /* cond_command: COND_START COND_CMD COND_END */ +#line 1078 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[-1].command); } +#line 3054 "y.tab.c" + break; + + case 120: /* elif_clause: ELIF compound_list THEN compound_list */ +#line 1082 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_if_command ((yyvsp[-2].command), (yyvsp[0].command), (COMMAND *)NULL); } +#line 3060 "y.tab.c" + break; + + case 121: /* elif_clause: ELIF compound_list THEN compound_list ELSE compound_list */ +#line 1084 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_if_command ((yyvsp[-4].command), (yyvsp[-2].command), (yyvsp[0].command)); } +#line 3066 "y.tab.c" + break; + + case 122: /* elif_clause: ELIF compound_list THEN compound_list elif_clause */ +#line 1086 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = make_if_command ((yyvsp[-3].command), (yyvsp[-1].command), (yyvsp[0].command)); } +#line 3072 "y.tab.c" + break; + + case 124: /* case_clause: case_clause_sequence pattern_list */ +#line 1091 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyvsp[0].pattern)->next = (yyvsp[-1].pattern); (yyval.pattern) = (yyvsp[0].pattern); } +#line 3078 "y.tab.c" + break; + + case 125: /* pattern_list: newline_list pattern ')' compound_list */ +#line 1095 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.pattern) = make_pattern_list ((yyvsp[-2].word_list), (yyvsp[0].command)); } +#line 3084 "y.tab.c" + break; + + case 126: /* pattern_list: newline_list pattern ')' newline_list */ +#line 1097 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.pattern) = make_pattern_list ((yyvsp[-2].word_list), (COMMAND *)NULL); } +#line 3090 "y.tab.c" + break; + + case 127: /* pattern_list: newline_list '(' pattern ')' compound_list */ +#line 1099 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.pattern) = make_pattern_list ((yyvsp[-2].word_list), (yyvsp[0].command)); } +#line 3096 "y.tab.c" + break; + + case 128: /* pattern_list: newline_list '(' pattern ')' newline_list */ +#line 1101 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.pattern) = make_pattern_list ((yyvsp[-2].word_list), (COMMAND *)NULL); } +#line 3102 "y.tab.c" + break; + + case 129: /* case_clause_sequence: pattern_list SEMI_SEMI */ +#line 1105 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.pattern) = (yyvsp[-1].pattern); } +#line 3108 "y.tab.c" + break; + + case 130: /* case_clause_sequence: case_clause_sequence pattern_list SEMI_SEMI */ +#line 1107 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyvsp[-1].pattern)->next = (yyvsp[-2].pattern); (yyval.pattern) = (yyvsp[-1].pattern); } +#line 3114 "y.tab.c" + break; + + case 131: /* case_clause_sequence: pattern_list SEMI_AND */ +#line 1109 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyvsp[-1].pattern)->flags |= CASEPAT_FALLTHROUGH; (yyval.pattern) = (yyvsp[-1].pattern); } +#line 3120 "y.tab.c" + break; + + case 132: /* case_clause_sequence: case_clause_sequence pattern_list SEMI_AND */ +#line 1111 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyvsp[-1].pattern)->flags |= CASEPAT_FALLTHROUGH; (yyvsp[-1].pattern)->next = (yyvsp[-2].pattern); (yyval.pattern) = (yyvsp[-1].pattern); } +#line 3126 "y.tab.c" + break; + + case 133: /* case_clause_sequence: pattern_list SEMI_SEMI_AND */ +#line 1113 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyvsp[-1].pattern)->flags |= CASEPAT_TESTNEXT; (yyval.pattern) = (yyvsp[-1].pattern); } +#line 3132 "y.tab.c" + break; + + case 134: /* case_clause_sequence: case_clause_sequence pattern_list SEMI_SEMI_AND */ +#line 1115 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyvsp[-1].pattern)->flags |= CASEPAT_TESTNEXT; (yyvsp[-1].pattern)->next = (yyvsp[-2].pattern); (yyval.pattern) = (yyvsp[-1].pattern); } +#line 3138 "y.tab.c" + break; + + case 135: /* pattern: WORD */ +#line 1119 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.word_list) = make_word_list ((yyvsp[0].word), (WORD_LIST *)NULL); } +#line 3144 "y.tab.c" + break; + + case 136: /* pattern: pattern '|' WORD */ +#line 1121 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.word_list) = make_word_list ((yyvsp[0].word), (yyvsp[-2].word_list)); } +#line 3150 "y.tab.c" + break; + + case 137: /* compound_list: newline_list list0 */ +#line 1130 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = (yyvsp[0].command); + if (need_here_doc && last_read_token == '\n') + gather_here_documents (); + } +#line 3160 "y.tab.c" + break; + + case 138: /* compound_list: newline_list list1 */ +#line 1136 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = (yyvsp[0].command); + } +#line 3168 "y.tab.c" + break; + + case 140: /* list0: list1 '&' newline_list */ +#line 1143 "/usr/local/src/chet/src/bash/src/parse.y" + { + if ((yyvsp[-2].command)->type == cm_connection) + (yyval.command) = connect_async_list ((yyvsp[-2].command), (COMMAND *)NULL, '&'); + else + (yyval.command) = command_connect ((yyvsp[-2].command), (COMMAND *)NULL, '&'); + } +#line 3179 "y.tab.c" + break; + + case 142: /* list1: list1 AND_AND newline_list list1 */ +#line 1154 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), AND_AND); } +#line 3185 "y.tab.c" + break; + + case 143: /* list1: list1 OR_OR newline_list list1 */ +#line 1156 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), OR_OR); } +#line 3191 "y.tab.c" + break; + + case 144: /* list1: list1 '&' newline_list list1 */ +#line 1158 "/usr/local/src/chet/src/bash/src/parse.y" + { + if ((yyvsp[-3].command)->type == cm_connection) + (yyval.command) = connect_async_list ((yyvsp[-3].command), (yyvsp[0].command), '&'); + else + (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), '&'); + } +#line 3202 "y.tab.c" + break; + + case 145: /* list1: list1 ';' newline_list list1 */ +#line 1165 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), ';'); } +#line 3208 "y.tab.c" + break; + + case 146: /* list1: list1 '\n' newline_list list1 */ +#line 1167 "/usr/local/src/chet/src/bash/src/parse.y" + { + if (parser_state & PST_CMDSUBST) + (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), '\n'); + else + (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), ';'); + } +#line 3219 "y.tab.c" + break; + + case 147: /* list1: pipeline_command */ +#line 1174 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 3225 "y.tab.c" + break; + + case 150: /* list_terminator: '\n' */ +#line 1182 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.number) = '\n'; } +#line 3231 "y.tab.c" + break; + + case 151: /* list_terminator: ';' */ +#line 1184 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.number) = ';'; } +#line 3237 "y.tab.c" + break; + + case 152: /* list_terminator: yacc_EOF */ +#line 1186 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.number) = yacc_EOF; } +#line 3243 "y.tab.c" + break; + + case 155: /* simple_list: simple_list1 */ +#line 1200 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = (yyvsp[0].command); + if (need_here_doc) + gather_here_documents (); /* XXX */ + if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token) + { +INTERNAL_DEBUG (("LEGACY: parser: command substitution simple_list1 -> simple_list")); + global_command = (yyvsp[0].command); + eof_encountered = 0; + if (bash_input.type == st_string) + rewind_input_string (); + YYACCEPT; + } + } +#line 3262 "y.tab.c" + break; + + case 156: /* simple_list: simple_list1 '&' */ +#line 1215 "/usr/local/src/chet/src/bash/src/parse.y" + { + if ((yyvsp[-1].command)->type == cm_connection) + (yyval.command) = connect_async_list ((yyvsp[-1].command), (COMMAND *)NULL, '&'); + else + (yyval.command) = command_connect ((yyvsp[-1].command), (COMMAND *)NULL, '&'); + if (need_here_doc) + gather_here_documents (); /* XXX */ + if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token) + { +INTERNAL_DEBUG (("LEGACY: parser: command substitution simple_list1 '&' -> simple_list")); + global_command = (yyvsp[-1].command); + eof_encountered = 0; + if (bash_input.type == st_string) + rewind_input_string (); + YYACCEPT; + } + } +#line 3284 "y.tab.c" + break; + + case 157: /* simple_list: simple_list1 ';' */ +#line 1233 "/usr/local/src/chet/src/bash/src/parse.y" + { + (yyval.command) = (yyvsp[-1].command); + if (need_here_doc) + gather_here_documents (); /* XXX */ + if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token) + { +INTERNAL_DEBUG (("LEGACY: parser: command substitution simple_list1 ';' -> simple_list")); + global_command = (yyvsp[-1].command); + eof_encountered = 0; + if (bash_input.type == st_string) + rewind_input_string (); + YYACCEPT; + } + } +#line 3303 "y.tab.c" + break; + + case 158: /* simple_list1: simple_list1 AND_AND newline_list simple_list1 */ +#line 1250 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), AND_AND); } +#line 3309 "y.tab.c" + break; + + case 159: /* simple_list1: simple_list1 OR_OR newline_list simple_list1 */ +#line 1252 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), OR_OR); } +#line 3315 "y.tab.c" + break; + + case 160: /* simple_list1: simple_list1 '&' simple_list1 */ +#line 1254 "/usr/local/src/chet/src/bash/src/parse.y" + { + if ((yyvsp[-2].command)->type == cm_connection) + (yyval.command) = connect_async_list ((yyvsp[-2].command), (yyvsp[0].command), '&'); + else + (yyval.command) = command_connect ((yyvsp[-2].command), (yyvsp[0].command), '&'); + } +#line 3326 "y.tab.c" + break; + + case 161: /* simple_list1: simple_list1 ';' simple_list1 */ +#line 1261 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = command_connect ((yyvsp[-2].command), (yyvsp[0].command), ';'); } +#line 3332 "y.tab.c" + break; + + case 162: /* simple_list1: pipeline_command */ +#line 1264 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 3338 "y.tab.c" + break; + + case 163: /* pipeline_command: pipeline */ +#line 1268 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 3344 "y.tab.c" + break; + + case 164: /* pipeline_command: BANG pipeline_command */ +#line 1270 "/usr/local/src/chet/src/bash/src/parse.y" + { + if ((yyvsp[0].command)) + (yyvsp[0].command)->flags ^= CMD_INVERT_RETURN; /* toggle */ + (yyval.command) = (yyvsp[0].command); + } +#line 3354 "y.tab.c" + break; + + case 165: /* pipeline_command: timespec pipeline_command */ +#line 1276 "/usr/local/src/chet/src/bash/src/parse.y" + { + if ((yyvsp[0].command)) + (yyvsp[0].command)->flags |= (yyvsp[-1].number); + (yyval.command) = (yyvsp[0].command); + } +#line 3364 "y.tab.c" + break; + + case 166: /* pipeline_command: timespec list_terminator */ +#line 1282 "/usr/local/src/chet/src/bash/src/parse.y" + { + ELEMENT x; + + /* Boy, this is unclean. `time' by itself can + time a null command. We cheat and push a + newline back if the list_terminator was a newline + to avoid the double-newline problem (one to + terminate this, one to terminate the command) */ + x.word = 0; + x.redirect = 0; + (yyval.command) = make_simple_command (x, (COMMAND *)NULL); + (yyval.command)->flags |= (yyvsp[-1].number); + /* XXX - let's cheat and push a newline back */ + if ((yyvsp[0].number) == '\n') + token_to_read = '\n'; + else if ((yyvsp[0].number) == ';') + token_to_read = ';'; + parser_state &= ~PST_REDIRLIST; /* make_simple_command sets this */ + } +#line 3388 "y.tab.c" + break; + + case 167: /* pipeline_command: BANG list_terminator */ +#line 1302 "/usr/local/src/chet/src/bash/src/parse.y" + { + ELEMENT x; + + /* This is just as unclean. Posix says that `!' + by itself should be equivalent to `false'. + We cheat and push a + newline back if the list_terminator was a newline + to avoid the double-newline problem (one to + terminate this, one to terminate the command) */ + x.word = 0; + x.redirect = 0; + (yyval.command) = make_simple_command (x, (COMMAND *)NULL); + (yyval.command)->flags |= CMD_INVERT_RETURN; + /* XXX - let's cheat and push a newline back */ + if ((yyvsp[0].number) == '\n') + token_to_read = '\n'; + if ((yyvsp[0].number) == ';') + token_to_read = ';'; + parser_state &= ~PST_REDIRLIST; /* make_simple_command sets this */ + } +#line 3413 "y.tab.c" + break; + + case 168: /* pipeline: pipeline '|' newline_list pipeline */ +#line 1325 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), '|'); } +#line 3419 "y.tab.c" + break; + + case 169: /* pipeline: pipeline BAR_AND newline_list pipeline */ +#line 1327 "/usr/local/src/chet/src/bash/src/parse.y" + { + /* Make cmd1 |& cmd2 equivalent to cmd1 2>&1 | cmd2 */ + COMMAND *tc; + REDIRECTEE rd, sd; + REDIRECT *r; + + tc = (yyvsp[-3].command)->type == cm_simple ? (COMMAND *)(yyvsp[-3].command)->value.Simple : (yyvsp[-3].command); + sd.dest = 2; + rd.dest = 1; + r = make_redirection (sd, r_duplicating_output, rd, 0); + if (tc->redirects) + { + register REDIRECT *t; + for (t = tc->redirects; t->next; t = t->next) + ; + t->next = r; + } + else + tc->redirects = r; + + (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), '|'); + } +#line 3446 "y.tab.c" + break; + + case 170: /* pipeline: command */ +#line 1350 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.command) = (yyvsp[0].command); } +#line 3452 "y.tab.c" + break; + + case 171: /* timespec: TIME */ +#line 1354 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.number) = CMD_TIME_PIPELINE; } +#line 3458 "y.tab.c" + break; + + case 172: /* timespec: TIME TIMEOPT */ +#line 1356 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.number) = CMD_TIME_PIPELINE|CMD_TIME_POSIX; } +#line 3464 "y.tab.c" + break; + + case 173: /* timespec: TIME TIMEIGN */ +#line 1358 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.number) = CMD_TIME_PIPELINE|CMD_TIME_POSIX; } +#line 3470 "y.tab.c" + break; + + case 174: /* timespec: TIME TIMEOPT TIMEIGN */ +#line 1360 "/usr/local/src/chet/src/bash/src/parse.y" + { (yyval.number) = CMD_TIME_PIPELINE|CMD_TIME_POSIX; } +#line 3476 "y.tab.c" + break; + + +#line 3480 "y.tab.c" + + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", YY_CAST (yysymbol_kind_t, yyr1[yyn]), &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + + *++yyvsp = yyval; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + { + const int yylhs = yyr1[yyn] - YYNTOKENS; + const int yyi = yypgoto[yylhs] + *yyssp; + yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp + ? yytable[yyi] + : yydefgoto[yylhs]); + } + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar); + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; + yyerror (YY_("syntax error")); + } + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + /* Pacify compilers when the user code never invokes YYERROR and the + label yyerrorlab therefore never appears in user code. */ + if (0) + YYERROR; + ++yynerrs; + + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + /* Pop stack until we find a state that shifts the error token. */ + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYSYMBOL_YYerror; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYSYMBOL_YYerror) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + + yydestruct ("Error: popping", + YY_ACCESSING_SYMBOL (yystate), yyvsp); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturnlab; + + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturnlab; + + +/*-----------------------------------------------------------. +| yyexhaustedlab -- YYNOMEM (memory exhaustion) comes here. | +`-----------------------------------------------------------*/ +yyexhaustedlab: + yyerror (YY_("memory exhausted")); + yyresult = 2; + goto yyreturnlab; + + +/*----------------------------------------------------------. +| yyreturnlab -- parsing is finished, clean up and return. | +`----------------------------------------------------------*/ +yyreturnlab: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + YY_ACCESSING_SYMBOL (+*yyssp), yyvsp); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + + return yyresult; +} + +#line 1362 "/usr/local/src/chet/src/bash/src/parse.y" + + +/* Initial size to allocate for tokens, and the + amount to grow them by. */ +#define TOKEN_DEFAULT_INITIAL_SIZE 496 +#define TOKEN_DEFAULT_GROW_SIZE 512 + +/* Should we call prompt_again? */ +#define SHOULD_PROMPT() \ + (interactive && (bash_input.type == st_stdin || bash_input.type == st_stream)) + +#if defined (ALIAS) +# define expanding_alias() (pushed_string_list && pushed_string_list->expander) +#else +# define expanding_alias() 0 +#endif + +/* Global var is non-zero when end of file has been reached. */ +int EOF_Reached = 0; + +#ifdef DEBUG +static void +debug_parser (i) + int i; +{ +#if YYDEBUG != 0 + yydebug = i; + yyoutstream = stdout; + yyerrstream = stderr; +#endif +} +#endif + +/* yy_getc () returns the next available character from input or EOF. + yy_ungetc (c) makes `c' the next character to read. + init_yy_io (get, unget, type, location) makes the function GET the + installed function for getting the next character, makes UNGET the + installed function for un-getting a character, sets the type of stream + (either string or file) from TYPE, and makes LOCATION point to where + the input is coming from. */ + +/* Unconditionally returns end-of-file. */ +int +return_EOF () +{ + return (EOF); +} + +/* Variable containing the current get and unget functions. + See ./input.h for a clearer description. */ +BASH_INPUT bash_input; + +/* Set all of the fields in BASH_INPUT to NULL. Free bash_input.name if it + is non-null, avoiding a memory leak. */ +void +initialize_bash_input () +{ + bash_input.type = st_none; + FREE (bash_input.name); + bash_input.name = (char *)NULL; + bash_input.location.file = (FILE *)NULL; + bash_input.location.string = (char *)NULL; + bash_input.getter = (sh_cget_func_t *)NULL; + bash_input.ungetter = (sh_cunget_func_t *)NULL; +} + +/* Set the contents of the current bash input stream from + GET, UNGET, TYPE, NAME, and LOCATION. */ +void +init_yy_io (get, unget, type, name, location) + sh_cget_func_t *get; + sh_cunget_func_t *unget; + enum stream_type type; + const char *name; + INPUT_STREAM location; +{ + bash_input.type = type; + FREE (bash_input.name); + bash_input.name = name ? savestring (name) : (char *)NULL; + + /* XXX */ +#if defined (CRAY) + memcpy((char *)&bash_input.location.string, (char *)&location.string, sizeof(location)); +#else + bash_input.location = location; +#endif + bash_input.getter = get; + bash_input.ungetter = unget; +} + +char * +yy_input_name () +{ + return (bash_input.name ? bash_input.name : "stdin"); +} + +/* Call this to get the next character of input. */ +static int +yy_getc () +{ + return (*(bash_input.getter)) (); +} + +/* Call this to unget C. That is, to make C the next character + to be read. */ +static int +yy_ungetc (c) + int c; +{ + return (*(bash_input.ungetter)) (c); +} + +#if defined (BUFFERED_INPUT) +#ifdef INCLUDE_UNUSED +int +input_file_descriptor () +{ + switch (bash_input.type) + { + case st_stream: + return (fileno (bash_input.location.file)); + case st_bstream: + return (bash_input.location.buffered_fd); + case st_stdin: + default: + return (fileno (stdin)); + } +} +#endif +#endif /* BUFFERED_INPUT */ + +/* **************************************************************** */ +/* */ +/* Let input be read from readline (). */ +/* */ +/* **************************************************************** */ + +#if defined (READLINE) +char *current_readline_prompt = (char *)NULL; +char *current_readline_line = (char *)NULL; +int current_readline_line_index = 0; + +static int +yy_readline_get () +{ + SigHandler *old_sigint; + int line_len; + unsigned char c; + + if (current_readline_line == 0) + { + if (bash_readline_initialized == 0) + initialize_readline (); + +#if defined (JOB_CONTROL) + if (job_control) + give_terminal_to (shell_pgrp, 0); +#endif /* JOB_CONTROL */ + + old_sigint = IMPOSSIBLE_TRAP_HANDLER; + if (signal_is_ignored (SIGINT) == 0) + { + old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler); + } + + sh_unset_nodelay_mode (fileno (rl_instream)); /* just in case */ + current_readline_line = readline (current_readline_prompt ? + current_readline_prompt : ""); + + CHECK_TERMSIG; + if (signal_is_ignored (SIGINT) == 0) + { + if (old_sigint != IMPOSSIBLE_TRAP_HANDLER) + set_signal_handler (SIGINT, old_sigint); + } + +#if 0 + /* Reset the prompt to the decoded value of prompt_string_pointer. */ + reset_readline_prompt (); +#endif + + if (current_readline_line == 0) + return (EOF); + + current_readline_line_index = 0; + line_len = strlen (current_readline_line); + + current_readline_line = (char *)xrealloc (current_readline_line, 2 + line_len); + current_readline_line[line_len++] = '\n'; + current_readline_line[line_len] = '\0'; + } + + if (current_readline_line[current_readline_line_index] == 0) + { + free (current_readline_line); + current_readline_line = (char *)NULL; + return (yy_readline_get ()); + } + else + { + c = current_readline_line[current_readline_line_index++]; + return (c); + } +} + +static int +yy_readline_unget (c) + int c; +{ + if (current_readline_line_index && current_readline_line) + current_readline_line[--current_readline_line_index] = c; + return (c); +} + +void +with_input_from_stdin () +{ + INPUT_STREAM location; + + if (bash_input.type != st_stdin && stream_on_stack (st_stdin) == 0) + { + location.string = current_readline_line; + init_yy_io (yy_readline_get, yy_readline_unget, + st_stdin, "readline stdin", location); + } +} + +/* Will we be collecting another input line and printing a prompt? This uses + different conditions than SHOULD_PROMPT(), since readline allows a user to + embed a newline in the middle of the line it collects, which the parser + will interpret as a line break and command delimiter. */ +int +parser_will_prompt () +{ + return (current_readline_line == 0 || current_readline_line[current_readline_line_index] == 0); +} + +#else /* !READLINE */ + +void +with_input_from_stdin () +{ + with_input_from_stream (stdin, "stdin"); +} +#endif /* !READLINE */ + +/* **************************************************************** */ +/* */ +/* Let input come from STRING. STRING is zero terminated. */ +/* */ +/* **************************************************************** */ + +static int +yy_string_get () +{ + register char *string; + register unsigned char c; + + string = bash_input.location.string; + + /* If the string doesn't exist, or is empty, EOF found. */ + if (string && *string) + { + c = *string++; + bash_input.location.string = string; + return (c); + } + else + return (EOF); +} + +static int +yy_string_unget (c) + int c; +{ + *(--bash_input.location.string) = c; + return (c); +} + +void +with_input_from_string (string, name) + char *string; + const char *name; +{ + INPUT_STREAM location; + + location.string = string; + init_yy_io (yy_string_get, yy_string_unget, st_string, name, location); +} + +/* Count the number of characters we've consumed from bash_input.location.string + and read into shell_input_line, but have not returned from shell_getc. + That is the true input location. Rewind bash_input.location.string by + that number of characters, so it points to the last character actually + consumed by the parser. */ +void +rewind_input_string () +{ + int xchars; + + /* number of unconsumed characters in the input -- XXX need to take newlines + into account, e.g., $(...\n) */ + xchars = shell_input_line_len - shell_input_line_index; + if (bash_input.location.string[-1] == '\n') + xchars++; + + /* XXX - how to reflect bash_input.location.string back to string passed to + parse_and_execute or xparse_dolparen? xparse_dolparen needs to know how + far into the string we parsed. parse_and_execute knows where bash_input. + location.string is, and how far from orig_string that is -- that's the + number of characters the command consumed. */ + + /* bash_input.location.string - xchars should be where we parsed to */ + /* need to do more validation on xchars value for sanity -- test cases. */ + bash_input.location.string -= xchars; +} + +/* **************************************************************** */ +/* */ +/* Let input come from STREAM. */ +/* */ +/* **************************************************************** */ + +/* These two functions used to test the value of the HAVE_RESTARTABLE_SYSCALLS + define, and just use getc/ungetc if it was defined, but since bash + installs most of its signal handlers without the SA_RESTART flag, some + signals received during a read(2) will not cause the read to be restarted. + We will need to restart it ourselves. */ + +static int +yy_stream_get () +{ + int result; + + result = EOF; + if (bash_input.location.file) + { + /* XXX - don't need terminate_immediately; getc_with_restart checks + for terminating signals itself if read returns < 0 */ + result = getc_with_restart (bash_input.location.file); + } + return (result); +} + +static int +yy_stream_unget (c) + int c; +{ + return (ungetc_with_restart (c, bash_input.location.file)); +} + +void +with_input_from_stream (stream, name) + FILE *stream; + const char *name; +{ + INPUT_STREAM location; + + location.file = stream; + init_yy_io (yy_stream_get, yy_stream_unget, st_stream, name, location); +} + +typedef struct stream_saver { + struct stream_saver *next; + BASH_INPUT bash_input; + int line; +#if defined (BUFFERED_INPUT) + BUFFERED_STREAM *bstream; +#endif /* BUFFERED_INPUT */ +} STREAM_SAVER; + +/* The globally known line number. */ +int line_number = 0; + +/* The line number offset set by assigning to LINENO. Not currently used. */ +int line_number_base = 0; + +#if defined (COND_COMMAND) +static int cond_lineno; +static int cond_token; +#endif + +STREAM_SAVER *stream_list = (STREAM_SAVER *)NULL; + +void +push_stream (reset_lineno) + int reset_lineno; +{ + STREAM_SAVER *saver = (STREAM_SAVER *)xmalloc (sizeof (STREAM_SAVER)); + + xbcopy ((char *)&bash_input, (char *)&(saver->bash_input), sizeof (BASH_INPUT)); + +#if defined (BUFFERED_INPUT) + saver->bstream = (BUFFERED_STREAM *)NULL; + /* If we have a buffered stream, clear out buffers[fd]. */ + if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0) + saver->bstream = set_buffered_stream (bash_input.location.buffered_fd, + (BUFFERED_STREAM *)NULL); +#endif /* BUFFERED_INPUT */ + + saver->line = line_number; + bash_input.name = (char *)NULL; + saver->next = stream_list; + stream_list = saver; + EOF_Reached = 0; + if (reset_lineno) + line_number = 0; +} + +void +pop_stream () +{ + if (!stream_list) + EOF_Reached = 1; + else + { + STREAM_SAVER *saver = stream_list; + + EOF_Reached = 0; + stream_list = stream_list->next; + + init_yy_io (saver->bash_input.getter, + saver->bash_input.ungetter, + saver->bash_input.type, + saver->bash_input.name, + saver->bash_input.location); + +#if defined (BUFFERED_INPUT) + /* If we have a buffered stream, restore buffers[fd]. */ + /* If the input file descriptor was changed while this was on the + save stack, update the buffered fd to the new file descriptor and + re-establish the buffer <-> bash_input fd correspondence. */ + if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0) + { + if (bash_input_fd_changed) + { + bash_input_fd_changed = 0; + if (default_buffered_input >= 0) + { + bash_input.location.buffered_fd = default_buffered_input; + saver->bstream->b_fd = default_buffered_input; + SET_CLOSE_ON_EXEC (default_buffered_input); + } + } + /* XXX could free buffered stream returned as result here. */ + set_buffered_stream (bash_input.location.buffered_fd, saver->bstream); + } +#endif /* BUFFERED_INPUT */ + + line_number = saver->line; + + FREE (saver->bash_input.name); + free (saver); + } +} + +/* Return 1 if a stream of type TYPE is saved on the stack. */ +int +stream_on_stack (type) + enum stream_type type; +{ + register STREAM_SAVER *s; + + for (s = stream_list; s; s = s->next) + if (s->bash_input.type == type) + return 1; + return 0; +} + +/* Save the current token state and return it in a malloced array. */ +int * +save_token_state () +{ + int *ret; + + ret = (int *)xmalloc (4 * sizeof (int)); + ret[0] = last_read_token; + ret[1] = token_before_that; + ret[2] = two_tokens_ago; + ret[3] = current_token; + return ret; +} + +void +restore_token_state (ts) + int *ts; +{ + if (ts == 0) + return; + last_read_token = ts[0]; + token_before_that = ts[1]; + two_tokens_ago = ts[2]; + current_token = ts[3]; +} + +/* + * This is used to inhibit alias expansion and reserved word recognition + * inside case statement pattern lists. A `case statement pattern list' is: + * + * everything between the `in' in a `case word in' and the next ')' + * or `esac' + * everything between a `;;' and the next `)' or `esac' + */ + +#define END_OF_ALIAS 0 + +/* + * Pseudo-global variables used in implementing token-wise alias expansion. + */ + +/* + * Pushing and popping strings. This works together with shell_getc to + * implement alias expansion on a per-token basis. + */ + +#define PSH_ALIAS 0x01 +#define PSH_DPAREN 0x02 +#define PSH_SOURCE 0x04 +#define PSH_ARRAY 0x08 + +typedef struct string_saver { + struct string_saver *next; + int expand_alias; /* Value to set expand_alias to when string is popped. */ + char *saved_line; +#if defined (ALIAS) + alias_t *expander; /* alias that caused this line to be pushed. */ +#endif + size_t saved_line_size, saved_line_index, saved_line_len; + int saved_line_terminator; + int flags; +} STRING_SAVER; + +STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL; + +/* + * Push the current shell_input_line onto a stack of such lines and make S + * the current input. Used when expanding aliases. EXPAND is used to set + * the value of expand_next_token when the string is popped, so that the + * word after the alias in the original line is handled correctly when the + * alias expands to multiple words. TOKEN is the token that was expanded + * into S; it is saved and used to prevent infinite recursive expansion. + */ +static void +push_string (s, expand, ap) + char *s; + int expand; + alias_t *ap; +{ + STRING_SAVER *temp = (STRING_SAVER *)xmalloc (sizeof (STRING_SAVER)); + + temp->expand_alias = expand; + temp->saved_line = shell_input_line; + temp->saved_line_size = shell_input_line_size; + temp->saved_line_len = shell_input_line_len; + temp->saved_line_index = shell_input_line_index; + temp->saved_line_terminator = shell_input_line_terminator; + temp->flags = 0; +#if defined (ALIAS) + temp->expander = ap; + if (ap) + temp->flags = PSH_ALIAS; +#endif + temp->next = pushed_string_list; + pushed_string_list = temp; + +#if defined (ALIAS) + if (ap) + ap->flags |= AL_BEINGEXPANDED; +#endif + + shell_input_line = s; + shell_input_line_size = shell_input_line_len = STRLEN (s); + shell_input_line_index = 0; + shell_input_line_terminator = '\0'; +#if 0 + parser_state &= ~PST_ALEXPNEXT; /* XXX */ +#endif + + set_line_mbstate (); +} + +/* + * Make the top of the pushed_string stack be the current shell input. + * Only called when there is something on the stack. Called from shell_getc + * when it thinks it has consumed the string generated by an alias expansion + * and needs to return to the original input line. + */ +static void +pop_string () +{ + STRING_SAVER *t; + + FREE (shell_input_line); + shell_input_line = pushed_string_list->saved_line; + shell_input_line_index = pushed_string_list->saved_line_index; + shell_input_line_size = pushed_string_list->saved_line_size; + shell_input_line_len = pushed_string_list->saved_line_len; + shell_input_line_terminator = pushed_string_list->saved_line_terminator; + +#if defined (ALIAS) + if (pushed_string_list->expand_alias) + parser_state |= PST_ALEXPNEXT; + else + parser_state &= ~PST_ALEXPNEXT; +#endif + + t = pushed_string_list; + pushed_string_list = pushed_string_list->next; + +#if defined (ALIAS) + if (t->expander) + t->expander->flags &= ~AL_BEINGEXPANDED; +#endif + + free ((char *)t); + + set_line_mbstate (); +} + +static void +free_string_list () +{ + register STRING_SAVER *t, *t1; + + for (t = pushed_string_list; t; ) + { + t1 = t->next; + FREE (t->saved_line); +#if defined (ALIAS) + if (t->expander) + t->expander->flags &= ~AL_BEINGEXPANDED; +#endif + free ((char *)t); + t = t1; + } + pushed_string_list = (STRING_SAVER *)NULL; +} + +void +free_pushed_string_input () +{ +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + free_string_list (); +#endif +} + +int +parser_expanding_alias () +{ + return (expanding_alias ()); +} + +void +parser_save_alias () +{ +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + push_string ((char *)NULL, 0, (alias_t *)NULL); + pushed_string_list->flags = PSH_SOURCE; /* XXX - for now */ +#else + ; +#endif +} + +void +parser_restore_alias () +{ +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + if (pushed_string_list) + pop_string (); +#else + ; +#endif +} + +#if defined (ALIAS) +/* Before freeing AP, make sure that there aren't any cases of pointer + aliasing that could cause us to reference freed memory later on. */ +void +clear_string_list_expander (ap) + alias_t *ap; +{ + register STRING_SAVER *t; + + for (t = pushed_string_list; t; t = t->next) + { + if (t->expander && t->expander == ap) + t->expander = 0; + } +} +#endif + +void +clear_shell_input_line () +{ + if (shell_input_line) + shell_input_line[shell_input_line_index = 0] = '\0'; +} + +/* Return a line of text, taken from wherever yylex () reads input. + If there is no more input, then we return NULL. If REMOVE_QUOTED_NEWLINE + is non-zero, we remove unquoted \ pairs. This is used by + read_secondary_line to read here documents. */ +static char * +read_a_line (remove_quoted_newline) + int remove_quoted_newline; +{ + static char *line_buffer = (char *)NULL; + static int buffer_size = 0; + int indx, c, peekc, pass_next; + +#if defined (READLINE) + if (no_line_editing && SHOULD_PROMPT ()) +#else + if (SHOULD_PROMPT ()) +#endif + print_prompt (); + + pass_next = indx = 0; + while (1) + { + /* Allow immediate exit if interrupted during input. */ + QUIT; + + c = yy_getc (); + + /* Ignore null bytes in input. */ + if (c == 0) + continue; + + /* If there is no more input, then we return NULL. */ + if (c == EOF) + { + if (interactive && bash_input.type == st_stream) + clearerr (stdin); + if (indx == 0) + return ((char *)NULL); + c = '\n'; + } + + /* `+2' in case the final character in the buffer is a newline or we + have to handle CTLESC or CTLNUL. */ + RESIZE_MALLOCED_BUFFER (line_buffer, indx, 2, buffer_size, 128); + + /* IF REMOVE_QUOTED_NEWLINES is non-zero, we are reading a + here document with an unquoted delimiter. In this case, + the line will be expanded as if it were in double quotes. + We allow a backslash to escape the next character, but we + need to treat the backslash specially only if a backslash + quoting a backslash-newline pair appears in the line. */ + if (pass_next) + { + line_buffer[indx++] = c; + pass_next = 0; + } + else if (c == '\\' && remove_quoted_newline) + { + QUIT; + peekc = yy_getc (); + if (peekc == '\n') + { + line_number++; + continue; /* Make the unquoted \ pair disappear. */ + } + else + { + yy_ungetc (peekc); + pass_next = 1; + line_buffer[indx++] = c; /* Preserve the backslash. */ + } + } + else + { + /* remove_quoted_newline is non-zero if the here-document delimiter + is unquoted. In this case, we will be expanding the lines and + need to make sure CTLESC and CTLNUL in the input are quoted. */ + if (remove_quoted_newline && (c == CTLESC || c == CTLNUL)) + line_buffer[indx++] = CTLESC; + line_buffer[indx++] = c; + } + + if (c == '\n') + { + line_buffer[indx] = '\0'; + return (line_buffer); + } + } +} + +/* Return a line as in read_a_line (), but insure that the prompt is + the secondary prompt. This is used to read the lines of a here + document. REMOVE_QUOTED_NEWLINE is non-zero if we should remove + newlines quoted with backslashes while reading the line. It is + non-zero unless the delimiter of the here document was quoted. */ +char * +read_secondary_line (remove_quoted_newline) + int remove_quoted_newline; +{ + char *ret; + int n, c; + + prompt_string_pointer = &ps2_prompt; + if (SHOULD_PROMPT ()) + prompt_again (0); + ret = read_a_line (remove_quoted_newline); +#if defined (HISTORY) + if (ret && remember_on_history && (parser_state & PST_HEREDOC)) + { + /* To make adding the here-document body right, we need to rely on + history_delimiting_chars() returning \n for the first line of the + here-document body and the null string for the second and subsequent + lines, so we avoid double newlines. + current_command_line_count == 2 for the first line of the body. */ + + current_command_line_count++; + maybe_add_history (ret); + } +#endif /* HISTORY */ + return ret; +} + +/* **************************************************************** */ +/* */ +/* YYLEX () */ +/* */ +/* **************************************************************** */ + +/* Reserved words. These are only recognized as the first word of a + command. */ +STRING_INT_ALIST word_token_alist[] = { + { "if", IF }, + { "then", THEN }, + { "else", ELSE }, + { "elif", ELIF }, + { "fi", FI }, + { "case", CASE }, + { "esac", ESAC }, + { "for", FOR }, +#if defined (SELECT_COMMAND) + { "select", SELECT }, +#endif + { "while", WHILE }, + { "until", UNTIL }, + { "do", DO }, + { "done", DONE }, + { "in", IN }, + { "function", FUNCTION }, +#if defined (COMMAND_TIMING) + { "time", TIME }, +#endif + { "{", '{' }, + { "}", '}' }, + { "!", BANG }, +#if defined (COND_COMMAND) + { "[[", COND_START }, + { "]]", COND_END }, +#endif +#if defined (COPROCESS_SUPPORT) + { "coproc", COPROC }, +#endif + { (char *)NULL, 0} +}; + +/* other tokens that can be returned by read_token() */ +STRING_INT_ALIST other_token_alist[] = { + /* Multiple-character tokens with special values */ + { "--", TIMEIGN }, + { "-p", TIMEOPT }, + { "&&", AND_AND }, + { "||", OR_OR }, + { ">>", GREATER_GREATER }, + { "<<", LESS_LESS }, + { "<&", LESS_AND }, + { ">&", GREATER_AND }, + { ";;", SEMI_SEMI }, + { ";&", SEMI_AND }, + { ";;&", SEMI_SEMI_AND }, + { "<<-", LESS_LESS_MINUS }, + { "<<<", LESS_LESS_LESS }, + { "&>", AND_GREATER }, + { "&>>", AND_GREATER_GREATER }, + { "<>", LESS_GREATER }, + { ">|", GREATER_BAR }, + { "|&", BAR_AND }, + { "EOF", yacc_EOF }, + /* Tokens whose value is the character itself */ + { ">", '>' }, + { "<", '<' }, + { "-", '-' }, + { "{", '{' }, + { "}", '}' }, + { ";", ';' }, + { "(", '(' }, + { ")", ')' }, + { "|", '|' }, + { "&", '&' }, + { "newline", '\n' }, + { (char *)NULL, 0} +}; + +/* others not listed here: + WORD look at yylval.word + ASSIGNMENT_WORD look at yylval.word + NUMBER look at yylval.number + ARITH_CMD look at yylval.word_list + ARITH_FOR_EXPRS look at yylval.word_list + COND_CMD look at yylval.command +*/ + +/* These are used by read_token_word, but appear up here so that shell_getc + can use them to decide when to add otherwise blank lines to the history. */ + +/* The primary delimiter stack. */ +struct dstack dstack = { (char *)NULL, 0, 0 }; + +/* A temporary delimiter stack to be used when decoding prompt strings. + This is needed because command substitutions in prompt strings (e.g., PS2) + can screw up the parser's quoting state. */ +static struct dstack temp_dstack = { (char *)NULL, 0, 0 }; + +/* Macro for accessing the top delimiter on the stack. Returns the + delimiter or zero if none. */ +#define current_delimiter(ds) \ + (ds.delimiter_depth ? ds.delimiters[ds.delimiter_depth - 1] : 0) + +#define push_delimiter(ds, character) \ + do \ + { \ + if (ds.delimiter_depth + 2 > ds.delimiter_space) \ + ds.delimiters = (char *)xrealloc \ + (ds.delimiters, (ds.delimiter_space += 10) * sizeof (char)); \ + ds.delimiters[ds.delimiter_depth] = character; \ + ds.delimiter_depth++; \ + } \ + while (0) + +#define pop_delimiter(ds) ds.delimiter_depth-- + +/* Return the next shell input character. This always reads characters + from shell_input_line; when that line is exhausted, it is time to + read the next line. This is called by read_token when the shell is + processing normal command input. */ + +/* This implements one-character lookahead/lookbehind across physical input + lines, to avoid something being lost because it's pushed back with + shell_ungetc when we're at the start of a line. */ +static int eol_ungetc_lookahead = 0; + +static int unquoted_backslash = 0; + +static int +shell_getc (remove_quoted_newline) + int remove_quoted_newline; +{ + register int i; + int c, truncating, last_was_backslash; + unsigned char uc; + + QUIT; + + last_was_backslash = 0; + if (sigwinch_received) + { + sigwinch_received = 0; + get_new_window_size (0, (int *)0, (int *)0); + } + + if (eol_ungetc_lookahead) + { + c = eol_ungetc_lookahead; + eol_ungetc_lookahead = 0; + return (c); + } + +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + /* If shell_input_line[shell_input_line_index] == 0, but there is + something on the pushed list of strings, then we don't want to go + off and get another line. We let the code down below handle it. */ + + if (!shell_input_line || ((!shell_input_line[shell_input_line_index]) && + (pushed_string_list == (STRING_SAVER *)NULL))) +#else /* !ALIAS && !DPAREN_ARITHMETIC */ + if (!shell_input_line || !shell_input_line[shell_input_line_index]) +#endif /* !ALIAS && !DPAREN_ARITHMETIC */ + { + line_number++; + + /* Let's not let one really really long line blow up memory allocation */ + if (shell_input_line && shell_input_line_size >= 32768) + { + free (shell_input_line); + shell_input_line = 0; + shell_input_line_size = 0; + } + + restart_read: + + /* Allow immediate exit if interrupted during input. */ + QUIT; + + i = truncating = 0; + shell_input_line_terminator = 0; + + /* If the shell is interactive, but not currently printing a prompt + (interactive_shell && interactive == 0), we don't want to print + notifies or cleanup the jobs -- we want to defer it until we do + print the next prompt. */ + if (interactive_shell == 0 || SHOULD_PROMPT()) + { +#if defined (JOB_CONTROL) + /* This can cause a problem when reading a command as the result + of a trap, when the trap is called from flush_child. This call + had better not cause jobs to disappear from the job table in + that case, or we will have big trouble. */ + notify_and_cleanup (); +#else /* !JOB_CONTROL */ + cleanup_dead_jobs (); +#endif /* !JOB_CONTROL */ + } + +#if defined (READLINE) + if (no_line_editing && SHOULD_PROMPT()) +#else + if (SHOULD_PROMPT()) +#endif + print_prompt (); + + if (bash_input.type == st_stream) + clearerr (stdin); + + while (1) + { + c = yy_getc (); + + /* Allow immediate exit if interrupted during input. */ + QUIT; + + if (c == '\0') + { + /* If we get EOS while parsing a string, treat it as EOF so we + don't just keep looping. Happens very rarely */ + if (bash_input.type == st_string) + { + if (i == 0) + shell_input_line_terminator = EOF; + shell_input_line[i] = '\0'; + c = EOF; + break; + } + continue; + } + + /* Theoretical overflow */ + /* If we can't put 256 bytes more into the buffer, allocate + everything we can and fill it as full as we can. */ + /* XXX - we ignore rest of line using `truncating' flag */ + if (shell_input_line_size > (SIZE_MAX - 256)) + { + size_t n; + + n = SIZE_MAX - i; /* how much more can we put into the buffer? */ + if (n <= 2) /* we have to save 1 for the newline added below */ + { + if (truncating == 0) + internal_warning(_("shell_getc: shell_input_line_size (%zu) exceeds SIZE_MAX (%lu): line truncated"), shell_input_line_size, (unsigned long)SIZE_MAX); + shell_input_line[i] = '\0'; + truncating = 1; + } + if (shell_input_line_size < SIZE_MAX) + { + shell_input_line_size = SIZE_MAX; + shell_input_line = xrealloc (shell_input_line, shell_input_line_size); + } + } + else + RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256); + + if (c == EOF) + { + if (bash_input.type == st_stream) + clearerr (stdin); + + if (i == 0) + shell_input_line_terminator = EOF; + + shell_input_line[i] = '\0'; + break; + } + + if (truncating == 0 || c == '\n') + shell_input_line[i++] = c; + + if (c == '\n') + { + shell_input_line[--i] = '\0'; + current_command_line_count++; + break; + } + + last_was_backslash = last_was_backslash == 0 && c == '\\'; + } + + shell_input_line_index = 0; + shell_input_line_len = i; /* == strlen (shell_input_line) */ + + set_line_mbstate (); + +#if defined (HISTORY) + if (remember_on_history && shell_input_line && shell_input_line[0]) + { + char *expansions; +# if defined (BANG_HISTORY) + /* If the current delimiter is a single quote, we should not be + performing history expansion, even if we're on a different + line from the original single quote. */ + if (current_delimiter (dstack) == '\'') + history_quoting_state = '\''; + else if (current_delimiter (dstack) == '"') + history_quoting_state = '"'; + else + history_quoting_state = 0; +# endif + /* Calling with a third argument of 1 allows remember_on_history to + determine whether or not the line is saved to the history list */ + expansions = pre_process_line (shell_input_line, 1, 1); +# if defined (BANG_HISTORY) + history_quoting_state = 0; +# endif + if (expansions != shell_input_line) + { + free (shell_input_line); + shell_input_line = expansions; + shell_input_line_len = shell_input_line ? + strlen (shell_input_line) : 0; + if (shell_input_line_len == 0) + current_command_line_count--; + + /* We have to force the xrealloc below because we don't know + the true allocated size of shell_input_line anymore. */ + shell_input_line_size = shell_input_line_len; + + set_line_mbstate (); + } + } + /* Try to do something intelligent with blank lines encountered while + entering multi-line commands. XXX - this is grotesque */ + else if (remember_on_history && shell_input_line && + shell_input_line[0] == '\0' && + current_command_line_count > 1) + { + if (current_delimiter (dstack)) + /* We know shell_input_line[0] == 0 and we're reading some sort of + quoted string. This means we've got a line consisting of only + a newline in a quoted string. We want to make sure this line + gets added to the history. */ + maybe_add_history (shell_input_line); + else + { + char *hdcs; + hdcs = history_delimiting_chars (shell_input_line); + if (hdcs && hdcs[0] == ';') + maybe_add_history (shell_input_line); + } + } + +#endif /* HISTORY */ + + if (shell_input_line) + { + /* Lines that signify the end of the shell's input should not be + echoed. We should not echo lines while parsing command + substitutions with recursive calls into the parsing engine; those + should only be echoed once when we read the word. That is the + reason for the test against shell_eof_token, which is set to a + right paren when parsing the contents of command substitutions. */ + if (echo_input_at_read && (shell_input_line[0] || + shell_input_line_terminator != EOF) && + shell_eof_token == 0) + fprintf (stderr, "%s\n", shell_input_line); + } + else + { + shell_input_line_size = 0; + prompt_string_pointer = ¤t_prompt_string; + if (SHOULD_PROMPT ()) + prompt_again (0); + goto restart_read; + } + + /* Add the newline to the end of this string, iff the string does + not already end in an EOF character. */ + if (shell_input_line_terminator != EOF) + { + if (shell_input_line_size < SIZE_MAX-3 && (shell_input_line_len+3 > shell_input_line_size)) + shell_input_line = (char *)xrealloc (shell_input_line, + 1 + (shell_input_line_size += 2)); + + /* Don't add a newline to a string that ends with a backslash if we're + going to be removing quoted newlines, since that will eat the + backslash. Add another backslash instead (will be removed by + word expansion). */ + if (bash_input.type == st_string && expanding_alias() == 0 && last_was_backslash && c == EOF && remove_quoted_newline) + shell_input_line[shell_input_line_len] = '\\'; + else + shell_input_line[shell_input_line_len] = '\n'; + shell_input_line[shell_input_line_len + 1] = '\0'; + +#if defined (HANDLE_MULTIBYTE) + /* This is kind of an abstraction violation, but there's no need to + go through the entire shell_input_line again with a call to + set_line_mbstate(). */ + EXTEND_SHELL_INPUT_LINE_PROPERTY(); + shell_input_line_property[shell_input_line_len] = 1; +#endif + } + } + +next_alias_char: + if (shell_input_line_index == 0) + unquoted_backslash = 0; + + uc = shell_input_line[shell_input_line_index]; + + if (uc) + { + unquoted_backslash = unquoted_backslash == 0 && uc == '\\'; + shell_input_line_index++; + } + +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + /* If UC is NULL, we have reached the end of the current input string. If + pushed_string_list is non-empty, it's time to pop to the previous string + because we have fully consumed the result of the last alias expansion. + Do it transparently; just return the next character of the string popped + to. */ + /* If pushed_string_list != 0 but pushed_string_list->expander == 0 (not + currently tested) and the flags value is not PSH_SOURCE, we are not + parsing an alias, we have just saved one (push_string, when called by + the parse_dparen code) In this case, just go on as well. The PSH_SOURCE + case is handled below. */ + + /* If we're at the end of an alias expansion add a space to make sure that + the alias remains marked as being in use while we expand its last word. + This makes sure that pop_string doesn't mark the alias as not in use + before the string resulting from the alias expansion is tokenized and + checked for alias expansion, preventing recursion. At this point, the + last character in shell_input_line is the last character of the alias + expansion. We test that last character to determine whether or not to + return the space that will delimit the token and postpone the pop_string. + This set of conditions duplicates what used to be in mk_alexpansion () + below, with the addition that we don't add a space if we're currently + reading a quoted string or in a shell comment. */ +#ifndef OLD_ALIAS_HACK + if (uc == 0 && pushed_string_list && pushed_string_list->flags != PSH_SOURCE && + pushed_string_list->flags != PSH_DPAREN && + (parser_state & PST_COMMENT) == 0 && + (parser_state & PST_ENDALIAS) == 0 && /* only once */ + shell_input_line_index > 0 && + shellblank (shell_input_line[shell_input_line_index-1]) == 0 && + shell_input_line[shell_input_line_index-1] != '\n' && + unquoted_backslash == 0 && + shellmeta (shell_input_line[shell_input_line_index-1]) == 0 && + (current_delimiter (dstack) != '\'' && current_delimiter (dstack) != '"')) + { + parser_state |= PST_ENDALIAS; + /* We need to do this to make sure last_shell_getc_is_singlebyte returns + true, since we are returning a single-byte space. */ + if (shell_input_line_index == shell_input_line_len && last_shell_getc_is_singlebyte == 0) + { +#if 0 + EXTEND_SHELL_INPUT_LINE_PROPERTY(); + shell_input_line_property[shell_input_line_len++] = 1; + /* extend shell_input_line to accommodate the shell_ungetc that + read_token_word() will perform, since we're extending the index */ + RESIZE_MALLOCED_BUFFER (shell_input_line, shell_input_line_index, 2, shell_input_line_size, 16); + shell_input_line[++shell_input_line_index] = '\0'; /* XXX */ +#else + shell_input_line_property[shell_input_line_index - 1] = 1; +#endif + } + return ' '; /* END_ALIAS */ + } +#endif + +pop_alias: +#endif /* ALIAS || DPAREN_ARITHMETIC */ + /* This case works for PSH_DPAREN as well as the shell_ungets() case that uses + push_string */ + if (uc == 0 && pushed_string_list && pushed_string_list->flags != PSH_SOURCE) + { + parser_state &= ~PST_ENDALIAS; + pop_string (); + uc = shell_input_line[shell_input_line_index]; + if (uc) + shell_input_line_index++; + } + + if MBTEST(uc == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n') + { + if (SHOULD_PROMPT ()) + prompt_again (0); + line_number++; + + /* What do we do here if we're expanding an alias whose definition + includes an escaped newline? If that's the last character in the + alias expansion, we just pop the pushed string list (recall that + we inhibit the appending of a space if newline is the last + character). If it's not the last character, we need to consume the + quoted newline and move to the next character in the expansion. */ +#if defined (ALIAS) + if (expanding_alias () && shell_input_line[shell_input_line_index+1] == '\0') + { + uc = 0; + goto pop_alias; + } + else if (expanding_alias () && shell_input_line[shell_input_line_index+1] != '\0') + { + shell_input_line_index++; /* skip newline */ + goto next_alias_char; /* and get next character */ + } + else +#endif + goto restart_read; + } + + if (uc == 0 && shell_input_line_terminator == EOF) + return ((shell_input_line_index != 0) ? '\n' : EOF); + +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + /* We already know that we are not parsing an alias expansion because of the + check for expanding_alias() above. This knows how parse_and_execute + handles switching to st_string input while an alias is being expanded, + hence the check for pushed_string_list without pushed_string_list->expander + and the check for PSH_SOURCE as pushed_string_list->flags. + parse_and_execute and parse_string both change the input type to st_string + and place the string to be parsed and executed into location.string, so + we should not stop reading that until the pointer is '\0'. + The check for shell_input_line_terminator may be superfluous. + + This solves the problem of `.' inside a multi-line alias with embedded + newlines executing things out of order. */ + if (uc == 0 && bash_input.type == st_string && *bash_input.location.string && + pushed_string_list && pushed_string_list->flags == PSH_SOURCE && + shell_input_line_terminator == 0) + { + shell_input_line_index = 0; + goto restart_read; + } +#endif + + return (uc); +} + +/* Put C back into the input for the shell. This might need changes for + HANDLE_MULTIBYTE around EOLs. Since we (currently) never push back a + character different than we read, shell_input_line_property doesn't need + to change when manipulating shell_input_line. The define for + last_shell_getc_is_singlebyte should take care of it, though. */ +static void +shell_ungetc (c) + int c; +{ + if (shell_input_line && shell_input_line_index) + shell_input_line[--shell_input_line_index] = c; + else + eol_ungetc_lookahead = c; +} + +/* Push S back into shell_input_line; updating shell_input_line_index */ +void +shell_ungets (s) + char *s; +{ + size_t slen, chars_left; + + slen = strlen (s); + + if (shell_input_line[shell_input_line_index] == '\0') + { + /* Easy, just overwrite shell_input_line. This is preferred because it + saves on set_line_mbstate () and other overhead like push_string */ + if (shell_input_line_size <= slen) + RESIZE_MALLOCED_BUFFER (shell_input_line, shell_input_line_index, slen + 1, shell_input_line_size, 64); + strcpy (shell_input_line, s); + shell_input_line_index = 0; + shell_input_line_len = slen; + shell_input_line_terminator = 0; + } + else if (shell_input_line_index >= slen) + { + /* Just as easy, just back up shell_input_line_index, but it means we + will re-process some characters in set_line_mbstate(). Need to + watch pushing back newlines here. */ + while (slen > 0) + shell_input_line[--shell_input_line_index] = s[--slen]; + } + else if (s[slen - 1] == '\n') + { + push_string (savestring (s), 0, (alias_t *)NULL); + /* push_string does set_line_mbstate () */ + return; + } + else + { + /* Harder case: pushing back input string that's longer than what we've + consumed from shell_input_line so far. */ + INTERNAL_DEBUG (("shell_ungets: not at end of shell_input_line")); + + chars_left = shell_input_line_len - shell_input_line_index; + if (shell_input_line_size <= (slen + chars_left)) + RESIZE_MALLOCED_BUFFER (shell_input_line, shell_input_line_index, chars_left + slen + 1, shell_input_line_size, 64); + memmove (shell_input_line + slen, shell_input_line + shell_input_line_index, shell_input_line_len - shell_input_line_index); + strcpy (shell_input_line, s); + shell_input_line_index = 0; + shell_input_line_len = strlen (shell_input_line); /* chars_left + slen? */ + } + +#if defined (HANDLE_MULTIBYTE) + set_line_mbstate (); /* XXX */ +#endif +} + +char * +parser_remaining_input () +{ + if (shell_input_line == 0) + return 0; + if ((int)shell_input_line_index < 0 || shell_input_line_index >= shell_input_line_len) + return ""; /* XXX */ + return (shell_input_line + shell_input_line_index); +} + +#ifdef INCLUDE_UNUSED +/* Back the input pointer up by one, effectively `ungetting' a character. */ +static void +shell_ungetchar () +{ + if (shell_input_line && shell_input_line_index) + shell_input_line_index--; +} +#endif + +/* Discard input until CHARACTER is seen, then push that character back + onto the input stream. */ +static void +discard_until (character) + int character; +{ + int c; + + while ((c = shell_getc (0)) != EOF && c != character) + ; + + if (c != EOF) + shell_ungetc (c); +} + +void +execute_variable_command (command, vname) + char *command, *vname; +{ + char *last_lastarg; + sh_parser_state_t ps; + + save_parser_state (&ps); + last_lastarg = get_string_value ("_"); + if (last_lastarg) + last_lastarg = savestring (last_lastarg); + + parse_and_execute (savestring (command), vname, SEVAL_NONINT|SEVAL_NOHIST); + + restore_parser_state (&ps); + bind_variable ("_", last_lastarg, 0); + FREE (last_lastarg); + + if (token_to_read == '\n') /* reset_parser was called */ + token_to_read = 0; +} + +void +push_token (x) + int x; +{ + two_tokens_ago = token_before_that; + token_before_that = last_read_token; + last_read_token = current_token; + + current_token = x; +} + +/* Place to remember the token. We try to keep the buffer + at a reasonable size, but it can grow. */ +static char *token = (char *)NULL; + +/* Current size of the token buffer. */ +static size_t token_buffer_size; + +/* Command to read_token () explaining what we want it to do. */ +#define READ 0 +#define RESET 1 +#define prompt_is_ps1 \ + (!prompt_string_pointer || prompt_string_pointer == &ps1_prompt) + +/* Function for yyparse to call. yylex keeps track of + the last two tokens read, and calls read_token. */ +static int +yylex () +{ + if (interactive && (current_token == 0 || current_token == '\n')) + { + /* Before we print a prompt, we might have to check mailboxes. + We do this only if it is time to do so. Notice that only here + is the mail alarm reset; nothing takes place in check_mail () + except the checking of mail. Please don't change this. */ + if (prompt_is_ps1 && parse_and_execute_level == 0 && time_to_check_mail ()) + { + check_mail (); + reset_mail_timer (); + } + + /* Avoid printing a prompt if we're not going to read anything, e.g. + after resetting the parser with read_token (RESET). */ + if (token_to_read == 0 && SHOULD_PROMPT ()) + prompt_again (0); + } + + two_tokens_ago = token_before_that; + token_before_that = last_read_token; + last_read_token = current_token; + current_token = read_token (READ); + + if ((parser_state & PST_EOFTOKEN) && current_token == shell_eof_token) + { + /* placeholder for any special handling. */ + return (current_token); + } + + if (current_token < 0) +#if defined (YYERRCODE) && !defined (YYUNDEF) + current_token = YYERRCODE; +#else + current_token = YYerror; +#endif + + return (current_token); +} + +/* When non-zero, we have read the required tokens + which allow ESAC to be the next one read. */ +static int esacs_needed_count; + +/* When non-zero, we can read IN as an acceptable token, regardless of how + many newlines we read. */ +static int expecting_in_token; + +static void +push_heredoc (r) + REDIRECT *r; +{ + if (need_here_doc >= HEREDOC_MAX) + { + last_command_exit_value = EX_BADUSAGE; + need_here_doc = 0; + report_syntax_error (_("maximum here-document count exceeded")); + reset_parser (); + exit_shell (last_command_exit_value); + } + redir_stack[need_here_doc++] = r; +} + +void +gather_here_documents () +{ + int r; + + r = 0; + here_doc_first_line = 1; + while (need_here_doc > 0) + { + parser_state |= PST_HEREDOC; + make_here_document (redir_stack[r++], line_number); + parser_state &= ~PST_HEREDOC; + need_here_doc--; + redir_stack[r - 1] = 0; /* XXX */ + } + here_doc_first_line = 0; /* just in case */ +} + +/* When non-zero, an open-brace used to create a group is awaiting a close + brace partner. */ +static int open_brace_count; + +/* In the following three macros, `token' is always last_read_token */ + +/* Are we in the middle of parsing a redirection where we are about to read + a word? This is used to make sure alias expansion doesn't happen in the + middle of a redirection, even though we're parsing a simple command. */ +#define parsing_redirection(token) \ + (token == '<' || token == '>' || \ + token == GREATER_GREATER || token == GREATER_BAR || \ + token == LESS_GREATER || token == LESS_LESS_MINUS || \ + token == LESS_LESS || token == LESS_LESS_LESS || \ + token == LESS_AND || token == GREATER_AND || token == AND_GREATER) + +/* Is `token' one that will allow a WORD to be read in a command position? + We can read a simple command name on which we should attempt alias expansion + or we can read an assignment statement. */ +#define command_token_position(token) \ + (((token) == ASSIGNMENT_WORD) || \ + ((parser_state&PST_REDIRLIST) && parsing_redirection(token) == 0) || \ + ((token) != SEMI_SEMI && (token) != SEMI_AND && (token) != SEMI_SEMI_AND && reserved_word_acceptable(token))) + +/* Are we in a position where we can read an assignment statement? */ +#define assignment_acceptable(token) \ + (command_token_position(token) && ((parser_state & PST_CASEPAT) == 0)) + +/* Check to see if TOKEN is a reserved word and return the token + value if it is. */ +#define CHECK_FOR_RESERVED_WORD(tok) \ + do { \ + if (!dollar_present && !quoted && \ + reserved_word_acceptable (last_read_token)) \ + { \ + int i; \ + for (i = 0; word_token_alist[i].word != (char *)NULL; i++) \ + if (STREQ (tok, word_token_alist[i].word)) \ + { \ + if ((parser_state & PST_CASEPAT) && (word_token_alist[i].token != ESAC)) \ + break; \ + if (word_token_alist[i].token == TIME && time_command_acceptable () == 0) \ + break; \ + if ((parser_state & PST_CASEPAT) && last_read_token == '|' && word_token_alist[i].token == ESAC) \ + break; /* Posix grammar rule 4 */ \ + if ((parser_state & PST_CASEPAT) && last_read_token == '(' && word_token_alist[i].token == ESAC) /*)*/ \ + break; /* phantom Posix grammar rule 4 */ \ + if (word_token_alist[i].token == ESAC) { \ + parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \ + esacs_needed_count--; \ + } else if (word_token_alist[i].token == CASE) \ + parser_state |= PST_CASESTMT; \ + else if (word_token_alist[i].token == COND_END) \ + parser_state &= ~(PST_CONDCMD|PST_CONDEXPR); \ + else if (word_token_alist[i].token == COND_START) \ + parser_state |= PST_CONDCMD; \ + else if (word_token_alist[i].token == '{') \ + open_brace_count++; \ + else if (word_token_alist[i].token == '}' && open_brace_count) \ + open_brace_count--; \ + return (word_token_alist[i].token); \ + } \ + } \ + } while (0) + +#if defined (ALIAS) + + /* OK, we have a token. Let's try to alias expand it, if (and only if) + it's eligible. + + It is eligible for expansion if EXPAND_ALIASES is set, and + the token is unquoted and the last token read was a command + separator (or expand_next_token is set), and we are currently + processing an alias (pushed_string_list is non-empty) and this + token is not the same as the current or any previously + processed alias. + + Special cases that disqualify: + In a pattern list in a case statement (parser_state & PST_CASEPAT). */ + +static char * +mk_alexpansion (s) + char *s; +{ + int l; + char *r; + + l = strlen (s); + r = xmalloc (l + 2); + strcpy (r, s); +#ifdef OLD_ALIAS_HACK + /* If the last character in the alias is a newline, don't add a trailing + space to the expansion. Works with shell_getc above. */ + /* Need to do something about the case where the alias expansion contains + an unmatched quoted string, since appending this space affects the + subsequent output. */ + if (l > 0 && r[l - 1] != ' ' && r[l - 1] != '\n' && shellmeta(r[l - 1]) == 0) + r[l++] = ' '; +#endif + r[l] = '\0'; + return r; +} + +static int +alias_expand_token (tokstr) + char *tokstr; +{ + char *expanded; + alias_t *ap; + +#if 0 + if (((parser_state & PST_ALEXPNEXT) || command_token_position (last_read_token)) && + (parser_state & PST_CASEPAT) == 0) +#else + if ((parser_state & PST_ALEXPNEXT) || assignment_acceptable (last_read_token)) +#endif + { + ap = find_alias (tokstr); + + /* Currently expanding this token. */ + if (ap && (ap->flags & AL_BEINGEXPANDED)) + return (NO_EXPANSION); + +#ifdef OLD_ALIAS_HACK + /* mk_alexpansion puts an extra space on the end of the alias expansion, + so the lookahead by the parser works right (the alias needs to remain + `in use' while parsing its last word to avoid alias recursion for + something like "alias echo=echo"). If this gets changed, make sure + the code in shell_getc that deals with reaching the end of an + expanded alias is changed with it. */ +#endif + expanded = ap ? mk_alexpansion (ap->value) : (char *)NULL; + + if (expanded) + { + push_string (expanded, ap->flags & AL_EXPANDNEXT, ap); + return (RE_READ_TOKEN); + } + else + /* This is an eligible token that does not have an expansion. */ + return (NO_EXPANSION); + } + return (NO_EXPANSION); +} +#endif /* ALIAS */ + +static int +time_command_acceptable () +{ +#if defined (COMMAND_TIMING) + int i; + + if (posixly_correct && shell_compatibility_level > 41) + { + /* Quick check of the rest of the line to find the next token. If it + begins with a `-', Posix says to not return `time' as the token. + This was interp 267. */ + i = shell_input_line_index; + while (i < shell_input_line_len && (shell_input_line[i] == ' ' || shell_input_line[i] == '\t')) + i++; + if (shell_input_line[i] == '-') + return 0; + } + + switch (last_read_token) + { + case 0: + case ';': + case '\n': + if (token_before_that == '|') + return (0); + /* FALLTHROUGH */ + case AND_AND: + case OR_OR: + case '&': + case WHILE: + case DO: + case UNTIL: + case IF: + case THEN: + case ELIF: + case ELSE: + case '{': /* } */ + case '(': /* )( */ + case ')': /* only valid in case statement */ + case BANG: /* ! time pipeline */ + case TIME: /* time time pipeline */ + case TIMEOPT: /* time -p time pipeline */ + case TIMEIGN: /* time -p -- ... */ + return 1; + default: + return 0; + } +#else + return 0; +#endif /* COMMAND_TIMING */ +} + +/* Handle special cases of token recognition: + IN is recognized if the last token was WORD and the token + before that was FOR or CASE or SELECT. + + DO is recognized if the last token was WORD and the token + before that was FOR or SELECT. + + ESAC is recognized if the last token caused `esacs_needed_count' + to be set + + `{' is recognized if the last token as WORD and the token + before that was FUNCTION, or if we just parsed an arithmetic + `for' command. + + `}' is recognized if there is an unclosed `{' present. + + `-p' is returned as TIMEOPT if the last read token was TIME. + `--' is returned as TIMEIGN if the last read token was TIME or TIMEOPT. + + ']]' is returned as COND_END if the parser is currently parsing + a conditional expression ((parser_state & PST_CONDEXPR) != 0) + + `time' is returned as TIME if and only if it is immediately + preceded by one of `;', `\n', `||', `&&', or `&'. +*/ + +static int +special_case_tokens (tokstr) + char *tokstr; +{ + /* Posix grammar rule 6 */ + if ((last_read_token == WORD) && +#if defined (SELECT_COMMAND) + ((token_before_that == FOR) || (token_before_that == CASE) || (token_before_that == SELECT)) && +#else + ((token_before_that == FOR) || (token_before_that == CASE)) && +#endif + (tokstr[0] == 'i' && tokstr[1] == 'n' && tokstr[2] == 0)) + { + if (token_before_that == CASE) + { + parser_state |= PST_CASEPAT; + esacs_needed_count++; + } + if (expecting_in_token) + expecting_in_token--; + return (IN); + } + + /* XXX - leaving above code intact for now, but it should eventually be + removed in favor of this clause. */ + /* Posix grammar rule 6 */ + if (expecting_in_token && (last_read_token == WORD || last_read_token == '\n') && + (tokstr[0] == 'i' && tokstr[1] == 'n' && tokstr[2] == 0)) + { + if (parser_state & PST_CASESTMT) + { + parser_state |= PST_CASEPAT; + esacs_needed_count++; + } + expecting_in_token--; + return (IN); + } + /* Posix grammar rule 6, third word in FOR: for i; do command-list; done */ + else if (expecting_in_token && (last_read_token == '\n' || last_read_token == ';') && + (tokstr[0] == 'd' && tokstr[1] == 'o' && tokstr[2] == '\0')) + { + expecting_in_token--; + return (DO); + } + + /* for i do; command-list; done */ + if (last_read_token == WORD && +#if defined (SELECT_COMMAND) + (token_before_that == FOR || token_before_that == SELECT) && +#else + (token_before_that == FOR) && +#endif + (tokstr[0] == 'd' && tokstr[1] == 'o' && tokstr[2] == '\0')) + { + if (expecting_in_token) + expecting_in_token--; + return (DO); + } + + /* Ditto for ESAC in the CASE case. + Specifically, this handles "case word in esac", which is a legal + construct, certainly because someone will pass an empty arg to the + case construct, and we don't want it to barf. Of course, we should + insist that the case construct has at least one pattern in it, but + the designers disagree. */ + if (esacs_needed_count) + { + if (last_read_token == IN && STREQ (tokstr, "esac")) + { + esacs_needed_count--; + parser_state &= ~PST_CASEPAT; + return (ESAC); + } + } + + /* The start of a shell function definition. */ + if (parser_state & PST_ALLOWOPNBRC) + { + parser_state &= ~PST_ALLOWOPNBRC; + if (tokstr[0] == '{' && tokstr[1] == '\0') /* } */ + { + open_brace_count++; + function_bstart = line_number; + return ('{'); /* } */ + } + } + + /* We allow a `do' after a for ((...)) without an intervening + list_terminator */ + if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == 'd' && tokstr[1] == 'o' && !tokstr[2]) + return (DO); + if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == '{' && tokstr[1] == '\0') /* } */ + { + open_brace_count++; + return ('{'); /* } */ + } + + if (open_brace_count && reserved_word_acceptable (last_read_token) && tokstr[0] == '}' && !tokstr[1]) + { + open_brace_count--; /* { */ + return ('}'); + } + +#if defined (COMMAND_TIMING) + /* Handle -p after `time'. */ + if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2]) + return (TIMEOPT); + /* Handle -- after `time'. */ + if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2]) + return (TIMEIGN); + /* Handle -- after `time -p'. */ + if (last_read_token == TIMEOPT && tokstr[0] == '-' && tokstr[1] == '-' && !tokstr[2]) + return (TIMEIGN); +#endif + +#if defined (COND_COMMAND) /* [[ */ + if ((parser_state & PST_CONDEXPR) && tokstr[0] == ']' && tokstr[1] == ']' && tokstr[2] == '\0') + return (COND_END); +#endif + + return (-1); +} + +/* Called from shell.c when Control-C is typed at top level. Or + by the error rule at top level. */ +void +reset_parser () +{ + dstack.delimiter_depth = 0; /* No delimiters found so far. */ + open_brace_count = 0; + +#if defined (EXTENDED_GLOB) + /* Reset to global value of extended glob */ + if (parser_state & (PST_EXTPAT|PST_CMDSUBST)) + extended_glob = global_extglob; +#endif + + parser_state = 0; + here_doc_first_line = 0; + +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + if (pushed_string_list) + free_string_list (); +#endif /* ALIAS || DPAREN_ARITHMETIC */ + + /* This is where we resynchronize to the next newline on error/reset */ + if (shell_input_line) + { + free (shell_input_line); + shell_input_line = (char *)NULL; + shell_input_line_size = shell_input_line_index = 0; + } + + FREE (word_desc_to_read); + word_desc_to_read = (WORD_DESC *)NULL; + + eol_ungetc_lookahead = 0; + + /* added post-bash-5.1 */ + need_here_doc = 0; + redir_stack[0] = 0; + esacs_needed_count = expecting_in_token = 0; + + current_token = '\n'; /* XXX */ + last_read_token = '\n'; + token_to_read = '\n'; +} + +void +reset_readahead_token () +{ + if (token_to_read == '\n') + token_to_read = 0; +} + +/* Read the next token. Command can be READ (normal operation) or + RESET (to normalize state). */ +static int +read_token (command) + int command; +{ + int character; /* Current character. */ + int peek_char; /* Temporary look-ahead character. */ + int result; /* The thing to return. */ + + if (command == RESET) + { + reset_parser (); + return ('\n'); + } + + if (token_to_read) + { + result = token_to_read; + if (token_to_read == WORD || token_to_read == ASSIGNMENT_WORD) + { + yylval.word = word_desc_to_read; + word_desc_to_read = (WORD_DESC *)NULL; + } + token_to_read = 0; + return (result); + } + +#if defined (COND_COMMAND) + if ((parser_state & (PST_CONDCMD|PST_CONDEXPR)) == PST_CONDCMD) + { + cond_lineno = line_number; + parser_state |= PST_CONDEXPR; + yylval.command = parse_cond_command (); + if (cond_token != COND_END) + { + cond_error (); + return (-1); + } + token_to_read = COND_END; + parser_state &= ~(PST_CONDEXPR|PST_CONDCMD); + return (COND_CMD); + } +#endif + +#if defined (ALIAS) + /* This is a place to jump back to once we have successfully expanded a + token with an alias and pushed the string with push_string () */ + re_read_token: +#endif /* ALIAS */ + + /* Read a single word from input. Start by skipping blanks. */ + while ((character = shell_getc (1)) != EOF && shellblank (character)) + ; + + if (character == EOF) + { + EOF_Reached = 1; + return (yacc_EOF); + } + + /* If we hit the end of the string and we're not expanding an alias (e.g., + we are eval'ing a string that is an incomplete command), return EOF */ + if (character == '\0' && bash_input.type == st_string && expanding_alias() == 0) + { + INTERNAL_DEBUG (("shell_getc: bash_input.location.string = `%s'", bash_input.location.string)); + EOF_Reached = 1; + return (yacc_EOF); + } + + if MBTEST(character == '#' && (!interactive || interactive_comments)) + { + /* A comment. Discard until EOL or EOF, and then return a newline. */ + parser_state |= PST_COMMENT; + discard_until ('\n'); + shell_getc (0); + parser_state &= ~PST_COMMENT; + character = '\n'; /* this will take the next if statement and return. */ + } + + if MBTEST(character == '\n') + { + /* If we're about to return an unquoted newline, we can go and collect + the text of any pending here document. */ + if (need_here_doc) + gather_here_documents (); + +#if defined (ALIAS) + parser_state &= ~PST_ALEXPNEXT; +#endif /* ALIAS */ + + parser_state &= ~PST_ASSIGNOK; + + return (character); + } + + if (parser_state & PST_REGEXP) + goto tokword; + + /* Shell meta-characters. */ + if MBTEST(shellmeta (character)) + { +#if defined (ALIAS) + /* Turn off alias tokenization iff this character sequence would + not leave us ready to read a command. */ + if (character == '<' || character == '>') + parser_state &= ~PST_ALEXPNEXT; +#endif /* ALIAS */ + + parser_state &= ~PST_ASSIGNOK; + + /* If we are parsing a command substitution and we have read a character + that marks the end of it, don't bother to skip over quoted newlines + when we read the next token. We're just interested in a character + that will turn this into a two-character token, so we let the higher + layers deal with quoted newlines following the command substitution. */ + if ((parser_state & PST_CMDSUBST) && character == shell_eof_token) + peek_char = shell_getc (0); + else + peek_char = shell_getc (1); + + if MBTEST(character == peek_char) + { + switch (character) + { + case '<': + /* If '<' then we could be at "<<" or at "<<-". We have to + look ahead one more character. */ + peek_char = shell_getc (1); + if MBTEST(peek_char == '-') + return (LESS_LESS_MINUS); + else if MBTEST(peek_char == '<') + return (LESS_LESS_LESS); + else + { + shell_ungetc (peek_char); + return (LESS_LESS); + } + + case '>': + return (GREATER_GREATER); + + case ';': + parser_state |= PST_CASEPAT; +#if defined (ALIAS) + parser_state &= ~PST_ALEXPNEXT; +#endif /* ALIAS */ + + peek_char = shell_getc (1); + if MBTEST(peek_char == '&') + return (SEMI_SEMI_AND); + else + { + shell_ungetc (peek_char); + return (SEMI_SEMI); + } + + case '&': + return (AND_AND); + + case '|': + return (OR_OR); + +#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) + case '(': /* ) */ + result = parse_dparen (character); + if (result == -2) + break; + else + return result; +#endif + } + } + else if MBTEST(character == '<' && peek_char == '&') + return (LESS_AND); + else if MBTEST(character == '>' && peek_char == '&') + return (GREATER_AND); + else if MBTEST(character == '<' && peek_char == '>') + return (LESS_GREATER); + else if MBTEST(character == '>' && peek_char == '|') + return (GREATER_BAR); + else if MBTEST(character == '&' && peek_char == '>') + { + peek_char = shell_getc (1); + if MBTEST(peek_char == '>') + return (AND_GREATER_GREATER); + else + { + shell_ungetc (peek_char); + return (AND_GREATER); + } + } + else if MBTEST(character == '|' && peek_char == '&') + return (BAR_AND); + else if MBTEST(character == ';' && peek_char == '&') + { + parser_state |= PST_CASEPAT; +#if defined (ALIAS) + parser_state &= ~PST_ALEXPNEXT; +#endif /* ALIAS */ + return (SEMI_AND); + } + + shell_ungetc (peek_char); + + /* If we look like we are reading the start of a function + definition, then let the reader know about it so that + we will do the right thing with `{'. */ + if MBTEST(character == ')' && last_read_token == '(' && token_before_that == WORD) + { + parser_state |= PST_ALLOWOPNBRC; +#if defined (ALIAS) + parser_state &= ~PST_ALEXPNEXT; +#endif /* ALIAS */ + function_dstart = line_number; + } + + /* case pattern lists may be preceded by an optional left paren. If + we're not trying to parse a case pattern list, the left paren + indicates a subshell. */ + if MBTEST(character == '(' && (parser_state & PST_CASEPAT) == 0) /* ) */ + parser_state |= PST_SUBSHELL; + /*(*/ + else if MBTEST((parser_state & PST_CASEPAT) && character == ')') + parser_state &= ~PST_CASEPAT; + /*(*/ + else if MBTEST((parser_state & PST_SUBSHELL) && character == ')') + parser_state &= ~PST_SUBSHELL; + +#if defined (PROCESS_SUBSTITUTION) + /* Check for the constructs which introduce process substitution. + Shells running in `posix mode' don't do process substitution. */ + if MBTEST((character != '>' && character != '<') || peek_char != '(') /*)*/ +#endif /* PROCESS_SUBSTITUTION */ + return (character); + } + + /* Hack <&- (close stdin) case. Also <&N- (dup and close). */ + if MBTEST(character == '-' && (last_read_token == LESS_AND || last_read_token == GREATER_AND)) + return (character); + +tokword: + /* Okay, if we got this far, we have to read a word. Read one, + and then check it against the known ones. */ + result = read_token_word (character); +#if defined (ALIAS) + if (result == RE_READ_TOKEN) + goto re_read_token; +#endif + return result; +} + +/* + * Match a $(...) or other grouping construct. This has to handle embedded + * quoted strings ('', ``, "") and nested constructs. It also must handle + * reprompting the user, if necessary, after reading a newline, and returning + * correct error values if it reads EOF. + */ +#define P_FIRSTCLOSE 0x0001 +#define P_ALLOWESC 0x0002 +#define P_DQUOTE 0x0004 +#define P_COMMAND 0x0008 /* parsing a command, so look for comments */ +#define P_BACKQUOTE 0x0010 /* parsing a backquoted command substitution */ +#define P_ARRAYSUB 0x0020 /* parsing a [...] array subscript for assignment */ +#define P_DOLBRACE 0x0040 /* parsing a ${...} construct */ + +/* Lexical state while parsing a grouping construct or $(...). */ +#define LEX_WASDOL 0x0001 +#define LEX_CKCOMMENT 0x0002 +#define LEX_INCOMMENT 0x0004 +#define LEX_PASSNEXT 0x0008 +#define LEX_RESWDOK 0x0010 +#define LEX_CKCASE 0x0020 +#define LEX_INCASE 0x0040 +#define LEX_INHEREDOC 0x0080 +#define LEX_HEREDELIM 0x0100 /* reading here-doc delimiter */ +#define LEX_STRIPDOC 0x0200 /* <<- strip tabs from here doc delim */ +#define LEX_QUOTEDDOC 0x0400 /* here doc with quoted delim */ +#define LEX_INWORD 0x0800 +#define LEX_GTLT 0x1000 +#define LEX_CKESAC 0x2000 /* check esac after in -- for later */ +#define LEX_CASEWD 0x4000 /* word after case */ +#define LEX_PATLIST 0x8000 /* case statement pattern list */ + +#define COMSUB_META(ch) ((ch) == ';' || (ch) == '&' || (ch) == '|') + +#define CHECK_NESTRET_ERROR() \ + do { \ + if (nestret == &matched_pair_error) \ + { \ + free (ret); \ + return &matched_pair_error; \ + } \ + } while (0) + +#define APPEND_NESTRET() \ + do { \ + if (nestlen) \ + { \ + RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); \ + strcpy (ret + retind, nestret); \ + retind += nestlen; \ + } \ + } while (0) + +static char matched_pair_error; + +static char * +parse_matched_pair (qc, open, close, lenp, flags) + int qc; /* `"' if this construct is within double quotes */ + int open, close; + int *lenp, flags; +{ + int count, ch, prevch, tflags; + int nestlen, ttranslen, start_lineno; + char *ret, *nestret, *ttrans; + int retind, retsize, rflags; + int dolbrace_state; + + dolbrace_state = (flags & P_DOLBRACE) ? DOLBRACE_PARAM : 0; + +/*itrace("parse_matched_pair[%d]: open = %c close = %c flags = %d", line_number, open, close, flags);*/ + count = 1; + tflags = 0; + + if ((flags & P_COMMAND) && qc != '`' && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0) + tflags |= LEX_CKCOMMENT; + + /* RFLAGS is the set of flags we want to pass to recursive calls. */ + rflags = (qc == '"') ? P_DQUOTE : (flags & P_DQUOTE); + + ret = (char *)xmalloc (retsize = 64); + retind = 0; + + start_lineno = line_number; + ch = EOF; /* just in case */ + while (count) + { + prevch = ch; + ch = shell_getc (qc != '\'' && (tflags & (LEX_PASSNEXT)) == 0); + + if (ch == EOF) + { + free (ret); + parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close); + EOF_Reached = 1; /* XXX */ + return (&matched_pair_error); + } + + /* Possible reprompting. */ + if MBTEST(ch == '\n' && SHOULD_PROMPT ()) + prompt_again (0); + + /* Don't bother counting parens or doing anything else if in a comment + or part of a case statement */ + if (tflags & LEX_INCOMMENT) + { + /* Add this character. */ + RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64); + ret[retind++] = ch; + + if MBTEST(ch == '\n') + tflags &= ~LEX_INCOMMENT; + + continue; + } + + /* Not exactly right yet, should handle shell metacharacters, too. If + any changes are made to this test, make analogous changes to subst.c: + extract_delimited_string(). */ + else if MBTEST((tflags & LEX_CKCOMMENT) && (tflags & LEX_INCOMMENT) == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || shellblank (ret[retind - 1]))) + tflags |= LEX_INCOMMENT; + + if (tflags & LEX_PASSNEXT) /* last char was backslash */ + { + tflags &= ~LEX_PASSNEXT; + /* XXX - PST_NOEXPAND? */ + if MBTEST(qc != '\'' && ch == '\n') /* double-quoted \ disappears. */ + { + if (retind > 0) + retind--; /* swallow previously-added backslash */ + continue; + } + + RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64); + if MBTEST(ch == CTLESC) + ret[retind++] = CTLESC; + ret[retind++] = ch; + continue; + } + /* If we're reparsing the input (e.g., from parse_string_to_word_list), + we've already prepended CTLESC to single-quoted results of $'...'. + We may want to do this for other CTLESC-quoted characters in + reparse, too. */ + else if MBTEST((parser_state & PST_REPARSE) && open == '\'' && (ch == CTLESC || ch == CTLNUL)) + { + RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64); + ret[retind++] = ch; + continue; + } + else if MBTEST(ch == CTLESC || ch == CTLNUL) /* special shell escapes */ + { + RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64); + ret[retind++] = CTLESC; + ret[retind++] = ch; + continue; + } + else if MBTEST(ch == close) /* ending delimiter */ + count--; + /* handle nested ${...} specially. */ + else if MBTEST(open != close && (tflags & LEX_WASDOL) && open == '{' && ch == open) /* } */ + count++; + else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && ch == open) /* nested begin */ + count++; + + /* Add this character. */ + RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64); + ret[retind++] = ch; + + /* If we just read the ending character, don't bother continuing. */ + if (count == 0) + break; + + if (open == '\'') /* '' inside grouping construct */ + { + if MBTEST((flags & P_ALLOWESC) && ch == '\\') + tflags |= LEX_PASSNEXT; + continue; + } + + if MBTEST(ch == '\\') /* backslashes */ + tflags |= LEX_PASSNEXT; + + /* Based on which dolstate is currently in (param, op, or word), + decide what the op is. We're really only concerned if it's % or + #, so we can turn on a flag that says whether or not we should + treat single quotes as special when inside a double-quoted + ${...}. This logic must agree with subst.c:extract_dollar_brace_string + since they share the same defines. */ + /* FLAG POSIX INTERP 221 */ + if (flags & P_DOLBRACE) + { + /* ${param%[%]word} */ + if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '%' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE; + /* ${param#[#]word} */ + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '#' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE; + /* ${param/[/]pat/rep} */ + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '/' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE2; /* XXX */ + /* ${param^[^]pat} */ + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == '^' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE; + /* ${param,[,]pat} */ + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && ch == ',' && retind > 1) + dolbrace_state = DOLBRACE_QUOTE; + else if MBTEST(dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", ch) != 0) + dolbrace_state = DOLBRACE_OP; + else if MBTEST(dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", ch) == 0) + dolbrace_state = DOLBRACE_WORD; + } + + /* The big hammer. Single quotes aren't special in double quotes. The + problem is that Posix used to say the single quotes are semi-special: + within a double-quoted ${...} construct "an even number of + unescaped double-quotes or single-quotes, if any, shall occur." */ + /* This was changed in Austin Group Interp 221 */ + if MBTEST(posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && dolbrace_state != DOLBRACE_QUOTE2 && (flags & P_DQUOTE) && (flags & P_DOLBRACE) && ch == '\'') + continue; + + /* Could also check open == '`' if we want to parse grouping constructs + inside old-style command substitution. */ + if (open != close) /* a grouping construct */ + { + if MBTEST(shellquote (ch)) + { + /* '', ``, or "" inside $(...) or other grouping construct. */ + push_delimiter (dstack, ch); + if MBTEST((tflags & LEX_WASDOL) && ch == '\'') /* $'...' inside group */ + nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC|rflags); + else + nestret = parse_matched_pair (ch, ch, ch, &nestlen, rflags); + pop_delimiter (dstack); + CHECK_NESTRET_ERROR (); + + if MBTEST((tflags & LEX_WASDOL) && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0 || dolbrace_state == DOLBRACE_QUOTE || dolbrace_state == DOLBRACE_QUOTE2)) + { + /* Translate $'...' here. */ + /* PST_NOEXPAND */ + ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen); + free (nestret); + + /* If we're parsing a double-quoted brace expansion and we are + not in a place where single quotes are treated specially, + make sure we single-quote the results of the ansi + expansion because quote removal should remove them later */ + /* FLAG POSIX INTERP 221 */ + if ((shell_compatibility_level > 42) && (rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_QUOTE2 || dolbrace_state == DOLBRACE_QUOTE) && (flags & P_DOLBRACE)) + { + nestret = sh_single_quote (ttrans); + free (ttrans); + nestlen = strlen (nestret); + } +#if 0 /* TAG:bash-5.3 */ + /* This single-quotes PARAM in ${PARAM OP WORD} when PARAM + contains a $'...' even when extended_quote is set. */ + else if ((rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_PARAM) && (flags & P_DOLBRACE)) + { + nestret = sh_single_quote (ttrans); + free (ttrans); + nestlen = strlen (nestret); + } +#endif + else if ((rflags & P_DQUOTE) == 0) + { + nestret = sh_single_quote (ttrans); + free (ttrans); + nestlen = strlen (nestret); + } + else + { + /* Should we quote CTLESC here? */ + nestret = ttrans; + nestlen = ttranslen; + } + retind -= 2; /* back up before the $' */ + } +#if defined (TRANSLATABLE_STRINGS) + else if MBTEST((tflags & LEX_WASDOL) && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0)) + { + /* Locale expand $"..." here. */ + /* PST_NOEXPAND */ + ttrans = locale_expand (nestret, 0, nestlen - 1, start_lineno, &ttranslen); + free (nestret); + + /* If we're supposed to single-quote translated strings, + check whether the translated result is different from + the original and single-quote the string if it is. */ + if (singlequote_translations && + ((nestlen - 1) != ttranslen || STREQN (nestret, ttrans, ttranslen) == 0)) + { + if ((rflags & P_DQUOTE) == 0) + nestret = sh_single_quote (ttrans); + else if ((rflags & P_DQUOTE) && (dolbrace_state == DOLBRACE_QUOTE2) && (flags & P_DOLBRACE)) + nestret = sh_single_quote (ttrans); + else + /* single quotes aren't special, use backslash instead */ + nestret = sh_backslash_quote_for_double_quotes (ttrans, 0); + } + else + nestret = sh_mkdoublequoted (ttrans, ttranslen, 0); + free (ttrans); + nestlen = strlen (nestret); + retind -= 2; /* back up before the $" */ + } +#endif /* TRANSLATABLE_STRINGS */ + + APPEND_NESTRET (); + FREE (nestret); + } + else if ((flags & (P_ARRAYSUB|P_DOLBRACE)) && (tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '[')) /* ) } ] */ + goto parse_dollar_word; +#if defined (PROCESS_SUBSTITUTION) + /* XXX - technically this should only be recognized at the start of + a word */ + else if ((flags & (P_ARRAYSUB|P_DOLBRACE)) && (tflags & LEX_GTLT) && (ch == '(')) /* ) */ + goto parse_dollar_word; +#endif + } + /* Parse an old-style command substitution within double quotes as a + single word. */ + /* XXX - sh and ksh93 don't do this - XXX */ + else if MBTEST(open == '"' && ch == '`') + { + nestret = parse_matched_pair (0, '`', '`', &nestlen, rflags); + + CHECK_NESTRET_ERROR (); + APPEND_NESTRET (); + + FREE (nestret); + } + else if MBTEST(open != '`' && (tflags & LEX_WASDOL) && (ch == '(' || ch == '{' || ch == '[')) /* ) } ] */ + /* check for $(), $[], or ${} inside quoted string. */ + { +parse_dollar_word: + if (open == ch) /* undo previous increment */ + count--; + if (ch == '(') /* ) */ + nestret = parse_comsub (0, '(', ')', &nestlen, (rflags|P_COMMAND) & ~P_DQUOTE); + else if (ch == '{') /* } */ + nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|P_DOLBRACE|rflags); + else if (ch == '[') /* ] */ + nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags); + + CHECK_NESTRET_ERROR (); + APPEND_NESTRET (); + + FREE (nestret); + } +#if defined (PROCESS_SUBSTITUTION) + if MBTEST((ch == '<' || ch == '>') && (tflags & LEX_GTLT) == 0) + tflags |= LEX_GTLT; + else + tflags &= ~LEX_GTLT; +#endif + if MBTEST(ch == '$' && (tflags & LEX_WASDOL) == 0) + tflags |= LEX_WASDOL; + else + tflags &= ~LEX_WASDOL; + } + + ret[retind] = '\0'; + if (lenp) + *lenp = retind; +/*itrace("parse_matched_pair[%d]: returning %s", line_number, ret);*/ + return ret; +} + +#if defined (DEBUG) +static void +dump_tflags (flags) + int flags; +{ + int f; + + f = flags; + fprintf (stderr, "%d -> ", f); + if (f & LEX_WASDOL) + { + f &= ~LEX_WASDOL; + fprintf (stderr, "LEX_WASDOL%s", f ? "|" : ""); + } + if (f & LEX_CKCOMMENT) + { + f &= ~LEX_CKCOMMENT; + fprintf (stderr, "LEX_CKCOMMENT%s", f ? "|" : ""); + } + if (f & LEX_INCOMMENT) + { + f &= ~LEX_INCOMMENT; + fprintf (stderr, "LEX_INCOMMENT%s", f ? "|" : ""); + } + if (f & LEX_PASSNEXT) + { + f &= ~LEX_PASSNEXT; + fprintf (stderr, "LEX_PASSNEXT%s", f ? "|" : ""); + } + if (f & LEX_RESWDOK) + { + f &= ~LEX_RESWDOK; + fprintf (stderr, "LEX_RESWDOK%s", f ? "|" : ""); + } + if (f & LEX_CKCASE) + { + f &= ~LEX_CKCASE; + fprintf (stderr, "LEX_CKCASE%s", f ? "|" : ""); + } + if (f & LEX_CKESAC) + { + f &= ~LEX_CKESAC; + fprintf (stderr, "LEX_CKESAC%s", f ? "|" : ""); + } + if (f & LEX_INCASE) + { + f &= ~LEX_INCASE; + fprintf (stderr, "LEX_INCASE%s", f ? "|" : ""); + } + if (f & LEX_CASEWD) + { + f &= ~LEX_CASEWD; + fprintf (stderr, "LEX_CASEWD%s", f ? "|" : ""); + } + if (f & LEX_PATLIST) + { + f &= ~LEX_PATLIST; + fprintf (stderr, "LEX_PATLIST%s", f ? "|" : ""); + } + if (f & LEX_INHEREDOC) + { + f &= ~LEX_INHEREDOC; + fprintf (stderr, "LEX_INHEREDOC%s", f ? "|" : ""); + } + if (f & LEX_HEREDELIM) + { + f &= ~LEX_HEREDELIM; + fprintf (stderr, "LEX_HEREDELIM%s", f ? "|" : ""); + } + if (f & LEX_STRIPDOC) + { + f &= ~LEX_STRIPDOC; + fprintf (stderr, "LEX_WASDOL%s", f ? "|" : ""); + } + if (f & LEX_QUOTEDDOC) + { + f &= ~LEX_QUOTEDDOC; + fprintf (stderr, "LEX_QUOTEDDOC%s", f ? "|" : ""); + } + if (f & LEX_INWORD) + { + f &= ~LEX_INWORD; + fprintf (stderr, "LEX_INWORD%s", f ? "|" : ""); + } + + fprintf (stderr, "\n"); + fflush (stderr); +} +#endif + +/* Parse a $(...) command substitution. This reads input from the current + input stream. */ +static char * +parse_comsub (qc, open, close, lenp, flags) + int qc; /* `"' if this construct is within double quotes */ + int open, close; + int *lenp, flags; +{ + int peekc, r; + int start_lineno, local_extglob, was_extpat; + char *ret, *tcmd; + int retlen; + sh_parser_state_t ps; + STRING_SAVER *saved_strings; + COMMAND *saved_global, *parsed_command; + + /* Posix interp 217 says arithmetic expressions have precedence, so + assume $(( introduces arithmetic expansion and parse accordingly. */ + if (open == '(') /*)*/ + { + peekc = shell_getc (1); + shell_ungetc (peekc); + if (peekc == '(') /*)*/ + return (parse_matched_pair (qc, open, close, lenp, 0)); + } + +/*itrace("parse_comsub: qc = `%c' open = %c close = %c", qc, open, close);*/ + + /*debug_parser(1);*/ + start_lineno = line_number; + + save_parser_state (&ps); + + was_extpat = (parser_state & PST_EXTPAT); + + /* State flags we don't want to persist into command substitutions. */ + parser_state &= ~(PST_REGEXP|PST_EXTPAT|PST_CONDCMD|PST_CONDEXPR|PST_COMPASSIGN); + /* Could do PST_CASESTMT too, but that also affects history. Setting + expecting_in_token below should take care of the parsing requirements. + Unsetting PST_REDIRLIST isn't strictly necessary because of how we set + token_to_read below, but we do it anyway. */ + parser_state &= ~(PST_CASEPAT|PST_ALEXPNEXT|PST_SUBSHELL|PST_REDIRLIST); + /* State flags we want to set for this run through the parser. */ + parser_state |= PST_CMDSUBST|PST_EOFTOKEN|PST_NOEXPAND; + + /* leave pushed_string_list alone, since we might need to consume characters + from it to satisfy this command substitution (in some perverse case). */ + shell_eof_token = close; + + saved_global = global_command; /* might not be necessary */ + global_command = (COMMAND *)NULL; + + /* These are reset by reset_parser() */ + need_here_doc = 0; + esacs_needed_count = expecting_in_token = 0; + + /* We want to expand aliases on this pass if we're in posix mode, since the + standard says you have to take aliases into account when looking for the + terminating right paren. Otherwise, we defer until execution time for + backwards compatibility. */ + if (expand_aliases) + expand_aliases = posixly_correct != 0; +#if defined (EXTENDED_GLOB) + /* If (parser_state & PST_EXTPAT), we're parsing an extended pattern for a + conditional command and have already set global_extglob appropriately. */ + if (shell_compatibility_level <= 51 && was_extpat == 0) + { + local_extglob = global_extglob = extended_glob; + extended_glob = 1; + } +#endif + + current_token = '\n'; /* XXX */ + token_to_read = DOLPAREN; /* let's trick the parser */ + + r = yyparse (); + + if (need_here_doc > 0) + { + internal_warning ("command substitution: %d unterminated here-document%s", need_here_doc, (need_here_doc == 1) ? "" : "s"); + gather_here_documents (); /* XXX check compatibility level? */ + } + +#if defined (EXTENDED_GLOB) + if (shell_compatibility_level <= 51 && was_extpat == 0) + extended_glob = local_extglob; +#endif + + parsed_command = global_command; + + if (EOF_Reached) + { + shell_eof_token = ps.eof_token; + expand_aliases = ps.expand_aliases; + + /* yyparse() has already called yyerror() and reset_parser() */ + return (&matched_pair_error); + } + else if (r != 0) + { + /* parser_error (start_lineno, _("could not parse command substitution")); */ + /* Non-interactive shells exit on parse error in a command substitution. */ + if (last_command_exit_value == 0) + last_command_exit_value = EXECUTION_FAILURE; + set_exit_status (last_command_exit_value); + if (interactive_shell == 0) + jump_to_top_level (FORCE_EOF); /* This is like reader_loop() */ + else + { + shell_eof_token = ps.eof_token; + expand_aliases = ps.expand_aliases; + + jump_to_top_level (DISCARD); /* XXX - return (&matched_pair_error)? */ + } + } + + if (current_token != shell_eof_token) + { +INTERNAL_DEBUG(("current_token (%d) != shell_eof_token (%c)", current_token, shell_eof_token)); + token_to_read = current_token; + + /* If we get here we can check eof_encountered and if it's 1 but the + previous EOF_Reached test didn't succeed, we can assume that the shell + is interactive and ignoreeof is set. We might want to restore the + parser state in this case. */ + shell_eof_token = ps.eof_token; + expand_aliases = ps.expand_aliases; + + return (&matched_pair_error); + } + + /* We don't want to restore the old pushed string list, since we might have + used it to consume additional input from an alias while parsing this + command substitution. */ + saved_strings = pushed_string_list; + restore_parser_state (&ps); + pushed_string_list = saved_strings; + + tcmd = print_comsub (parsed_command); /* returns static memory */ + retlen = strlen (tcmd); + if (tcmd[0] == '(') /* ) need a space to prevent arithmetic expansion */ + retlen++; + ret = xmalloc (retlen + 2); + if (tcmd[0] == '(') /* ) */ + { + ret[0] = ' '; + strcpy (ret + 1, tcmd); + } + else + strcpy (ret, tcmd); + ret[retlen++] = ')'; + ret[retlen] = '\0'; + + dispose_command (parsed_command); + global_command = saved_global; + + if (lenp) + *lenp = retlen; + +/*itrace("parse_comsub:%d: returning `%s'", line_number, ret);*/ + return ret; +} + +/* Recursively call the parser to parse a $(...) command substitution. This is + called by the word expansion code and so does not have to reset as much + parser state before calling yyparse(). */ +char * +xparse_dolparen (base, string, indp, flags) + char *base; + char *string; + int *indp; + int flags; +{ + sh_parser_state_t ps; + sh_input_line_state_t ls; + int orig_ind, nc, sflags, start_lineno; + char *ret, *ep, *ostring; + +/*debug_parser(1);*/ + orig_ind = *indp; + ostring = string; + start_lineno = line_number; + + if (*string == 0) + { + if (flags & SX_NOALLOC) + return (char *)NULL; + + ret = xmalloc (1); + ret[0] = '\0'; + return ret; + } + +/*itrace("xparse_dolparen: size = %d shell_input_line = `%s' string=`%s'", shell_input_line_size, shell_input_line, string);*/ + + sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE; + if (flags & SX_NOLONGJMP) + sflags |= SEVAL_NOLONGJMP; + + save_parser_state (&ps); + save_input_line_state (&ls); + +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + pushed_string_list = (STRING_SAVER *)NULL; +#endif + /*(*/ + parser_state |= PST_CMDSUBST|PST_EOFTOKEN; /* allow instant ')' */ /*(*/ + shell_eof_token = ')'; + if (flags & SX_COMPLETE) + parser_state |= PST_NOERROR; + + /* Don't expand aliases on this pass at all. Either parse_comsub() does it + at parse time, in which case this string already has aliases expanded, + or command_substitute() does it in the child process executing the + command substitution and we want to defer it completely until then. The + old value will be restored by restore_parser_state(). */ + expand_aliases = 0; +#if defined (EXTENDED_GLOB) + global_extglob = extended_glob; /* for reset_parser() */ +#endif + + token_to_read = DOLPAREN; /* let's trick the parser */ + + nc = parse_string (string, "command substitution", sflags, (COMMAND **)NULL, &ep); + + /* Should we save and restore the bison/yacc lookahead token (yychar) here? + Or only if it's not YYEMPTY? */ + if (current_token == shell_eof_token) + yyclearin; /* might want to clear lookahead token unconditionally */ + + reset_parser (); /* resets extended_glob too */ + /* reset_parser() clears shell_input_line and associated variables, including + parser_state, so we want to reset things, then restore what we need. */ + restore_input_line_state (&ls); + restore_parser_state (&ps); + + token_to_read = 0; + + /* If parse_string returns < 0, we need to jump to top level with the + negative of the return value. We abandon the rest of this input line + first */ + if (nc < 0) + { + clear_shell_input_line (); /* XXX */ + if (bash_input.type != st_string) /* paranoia */ + parser_state &= ~(PST_CMDSUBST|PST_EOFTOKEN); + if ((flags & SX_NOLONGJMP) == 0) + jump_to_top_level (-nc); /* XXX */ + } + + /* Need to find how many characters parse_string() consumed, update + *indp, if flags != 0, copy the portion of the string parsed into RET + and return it. If flags & 1 (SX_NOALLOC) we can return NULL. */ + + /*(*/ + if (ep[-1] != ')') + { +#if 0 + if (ep[-1] != '\n') + itrace("xparse_dolparen:%d: ep[-1] != RPAREN (%d), ep = `%s'", line_number, ep[-1], ep); +#endif + + while (ep > ostring && ep[-1] == '\n') ep--; + } + + nc = ep - ostring; + *indp = ep - base - 1; + + /*((*/ +#if 0 + if (base[*indp] != ')') + itrace("xparse_dolparen:%d: base[%d] != RPAREN (%d), base = `%s'", line_number, *indp, base[*indp], base); + if (*indp < orig_ind) + itrace("xparse_dolparen:%d: *indp (%d) < orig_ind (%d), orig_string = `%s'", line_number, *indp, orig_ind, ostring); +#endif + + if (base[*indp] != ')' && (flags & SX_NOLONGJMP) == 0) + { + /*(*/ + if ((flags & SX_NOERROR) == 0) + parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), ')'); + jump_to_top_level (DISCARD); + } + + if (flags & SX_NOALLOC) + return (char *)NULL; + + if (nc == 0) + { + ret = xmalloc (1); + ret[0] = '\0'; + } + else + ret = substring (ostring, 0, nc - 1); + + return ret; +} + +/* Recursively call the parser to parse the string from a $(...) command + substitution to a COMMAND *. This is called from command_substitute() and + has the same parser state constraints as xparse_dolparen(). */ +COMMAND * +parse_string_to_command (string, flags) + char *string; + int flags; +{ + sh_parser_state_t ps; + sh_input_line_state_t ls; + int nc, sflags; + size_t slen; + char *ret, *ep; + COMMAND *cmd; + + if (*string == 0) + return (COMMAND *)NULL; + + ep = string; + slen = STRLEN (string); + +/*itrace("parse_string_to_command: size = %d shell_input_line = `%s' string=`%s'", shell_input_line_size, shell_input_line, string);*/ + + sflags = SEVAL_NONINT|SEVAL_NOHIST|SEVAL_NOFREE; + if (flags & SX_NOLONGJMP) + sflags |= SEVAL_NOLONGJMP; + + save_parser_state (&ps); + save_input_line_state (&ls); + +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + pushed_string_list = (STRING_SAVER *)NULL; +#endif + if (flags & SX_COMPLETE) + parser_state |= PST_NOERROR; + + expand_aliases = 0; + + cmd = 0; + nc = parse_string (string, "command substitution", sflags, &cmd, &ep); + + reset_parser (); + /* reset_parser() clears shell_input_line and associated variables, including + parser_state, so we want to reset things, then restore what we need. */ + restore_input_line_state (&ls); + restore_parser_state (&ps); + + /* If parse_string returns < 0, we need to jump to top level with the + negative of the return value. We abandon the rest of this input line + first */ + if (nc < 0) + { + clear_shell_input_line (); /* XXX */ + if ((flags & SX_NOLONGJMP) == 0) + jump_to_top_level (-nc); /* XXX */ + } + + /* Need to check how many characters parse_string() consumed, make sure it's + the entire string. */ + if (nc < slen) + { + dispose_command (cmd); + return (COMMAND *)NULL; + } + + return cmd; +} + +#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) +/* Parse a double-paren construct. It can be either an arithmetic + command, an arithmetic `for' command, or a nested subshell. Returns + the parsed token, -1 on error, or -2 if we didn't do anything and + should just go on. */ +static int +parse_dparen (c) + int c; +{ + int cmdtyp, sline; + char *wval; + WORD_DESC *wd; + +#if defined (ARITH_FOR_COMMAND) + if (last_read_token == FOR) + { + if (word_top < MAX_CASE_NEST) + word_top++; + arith_for_lineno = word_lineno[word_top] = line_number; + cmdtyp = parse_arith_cmd (&wval, 0); + if (cmdtyp == 1) + { + wd = alloc_word_desc (); + wd->word = wval; + yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL); + return (ARITH_FOR_EXPRS); + } + else + return -1; /* ERROR */ + } +#endif + +#if defined (DPAREN_ARITHMETIC) + if (reserved_word_acceptable (last_read_token)) + { + sline = line_number; + + cmdtyp = parse_arith_cmd (&wval, 0); + if (cmdtyp == 1) /* arithmetic command */ + { + wd = alloc_word_desc (); + wd->word = wval; + wd->flags = W_QUOTED|W_NOSPLIT|W_NOGLOB|W_NOTILDE|W_NOPROCSUB; + yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL); + return (ARITH_CMD); + } + else if (cmdtyp == 0) /* nested subshell */ + { + push_string (wval, 0, (alias_t *)NULL); + pushed_string_list->flags = PSH_DPAREN; + if ((parser_state & PST_CASEPAT) == 0) + parser_state |= PST_SUBSHELL; + return (c); + } + else /* ERROR */ + return -1; + } +#endif + + return -2; /* XXX */ +} + +/* We've seen a `(('. Look for the matching `))'. If we get it, return 1. + If not, assume it's a nested subshell for backwards compatibility and + return 0. In any case, put the characters we've consumed into a locally- + allocated buffer and make *ep point to that buffer. Return -1 on an + error, for example EOF. */ +static int +parse_arith_cmd (ep, adddq) + char **ep; + int adddq; +{ + int exp_lineno, rval, c; + char *ttok, *tokstr; + int ttoklen; + + exp_lineno = line_number; + ttok = parse_matched_pair (0, '(', ')', &ttoklen, 0); + rval = 1; + if (ttok == &matched_pair_error) + return -1; + /* Check that the next character is the closing right paren. If + not, this is a syntax error. ( */ + c = shell_getc (0); + if MBTEST(c != ')') + rval = 0; + + tokstr = (char *)xmalloc (ttoklen + 4); + + /* if ADDDQ != 0 then (( ... )) -> "..." */ + if (rval == 1 && adddq) /* arith cmd, add double quotes */ + { + tokstr[0] = '"'; + strncpy (tokstr + 1, ttok, ttoklen - 1); + tokstr[ttoklen] = '"'; + tokstr[ttoklen+1] = '\0'; + } + else if (rval == 1) /* arith cmd, don't add double quotes */ + { + strncpy (tokstr, ttok, ttoklen - 1); + tokstr[ttoklen-1] = '\0'; + } + else /* nested subshell */ + { + tokstr[0] = '('; + strncpy (tokstr + 1, ttok, ttoklen - 1); + tokstr[ttoklen] = ')'; + tokstr[ttoklen+1] = c; + tokstr[ttoklen+2] = '\0'; + } + + *ep = tokstr; + FREE (ttok); + return rval; +} +#endif /* DPAREN_ARITHMETIC || ARITH_FOR_COMMAND */ + +#if defined (COND_COMMAND) +static void +cond_error () +{ + char *etext; + + if (EOF_Reached && cond_token != COND_ERROR) /* [[ */ + parser_error (cond_lineno, _("unexpected EOF while looking for `]]'")); + else if (cond_token != COND_ERROR) + { + if (etext = error_token_from_token (cond_token)) + { + parser_error (cond_lineno, _("syntax error in conditional expression: unexpected token `%s'"), etext); + free (etext); + } + else + parser_error (cond_lineno, _("syntax error in conditional expression")); + } +} + +static COND_COM * +cond_expr () +{ + return (cond_or ()); +} + +static COND_COM * +cond_or () +{ + COND_COM *l, *r; + + l = cond_and (); + if (cond_token == OR_OR) + { + r = cond_or (); + l = make_cond_node (COND_OR, (WORD_DESC *)NULL, l, r); + } + return l; +} + +static COND_COM * +cond_and () +{ + COND_COM *l, *r; + + l = cond_term (); + if (cond_token == AND_AND) + { + r = cond_and (); + l = make_cond_node (COND_AND, (WORD_DESC *)NULL, l, r); + } + return l; +} + +static int +cond_skip_newlines () +{ + while ((cond_token = read_token (READ)) == '\n') + { + if (SHOULD_PROMPT ()) + prompt_again (0); + } + return (cond_token); +} + +#define COND_RETURN_ERROR() \ + do { cond_token = COND_ERROR; return ((COND_COM *)NULL); } while (0) + +static COND_COM * +cond_term () +{ + WORD_DESC *op; + COND_COM *term, *tleft, *tright; + int tok, lineno, local_extglob; + char *etext; + + /* Read a token. It can be a left paren, a `!', a unary operator, or a + word that should be the first argument of a binary operator. Start by + skipping newlines, since this is a compound command. */ + tok = cond_skip_newlines (); + lineno = line_number; + if (tok == COND_END) + { + COND_RETURN_ERROR (); + } + else if (tok == '(') + { + term = cond_expr (); + if (cond_token != ')') + { + if (term) + dispose_cond_node (term); /* ( */ + if (etext = error_token_from_token (cond_token)) + { + parser_error (lineno, _("unexpected token `%s', expected `)'"), etext); + free (etext); + } + else + parser_error (lineno, _("expected `)'")); + COND_RETURN_ERROR (); + } + term = make_cond_node (COND_EXPR, (WORD_DESC *)NULL, term, (COND_COM *)NULL); + (void)cond_skip_newlines (); + } + else if (tok == BANG || (tok == WORD && (yylval.word->word[0] == '!' && yylval.word->word[1] == '\0'))) + { + if (tok == WORD) + dispose_word (yylval.word); /* not needed */ + term = cond_term (); + if (term) + term->flags ^= CMD_INVERT_RETURN; + } + else if (tok == WORD && yylval.word->word[0] == '-' && yylval.word->word[1] && yylval.word->word[2] == 0 && test_unop (yylval.word->word)) + { + op = yylval.word; + tok = read_token (READ); + if (tok == WORD) + { + tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); + term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL); + } + else + { + dispose_word (op); + if (etext = error_token_from_token (tok)) + { + parser_error (line_number, _("unexpected argument `%s' to conditional unary operator"), etext); + free (etext); + } + else + parser_error (line_number, _("unexpected argument to conditional unary operator")); + COND_RETURN_ERROR (); + } + + (void)cond_skip_newlines (); + } + else if (tok == WORD) /* left argument to binary operator */ + { + /* lhs */ + tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); + + /* binop */ + /* tok = cond_skip_newlines (); ? */ + tok = read_token (READ); + if (tok == WORD && test_binop (yylval.word->word)) + { + op = yylval.word; + if (op->word[0] == '=' && (op->word[1] == '\0' || (op->word[1] == '=' && op->word[2] == '\0'))) + parser_state |= PST_EXTPAT; + else if (op->word[0] == '!' && op->word[1] == '=' && op->word[2] == '\0') + parser_state |= PST_EXTPAT; + } +#if defined (COND_REGEXP) + else if (tok == WORD && STREQ (yylval.word->word, "=~")) + { + op = yylval.word; + parser_state |= PST_REGEXP; + } +#endif + else if (tok == '<' || tok == '>') + op = make_word_from_token (tok); /* ( */ + /* There should be a check before blindly accepting the `)' that we have + seen the opening `('. */ + else if (tok == COND_END || tok == AND_AND || tok == OR_OR || tok == ')') + { + /* Special case. [[ x ]] is equivalent to [[ -n x ]], just like + the test command. Similarly for [[ x && expr ]] or + [[ x || expr ]] or [[ (x) ]]. */ + op = make_word ("-n"); + term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL); + cond_token = tok; + return (term); + } + else + { + if (etext = error_token_from_token (tok)) + { + parser_error (line_number, _("unexpected token `%s', conditional binary operator expected"), etext); + free (etext); + } + else + parser_error (line_number, _("conditional binary operator expected")); + dispose_cond_node (tleft); + COND_RETURN_ERROR (); + } + + /* rhs */ + local_extglob = extended_glob; + if (parser_state & PST_EXTPAT) + extended_glob = 1; + tok = read_token (READ); + if (parser_state & PST_EXTPAT) + extended_glob = local_extglob; + parser_state &= ~(PST_REGEXP|PST_EXTPAT); + + if (tok == WORD) + { + tright = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); + term = make_cond_node (COND_BINARY, op, tleft, tright); + } + else + { + if (etext = error_token_from_token (tok)) + { + parser_error (line_number, _("unexpected argument `%s' to conditional binary operator"), etext); + free (etext); + } + else + parser_error (line_number, _("unexpected argument to conditional binary operator")); + dispose_cond_node (tleft); + dispose_word (op); + COND_RETURN_ERROR (); + } + + (void)cond_skip_newlines (); + } + else + { + if (tok < 256) + parser_error (line_number, _("unexpected token `%c' in conditional command"), tok); + else if (etext = error_token_from_token (tok)) + { + parser_error (line_number, _("unexpected token `%s' in conditional command"), etext); + free (etext); + } + else + parser_error (line_number, _("unexpected token %d in conditional command"), tok); + COND_RETURN_ERROR (); + } + return (term); +} + +/* This is kind of bogus -- we slip a mini recursive-descent parser in + here to handle the conditional statement syntax. */ +static COMMAND * +parse_cond_command () +{ + COND_COM *cexp; + + global_extglob = extended_glob; + cexp = cond_expr (); + return (make_cond_command (cexp)); +} +#endif + +#if defined (ARRAY_VARS) +/* When this is called, it's guaranteed that we don't care about anything + in t beyond i. We use a buffer with room for the characters we add just + in case assignment() ends up doing something like parsing a command + substitution that will reallocate atoken. We don't want to write beyond + the end of an allocated buffer. */ +static int +token_is_assignment (t, i) + char *t; + int i; +{ + int r; + char *atoken; + + atoken = xmalloc (i + 3); + memcpy (atoken, t, i); + atoken[i] = '='; + atoken[i+1] = '\0'; + + r = assignment (atoken, (parser_state & PST_COMPASSIGN) != 0); + + free (atoken); + + /* XXX - check that r == i to avoid returning false positive for + t containing `=' before t[i]. */ + return (r > 0 && r == i); +} + +/* XXX - possible changes here for `+=' */ +static int +token_is_ident (t, i) + char *t; + int i; +{ + unsigned char c; + int r; + + c = t[i]; + t[i] = '\0'; + r = legal_identifier (t); + t[i] = c; + return r; +} +#endif + +static int +read_token_word (character) + int character; +{ + /* The value for YYLVAL when a WORD is read. */ + WORD_DESC *the_word; + + /* Index into the token that we are building. */ + int token_index; + + /* ALL_DIGITS becomes zero when we see a non-digit. */ + int all_digit_token; + + /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */ + int dollar_present; + + /* COMPOUND_ASSIGNMENT becomes non-zero if we are parsing a compound + assignment. */ + int compound_assignment; + + /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */ + int quoted; + + /* Non-zero means to ignore the value of the next character, and just + to add it no matter what. */ + int pass_next_character; + + /* The current delimiting character. */ + int cd; + int result, peek_char; + char *ttok, *ttrans; + int ttoklen, ttranslen; + intmax_t lvalue; + + if (token_buffer_size < TOKEN_DEFAULT_INITIAL_SIZE) + token = (char *)xrealloc (token, token_buffer_size = TOKEN_DEFAULT_INITIAL_SIZE); + + token_index = 0; + all_digit_token = DIGIT (character); + dollar_present = quoted = pass_next_character = compound_assignment = 0; + + for (;;) + { + if (character == EOF) + goto got_token; + + if (pass_next_character) + { + pass_next_character = 0; + goto got_escaped_character; + } + + cd = current_delimiter (dstack); + + /* Handle backslashes. Quote lots of things when not inside of + double-quotes, quote some things inside of double-quotes. */ + if MBTEST(character == '\\') + { + if (parser_state & PST_NOEXPAND) + { + pass_next_character++; + quoted = 1; + goto got_character; + } + + peek_char = shell_getc (0); + + /* Backslash-newline is ignored in all cases except + when quoted with single quotes. */ + if MBTEST(peek_char == '\n') + { + character = '\n'; + goto next_character; + } + else + { + shell_ungetc (peek_char); + + /* If the next character is to be quoted, note it now. */ + if MBTEST(cd == 0 || cd == '`' || + (cd == '"' && peek_char >= 0 && (sh_syntaxtab[peek_char] & CBSDQUOTE))) + pass_next_character++; + + quoted = 1; + goto got_character; + } + } + + /* Parse a matched pair of quote characters. */ + if MBTEST(shellquote (character)) + { + push_delimiter (dstack, character); + ttok = parse_matched_pair (character, character, character, &ttoklen, (character == '`') ? P_COMMAND : 0); + pop_delimiter (dstack); + if (ttok == &matched_pair_error) + return -1; /* Bail immediately. */ + RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, + token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); + token[token_index++] = character; + strcpy (token + token_index, ttok); + token_index += ttoklen; + all_digit_token = 0; + if (character != '`') + quoted = 1; + dollar_present |= (character == '"' && strchr (ttok, '$') != 0); + FREE (ttok); + goto next_character; + } + +#ifdef COND_REGEXP + /* When parsing a regexp as a single word inside a conditional command, + we need to special-case characters special to both the shell and + regular expressions. Right now, that is only '(' and '|'. */ /*)*/ + if MBTEST((parser_state & PST_REGEXP) && (character == '(' || character == '|')) /*)*/ + { + if (character == '|') + goto got_character; + + push_delimiter (dstack, character); + ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0); + pop_delimiter (dstack); + if (ttok == &matched_pair_error) + return -1; /* Bail immediately. */ + RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, + token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); + token[token_index++] = character; + strcpy (token + token_index, ttok); + token_index += ttoklen; + FREE (ttok); + dollar_present = all_digit_token = 0; + goto next_character; + } +#endif /* COND_REGEXP */ + +#ifdef EXTENDED_GLOB + /* Parse a ksh-style extended pattern matching specification. */ + if MBTEST(extended_glob && PATTERN_CHAR (character)) + { + peek_char = shell_getc (1); + if MBTEST(peek_char == '(') /* ) */ + { + push_delimiter (dstack, peek_char); + ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0); + pop_delimiter (dstack); + if (ttok == &matched_pair_error) + return -1; /* Bail immediately. */ + RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 3, + token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); + token[token_index++] = character; + token[token_index++] = peek_char; + strcpy (token + token_index, ttok); + token_index += ttoklen; + FREE (ttok); + dollar_present = all_digit_token = 0; + goto next_character; + } + else + shell_ungetc (peek_char); + } +#endif /* EXTENDED_GLOB */ + + /* If the delimiter character is not single quote, parse some of + the shell expansions that must be read as a single word. */ + if MBTEST(shellexp (character)) + { + peek_char = shell_getc (1); + /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */ + if MBTEST(peek_char == '(' || + ((peek_char == '{' || peek_char == '[') && character == '$')) /* ) ] } */ + { + if (peek_char == '{') /* } */ + ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE|P_DOLBRACE); + else if (peek_char == '(') /* ) */ + { + /* XXX - push and pop the `(' as a delimiter for use by + the command-oriented-history code. This way newlines + appearing in the $(...) string get added to the + history literally rather than causing a possibly- + incorrect `;' to be added. ) */ + push_delimiter (dstack, peek_char); + ttok = parse_comsub (cd, '(', ')', &ttoklen, P_COMMAND); + pop_delimiter (dstack); + } + else + ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0); + if (ttok == &matched_pair_error) + return -1; /* Bail immediately. */ + RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 3, + token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); + token[token_index++] = character; + token[token_index++] = peek_char; + strcpy (token + token_index, ttok); + token_index += ttoklen; + FREE (ttok); + dollar_present = 1; + all_digit_token = 0; + goto next_character; + } + /* This handles $'...' and $"..." new-style quoted strings. */ +#if defined (TRANSLATABLE_STRINGS) + else if MBTEST(character == '$' && (peek_char == '\'' || peek_char == '"')) +#else + else if MBTEST(character == '$' && peek_char == '\'') +#endif + { + int first_line; + + first_line = line_number; + push_delimiter (dstack, peek_char); + ttok = parse_matched_pair (peek_char, peek_char, peek_char, + &ttoklen, + (peek_char == '\'') ? P_ALLOWESC : 0); + pop_delimiter (dstack); + if (ttok == &matched_pair_error) + return -1; + if (peek_char == '\'') + { + /* PST_NOEXPAND */ + ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen); + free (ttok); + + /* Insert the single quotes and correctly quote any + embedded single quotes (allowed because P_ALLOWESC was + passed to parse_matched_pair). */ + ttok = sh_single_quote (ttrans); + free (ttrans); + ttranslen = strlen (ttok); + ttrans = ttok; + } +#if defined (TRANSLATABLE_STRINGS) + else + { + /* PST_NOEXPAND */ + /* Try to locale-expand the converted string. */ + ttrans = locale_expand (ttok, 0, ttoklen - 1, first_line, &ttranslen); + free (ttok); + + /* Add the double quotes back (or single quotes if the user + has set that option). */ + if (singlequote_translations && + ((ttoklen - 1) != ttranslen || STREQN (ttok, ttrans, ttranslen) == 0)) + ttok = sh_single_quote (ttrans); + else + ttok = sh_mkdoublequoted (ttrans, ttranslen, 0); + + free (ttrans); + ttrans = ttok; + ttranslen = strlen (ttrans); + } +#endif /* TRANSLATABLE_STRINGS */ + + RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 1, + token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); + strcpy (token + token_index, ttrans); + token_index += ttranslen; + FREE (ttrans); + quoted = 1; + all_digit_token = 0; + goto next_character; + } + /* This could eventually be extended to recognize all of the + shell's single-character parameter expansions, and set flags.*/ + else if MBTEST(character == '$' && peek_char == '$') + { + RESIZE_MALLOCED_BUFFER (token, token_index, 3, + token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); + token[token_index++] = '$'; + token[token_index++] = peek_char; + dollar_present = 1; + all_digit_token = 0; + goto next_character; + } + else + shell_ungetc (peek_char); + } + +#if defined (ARRAY_VARS) + /* Identify possible array subscript assignment; match [...]. If + parser_state&PST_COMPASSIGN, we need to parse [sub]=words treating + `sub' as if it were enclosed in double quotes. */ + else if MBTEST(character == '[' && /* ] */ + ((token_index > 0 && assignment_acceptable (last_read_token) && token_is_ident (token, token_index)) || + (token_index == 0 && (parser_state&PST_COMPASSIGN)))) + { + ttok = parse_matched_pair (cd, '[', ']', &ttoklen, P_ARRAYSUB); + if (ttok == &matched_pair_error) + return -1; /* Bail immediately. */ + RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, + token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); + token[token_index++] = character; + strcpy (token + token_index, ttok); + token_index += ttoklen; + FREE (ttok); + all_digit_token = 0; + goto next_character; + } + /* Identify possible compound array variable assignment. */ + else if MBTEST(character == '=' && token_index > 0 && (assignment_acceptable (last_read_token) || (parser_state & PST_ASSIGNOK)) && token_is_assignment (token, token_index)) + { + peek_char = shell_getc (1); + if MBTEST(peek_char == '(') /* ) */ + { + ttok = parse_compound_assignment (&ttoklen); + + RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 4, + token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); + + token[token_index++] = '='; + token[token_index++] = '('; + if (ttok) + { + strcpy (token + token_index, ttok); + token_index += ttoklen; + } + token[token_index++] = ')'; + FREE (ttok); + all_digit_token = 0; + compound_assignment = 1; +#if 1 + goto next_character; +#else + goto got_token; /* ksh93 seems to do this */ +#endif + } + else + shell_ungetc (peek_char); + } +#endif + + /* When not parsing a multi-character word construct, shell meta- + characters break words. */ + if MBTEST(shellbreak (character)) + { + shell_ungetc (character); + goto got_token; + } + +got_character: + if MBTEST(character == CTLESC || character == CTLNUL) + { + RESIZE_MALLOCED_BUFFER (token, token_index, 2, token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); + token[token_index++] = CTLESC; + } + else +got_escaped_character: + RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size, + TOKEN_DEFAULT_GROW_SIZE); + + token[token_index++] = character; + + all_digit_token &= DIGIT (character); + dollar_present |= character == '$'; + + next_character: + if (character == '\n' && SHOULD_PROMPT ()) + prompt_again (0); + + /* We want to remove quoted newlines (that is, a \ pair) + unless we are within single quotes or pass_next_character is + set (the shell equivalent of literal-next). */ + cd = current_delimiter (dstack); + character = shell_getc (cd != '\'' && pass_next_character == 0); + } /* end for (;;) */ + +got_token: + + /* Calls to RESIZE_MALLOCED_BUFFER ensure there is sufficient room. */ + token[token_index] = '\0'; + + /* Check to see what thing we should return. If the last_read_token + is a `<', or a `&', or the character which ended this token is + a '>' or '<', then, and ONLY then, is this input token a NUMBER. + Otherwise, it is just a word, and should be returned as such. */ + if MBTEST(all_digit_token && (character == '<' || character == '>' || + last_read_token == LESS_AND || + last_read_token == GREATER_AND)) + { + if (legal_number (token, &lvalue) && (int)lvalue == lvalue) + { + yylval.number = lvalue; + return (NUMBER); + } + } + + /* Check for special case tokens. */ + result = (last_shell_getc_is_singlebyte) ? special_case_tokens (token) : -1; + if (result >= 0) + return result; + +#if defined (ALIAS) + /* Posix.2 does not allow reserved words to be aliased, so check for all + of them, including special cases, before expanding the current token + as an alias. */ + if MBTEST(posixly_correct) + CHECK_FOR_RESERVED_WORD (token); + + /* Aliases are expanded iff EXPAND_ALIASES is non-zero, and quoting + inhibits alias expansion. */ + if (expand_aliases && quoted == 0) + { + result = alias_expand_token (token); + if (result == RE_READ_TOKEN) + return (RE_READ_TOKEN); + else if (result == NO_EXPANSION) + parser_state &= ~PST_ALEXPNEXT; + } + + /* If not in Posix.2 mode, check for reserved words after alias + expansion. */ + if MBTEST(posixly_correct == 0) +#endif + CHECK_FOR_RESERVED_WORD (token); + + the_word = alloc_word_desc (); + the_word->word = (char *)xmalloc (1 + token_index); + the_word->flags = 0; + strcpy (the_word->word, token); + if (dollar_present) + the_word->flags |= W_HASDOLLAR; + if (quoted) + the_word->flags |= W_QUOTED; /*(*/ + if (compound_assignment && token[token_index-1] == ')') + the_word->flags |= W_COMPASSIGN; + /* A word is an assignment if it appears at the beginning of a + simple command, or after another assignment word. This is + context-dependent, so it cannot be handled in the grammar. */ + if (assignment (token, (parser_state & PST_COMPASSIGN) != 0)) + { + the_word->flags |= W_ASSIGNMENT; + /* Don't perform word splitting on assignment statements. */ + if (assignment_acceptable (last_read_token) || (parser_state & PST_COMPASSIGN) != 0) + { + the_word->flags |= W_NOSPLIT; + if (parser_state & PST_COMPASSIGN) + the_word->flags |= W_NOGLOB; /* XXX - W_NOBRACE? */ + } + } + + if (command_token_position (last_read_token)) + { + struct builtin *b; + b = builtin_address_internal (token, 0); + if (b && (b->flags & ASSIGNMENT_BUILTIN)) + parser_state |= PST_ASSIGNOK; + else if (STREQ (token, "eval") || STREQ (token, "let")) + parser_state |= PST_ASSIGNOK; + } + + yylval.word = the_word; + + /* should we check that quoted == 0 as well? */ + if MBTEST(token[0] == '{' && token[token_index-1] == '}' && + (character == '<' || character == '>')) + { + /* can use token; already copied to the_word */ + token[token_index-1] = '\0'; +#if defined (ARRAY_VARS) + if (legal_identifier (token+1) || valid_array_reference (token+1, 0)) +#else + if (legal_identifier (token+1)) +#endif + { + strcpy (the_word->word, token+1); +/* itrace("read_token_word: returning REDIR_WORD for %s", the_word->word); */ + yylval.word = the_word; /* accommodate recursive call */ + return (REDIR_WORD); + } + else + /* valid_array_reference can call the parser recursively; need to + make sure that yylval.word doesn't change if we are going to + return WORD or ASSIGNMENT_WORD */ + yylval.word = the_word; + } + + result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT)) + ? ASSIGNMENT_WORD : WORD; + + switch (last_read_token) + { + case FUNCTION: + parser_state |= PST_ALLOWOPNBRC; + function_dstart = line_number; + break; + case CASE: + case SELECT: + case FOR: + if (word_top < MAX_CASE_NEST) + word_top++; + word_lineno[word_top] = line_number; + expecting_in_token++; + break; + } + + return (result); +} + +/* Return 1 if TOKSYM is a token that after being read would allow + a reserved word to be seen, else 0. */ +static int +reserved_word_acceptable (toksym) + int toksym; +{ + switch (toksym) + { + case '\n': + case ';': + case '(': + case ')': + case '|': + case '&': + case '{': + case '}': /* XXX */ + case AND_AND: + case ARITH_CMD: + case BANG: + case BAR_AND: + case COND_END: + case DO: + case DONE: + case ELIF: + case ELSE: + case ESAC: + case FI: + case IF: + case OR_OR: + case SEMI_SEMI: + case SEMI_AND: + case SEMI_SEMI_AND: + case THEN: + case TIME: + case TIMEOPT: + case TIMEIGN: + case COPROC: + case UNTIL: + case WHILE: + case 0: + case DOLPAREN: + return 1; + default: +#if defined (COPROCESS_SUPPORT) + if (last_read_token == WORD && token_before_that == COPROC) + return 1; +#endif + if (last_read_token == WORD && token_before_that == FUNCTION) + return 1; + return 0; + } +} + +/* Return the index of TOKEN in the alist of reserved words, or -1 if + TOKEN is not a shell reserved word. */ +int +find_reserved_word (tokstr) + char *tokstr; +{ + int i; + for (i = 0; word_token_alist[i].word; i++) + if (STREQ (tokstr, word_token_alist[i].word)) + return i; + return -1; +} + +/* An interface to let the rest of the shell (primarily the completion + system) know what the parser is expecting. */ +int +parser_in_command_position () +{ + return (command_token_position (last_read_token)); +} + +#if 0 +#if defined (READLINE) +/* Called after each time readline is called. This insures that whatever + the new prompt string is gets propagated to readline's local prompt + variable. */ +static void +reset_readline_prompt () +{ + char *temp_prompt; + + if (prompt_string_pointer) + { + temp_prompt = (*prompt_string_pointer) + ? decode_prompt_string (*prompt_string_pointer) + : (char *)NULL; + + if (temp_prompt == 0) + { + temp_prompt = (char *)xmalloc (1); + temp_prompt[0] = '\0'; + } + + FREE (current_readline_prompt); + current_readline_prompt = temp_prompt; + } +} +#endif /* READLINE */ +#endif /* 0 */ + +#if defined (HISTORY) +/* A list of tokens which can be followed by newlines, but not by + semi-colons. When concatenating multiple lines of history, the + newline separator for such tokens is replaced with a space. */ +static const int no_semi_successors[] = { + '\n', '{', '(', ')', ';', '&', '|', + CASE, DO, ELSE, IF, SEMI_SEMI, SEMI_AND, SEMI_SEMI_AND, THEN, UNTIL, + WHILE, AND_AND, OR_OR, IN, + 0 +}; + +/* If we are not within a delimited expression, try to be smart + about which separators can be semi-colons and which must be + newlines. Returns the string that should be added into the + history entry. LINE is the line we're about to add; it helps + make some more intelligent decisions in certain cases. */ +char * +history_delimiting_chars (line) + const char *line; +{ + static int last_was_heredoc = 0; /* was the last entry the start of a here document? */ + register int i; + + if ((parser_state & PST_HEREDOC) == 0) + last_was_heredoc = 0; + + if (dstack.delimiter_depth != 0) + return ("\n"); + + /* We look for current_command_line_count == 2 because we are looking to + add the first line of the body of the here document (the second line + of the command). We also keep LAST_WAS_HEREDOC as a private sentinel + variable to note when we think we added the first line of a here doc + (the one with a "<<" somewhere in it) */ + if (parser_state & PST_HEREDOC) + { + if (last_was_heredoc) + { + last_was_heredoc = 0; + return "\n"; + } + return (here_doc_first_line ? "\n" : ""); + } + + if (parser_state & PST_COMPASSIGN) + return (" "); + + /* First, handle some special cases. */ + /*(*/ + /* If we just read `()', assume it's a function definition, and don't + add a semicolon. If the token before the `)' was not `(', and we're + not in the midst of parsing a case statement, assume it's a + parenthesized command and add the semicolon. */ + /*)(*/ + if (token_before_that == ')') + { + if (two_tokens_ago == '(') /*)*/ /* function def */ + return " "; + /* This does not work for subshells inside case statement + command lists. It's a suboptimal solution. */ + else if (parser_state & PST_CASESTMT) /* case statement pattern */ + return " "; + else + return "; "; /* (...) subshell */ + } + else if (token_before_that == WORD && two_tokens_ago == FUNCTION) + return " "; /* function def using `function name' without `()' */ + + /* If we're not in a here document, but we think we're about to parse one, + and we would otherwise return a `;', return a newline to delimit the + line with the here-doc delimiter */ + else if ((parser_state & PST_HEREDOC) == 0 && current_command_line_count > 1 && last_read_token == '\n' && strstr (line, "<<")) + { + last_was_heredoc = 1; + return "\n"; + } + else if ((parser_state & PST_HEREDOC) == 0 && current_command_line_count > 1 && need_here_doc > 0) + return "\n"; + else if (token_before_that == WORD && two_tokens_ago == FOR) + { + /* Tricky. `for i\nin ...' should not have a semicolon, but + `for i\ndo ...' should. We do what we can. */ + for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++) + ; + if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n') + return " "; + return ";"; + } + else if (two_tokens_ago == CASE && token_before_that == WORD && (parser_state & PST_CASESTMT)) + return " "; + + for (i = 0; no_semi_successors[i]; i++) + { + if (token_before_that == no_semi_successors[i]) + return (" "); + } + + /* Assume that by this point we are reading lines in a multi-line command. + If we have multiple consecutive blank lines we want to return only one + semicolon. */ + if (line_isblank (line)) + return (current_command_line_count > 1 && last_read_token == '\n' && token_before_that != '\n') ? "; " : ""; + + return ("; "); +} +#endif /* HISTORY */ + +/* Issue a prompt, or prepare to issue a prompt when the next character + is read. */ +static void +prompt_again (force) + int force; +{ + char *temp_prompt; + + if (interactive == 0 || expanding_alias ()) /* XXX */ + return; + + ps1_prompt = get_string_value ("PS1"); + ps2_prompt = get_string_value ("PS2"); + + ps0_prompt = get_string_value ("PS0"); + + if (!prompt_string_pointer) + prompt_string_pointer = &ps1_prompt; + + temp_prompt = *prompt_string_pointer + ? decode_prompt_string (*prompt_string_pointer) + : (char *)NULL; + + if (temp_prompt == 0) + { + temp_prompt = (char *)xmalloc (1); + temp_prompt[0] = '\0'; + } + + current_prompt_string = *prompt_string_pointer; + prompt_string_pointer = &ps2_prompt; + +#if defined (READLINE) + if (!no_line_editing) + { + FREE (current_readline_prompt); + current_readline_prompt = temp_prompt; + } + else +#endif /* READLINE */ + { + FREE (current_decoded_prompt); + current_decoded_prompt = temp_prompt; + } +} + +int +get_current_prompt_level () +{ + return ((current_prompt_string && current_prompt_string == ps2_prompt) ? 2 : 1); +} + +void +set_current_prompt_level (x) + int x; +{ + prompt_string_pointer = (x == 2) ? &ps2_prompt : &ps1_prompt; + current_prompt_string = *prompt_string_pointer; +} + +static void +print_prompt () +{ + fprintf (stderr, "%s", current_decoded_prompt); + fflush (stderr); +} + +#if defined (HISTORY) + /* The history library increments the history offset as soon as it stores + the first line of a potentially multi-line command, so we compensate + here by returning one fewer when appropriate. */ +static int +prompt_history_number (pmt) + char *pmt; +{ + int ret; + + ret = history_number (); + if (ret == 1) + return ret; + + if (pmt == ps1_prompt) /* are we expanding $PS1? */ + return ret; + else if (pmt == ps2_prompt && command_oriented_history == 0) + return ret; /* not command oriented history */ + else if (pmt == ps2_prompt && command_oriented_history && current_command_first_line_saved) + return ret - 1; + else + return ret - 1; /* PS0, PS4, ${var@P}, PS2 other cases */ +} +#endif + +/* Return a string which will be printed as a prompt. The string + may contain special characters which are decoded as follows: + + \a bell (ascii 07) + \d the date in Day Mon Date format + \e escape (ascii 033) + \h the hostname up to the first `.' + \H the hostname + \j the number of active jobs + \l the basename of the shell's tty device name + \n CRLF + \r CR + \s the name of the shell + \t the time in 24-hour hh:mm:ss format + \T the time in 12-hour hh:mm:ss format + \@ the time in 12-hour hh:mm am/pm format + \A the time in 24-hour hh:mm format + \D{fmt} the result of passing FMT to strftime(3) + \u your username + \v the version of bash (e.g., 2.00) + \V the release of bash, version + patchlevel (e.g., 2.00.0) + \w the current working directory + \W the last element of $PWD + \! the history number of this command + \# the command number of this command + \$ a $ or a # if you are root + \nnn character code nnn in octal + \\ a backslash + \[ begin a sequence of non-printing chars + \] end a sequence of non-printing chars +*/ +#define PROMPT_GROWTH 48 +char * +decode_prompt_string (string) + char *string; +{ + WORD_LIST *list; + char *result, *t, *orig_string; + struct dstack save_dstack; + int last_exit_value, last_comsub_pid; +#if defined (PROMPT_STRING_DECODE) + size_t result_size; + size_t result_index; + int c, n, i; + char *temp, *t_host, octal_string[4]; + struct tm *tm; + time_t the_time; + char timebuf[128]; + char *timefmt; + + result = (char *)xmalloc (result_size = PROMPT_GROWTH); + result[result_index = 0] = 0; + temp = (char *)NULL; + orig_string = string; + + while (c = *string++) + { + if (posixly_correct && c == '!') + { + if (*string == '!') + { + temp = savestring ("!"); + goto add_string; + } + else + { +#if !defined (HISTORY) + temp = savestring ("1"); +#else /* HISTORY */ + temp = itos (prompt_history_number (orig_string)); +#endif /* HISTORY */ + string--; /* add_string increments string again. */ + goto add_string; + } + } + if (c == '\\') + { + c = *string; + + switch (c) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + strncpy (octal_string, string, 3); + octal_string[3] = '\0'; + + n = read_octal (octal_string); + temp = (char *)xmalloc (3); + + if (n == CTLESC || n == CTLNUL) + { + temp[0] = CTLESC; + temp[1] = n; + temp[2] = '\0'; + } + else if (n == -1) + { + temp[0] = '\\'; + temp[1] = '\0'; + } + else + { + temp[0] = n; + temp[1] = '\0'; + } + + for (c = 0; n != -1 && c < 3 && ISOCTAL (*string); c++) + string++; + + c = 0; /* tested at add_string: */ + goto add_string; + + case 'd': + case 't': + case 'T': + case '@': + case 'A': + /* Make the current time/date into a string. */ + (void) time (&the_time); +#if defined (HAVE_TZSET) + sv_tz ("TZ"); /* XXX -- just make sure */ +#endif + tm = localtime (&the_time); + + if (c == 'd') + n = strftime (timebuf, sizeof (timebuf), "%a %b %d", tm); + else if (c == 't') + n = strftime (timebuf, sizeof (timebuf), "%H:%M:%S", tm); + else if (c == 'T') + n = strftime (timebuf, sizeof (timebuf), "%I:%M:%S", tm); + else if (c == '@') + n = strftime (timebuf, sizeof (timebuf), "%I:%M %p", tm); + else if (c == 'A') + n = strftime (timebuf, sizeof (timebuf), "%H:%M", tm); + + if (n == 0) + timebuf[0] = '\0'; + else + timebuf[sizeof(timebuf) - 1] = '\0'; + + temp = savestring (timebuf); + goto add_string; + + case 'D': /* strftime format */ + if (string[1] != '{') /* } */ + goto not_escape; + + (void) time (&the_time); + tm = localtime (&the_time); + string += 2; /* skip { */ + timefmt = xmalloc (strlen (string) + 3); + for (t = timefmt; *string && *string != '}'; ) + *t++ = *string++; + *t = '\0'; + c = *string; /* tested at add_string */ + if (timefmt[0] == '\0') + { + timefmt[0] = '%'; + timefmt[1] = 'X'; /* locale-specific current time */ + timefmt[2] = '\0'; + } + n = strftime (timebuf, sizeof (timebuf), timefmt, tm); + free (timefmt); + + if (n == 0) + timebuf[0] = '\0'; + else + timebuf[sizeof(timebuf) - 1] = '\0'; + + if (promptvars || posixly_correct) + /* Make sure that expand_prompt_string is called with a + second argument of Q_DOUBLE_QUOTES if we use this + function here. */ + temp = sh_backslash_quote_for_double_quotes (timebuf, 0); + else + temp = savestring (timebuf); + goto add_string; + + case 'n': + temp = (char *)xmalloc (3); + temp[0] = no_line_editing ? '\n' : '\r'; + temp[1] = no_line_editing ? '\0' : '\n'; + temp[2] = '\0'; + goto add_string; + + case 's': + temp = base_pathname (shell_name); + /* Try to quote anything the user can set in the file system */ + if (promptvars || posixly_correct) + { + char *t; + t = sh_strvis (temp); + temp = sh_backslash_quote_for_double_quotes (t, 0); + free (t); + } + else + temp = sh_strvis (temp); + goto add_string; + + case 'v': + case 'V': + temp = (char *)xmalloc (16); + if (c == 'v') + strcpy (temp, dist_version); + else + sprintf (temp, "%s.%d", dist_version, patch_level); + goto add_string; + + case 'w': + case 'W': + { + /* Use the value of PWD because it is much more efficient. */ + char t_string[PATH_MAX]; + int tlen; + + temp = get_string_value ("PWD"); + + if (temp == 0) + { + if (getcwd (t_string, sizeof(t_string)) == 0) + { + t_string[0] = '.'; + tlen = 1; + } + else + tlen = strlen (t_string); + } + else + { + tlen = sizeof (t_string) - 1; + strncpy (t_string, temp, tlen); + } + t_string[tlen] = '\0'; + +#if defined (MACOSX) + /* Convert from "fs" format to "input" format */ + temp = fnx_fromfs (t_string, strlen (t_string)); + if (temp != t_string) + strcpy (t_string, temp); +#endif + +#define ROOT_PATH(x) ((x)[0] == '/' && (x)[1] == 0) +#define DOUBLE_SLASH_ROOT(x) ((x)[0] == '/' && (x)[1] == '/' && (x)[2] == 0) + /* Abbreviate \W as ~ if $PWD == $HOME */ + if (c == 'W' && (((t = get_string_value ("HOME")) == 0) || STREQ (t, t_string) == 0)) + { + if (ROOT_PATH (t_string) == 0 && DOUBLE_SLASH_ROOT (t_string) == 0) + { + t = strrchr (t_string, '/'); + if (t) + memmove (t_string, t + 1, strlen (t)); /* strlen(t) to copy NULL */ + } + } +#undef ROOT_PATH +#undef DOUBLE_SLASH_ROOT + else + { + /* polite_directory_format is guaranteed to return a string + no longer than PATH_MAX - 1 characters. */ + temp = polite_directory_format (t_string); + if (temp != t_string) + strcpy (t_string, temp); + } + + temp = trim_pathname (t_string, PATH_MAX - 1); + /* If we're going to be expanding the prompt string later, + quote the directory name. */ + if (promptvars || posixly_correct) + /* Make sure that expand_prompt_string is called with a + second argument of Q_DOUBLE_QUOTES if we use this + function here. */ + { + char *t; + t = sh_strvis (t_string); + temp = sh_backslash_quote_for_double_quotes (t, 0); + free (t); + } + else + temp = sh_strvis (t_string); + + goto add_string; + } + + case 'u': + if (current_user.user_name == 0) + get_current_user_info (); + temp = savestring (current_user.user_name); + goto add_string; + + case 'h': + case 'H': + t_host = savestring (current_host_name); + if (c == 'h' && (t = (char *)strchr (t_host, '.'))) + *t = '\0'; + if (promptvars || posixly_correct) + /* Make sure that expand_prompt_string is called with a + second argument of Q_DOUBLE_QUOTES if we use this + function here. */ + temp = sh_backslash_quote_for_double_quotes (t_host, 0); + else + temp = savestring (t_host); + free (t_host); + goto add_string; + + case '#': + n = current_command_number; + /* If we have already incremented current_command_number (PS4, + ${var@P}), compensate */ + if (orig_string != ps0_prompt && orig_string != ps1_prompt && orig_string != ps2_prompt) + n--; + temp = itos (n); + goto add_string; + + case '!': +#if !defined (HISTORY) + temp = savestring ("1"); +#else /* HISTORY */ + temp = itos (prompt_history_number (orig_string)); +#endif /* HISTORY */ + goto add_string; + + case '$': + t = temp = (char *)xmalloc (3); + if ((promptvars || posixly_correct) && (current_user.euid != 0)) + *t++ = '\\'; + *t++ = current_user.euid == 0 ? '#' : '$'; + *t = '\0'; + goto add_string; + + case 'j': + temp = itos (count_all_jobs ()); + goto add_string; + + case 'l': +#if defined (HAVE_TTYNAME) + temp = (char *)ttyname (fileno (stdin)); + t = temp ? base_pathname (temp) : "tty"; + temp = savestring (t); +#else + temp = savestring ("tty"); +#endif /* !HAVE_TTYNAME */ + goto add_string; + +#if defined (READLINE) + case '[': + case ']': + if (no_line_editing) + { + string++; + break; + } + temp = (char *)xmalloc (3); + n = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE; + i = 0; + if (n == CTLESC || n == CTLNUL) + temp[i++] = CTLESC; + temp[i++] = n; + temp[i] = '\0'; + goto add_string; +#endif /* READLINE */ + + case '\\': + case 'a': + case 'e': + case 'r': + temp = (char *)xmalloc (2); + if (c == 'a') + temp[0] = '\07'; + else if (c == 'e') + temp[0] = '\033'; + else if (c == 'r') + temp[0] = '\r'; + else /* (c == '\\') */ + temp[0] = c; + temp[1] = '\0'; + goto add_string; + + default: +not_escape: + temp = (char *)xmalloc (3); + temp[0] = '\\'; + temp[1] = c; + temp[2] = '\0'; + + add_string: + if (c) + string++; + result = + sub_append_string (temp, result, &result_index, &result_size); + temp = (char *)NULL; /* Freed in sub_append_string (). */ + result[result_index] = '\0'; + break; + } + } + else + { + RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, PROMPT_GROWTH); + /* dequote_string should take care of removing this if we are not + performing the rest of the word expansions. */ + if (c == CTLESC || c == CTLNUL) + result[result_index++] = CTLESC; + result[result_index++] = c; + result[result_index] = '\0'; + } + } +#else /* !PROMPT_STRING_DECODE */ + result = savestring (string); +#endif /* !PROMPT_STRING_DECODE */ + + /* Save the delimiter stack and point `dstack' to temp space so any + command substitutions in the prompt string won't result in screwing + up the parser's quoting state. */ + save_dstack = dstack; + dstack = temp_dstack; + dstack.delimiter_depth = 0; + + /* Perform variable and parameter expansion and command substitution on + the prompt string. */ + if (promptvars || posixly_correct) + { + last_exit_value = last_command_exit_value; + last_comsub_pid = last_command_subst_pid; + list = expand_prompt_string (result, Q_DOUBLE_QUOTES, 0); + free (result); + result = string_list (list); + dispose_words (list); + last_command_exit_value = last_exit_value; + last_command_subst_pid = last_comsub_pid; + } + else + { + t = dequote_string (result); + free (result); + result = t; + } + + dstack = save_dstack; + + return (result); +} + +/************************************************ + * * + * ERROR HANDLING * + * * + ************************************************/ + +/* Report a syntax error, and restart the parser. Call here for fatal + errors. */ +int +yyerror (msg) + const char *msg; +{ + if ((parser_state & PST_NOERROR) == 0) + report_syntax_error ((char *)NULL); + reset_parser (); + return (0); +} + +static char * +error_token_from_token (tok) + int tok; +{ + char *t; + + if (t = find_token_in_alist (tok, word_token_alist, 0)) + return t; + + if (t = find_token_in_alist (tok, other_token_alist, 0)) + return t; + + t = (char *)NULL; + /* This stuff is dicy and needs closer inspection */ + switch (current_token) + { + case WORD: + case ASSIGNMENT_WORD: + if (yylval.word) + t = savestring (yylval.word->word); + break; + case NUMBER: + t = itos (yylval.number); + break; + case ARITH_CMD: + if (yylval.word_list) + t = string_list (yylval.word_list); + break; + case ARITH_FOR_EXPRS: + if (yylval.word_list) + t = string_list_internal (yylval.word_list, " ; "); + break; + case COND_CMD: + t = (char *)NULL; /* punt */ + break; + } + + return t; +} + +static char * +error_token_from_text () +{ + char *msg, *t; + int token_end, i; + + t = shell_input_line; + i = shell_input_line_index; + token_end = 0; + msg = (char *)NULL; + + if (i && t[i] == '\0') + i--; + + while (i && (whitespace (t[i]) || t[i] == '\n')) + i--; + + if (i) + token_end = i + 1; + + while (i && (member (t[i], " \n\t;|&") == 0)) + i--; + + while (i != token_end && (whitespace (t[i]) || t[i] == '\n')) + i++; + + /* Return our idea of the offending token. */ + if (token_end || (i == 0 && token_end == 0)) + { + if (token_end) + msg = substring (t, i, token_end); + else /* one-character token */ + { + msg = (char *)xmalloc (2); + msg[0] = t[i]; + msg[1] = '\0'; + } + } + + return (msg); +} + +static void +print_offending_line () +{ + char *msg; + int token_end; + + msg = savestring (shell_input_line); + token_end = strlen (msg); + while (token_end && msg[token_end - 1] == '\n') + msg[--token_end] = '\0'; + + parser_error (line_number, "`%s'", msg); + free (msg); +} + +/* Report a syntax error with line numbers, etc. + Call here for recoverable errors. If you have a message to print, + then place it in MESSAGE, otherwise pass NULL and this will figure + out an appropriate message for you. */ +static void +report_syntax_error (message) + char *message; +{ + char *msg, *p; + + if (message) + { + parser_error (line_number, "%s", message); + if (interactive && EOF_Reached) + EOF_Reached = 0; + last_command_exit_value = (executing_builtin && parse_and_execute_level) ? EX_BADSYNTAX : EX_BADUSAGE; + set_pipestatus_from_exit (last_command_exit_value); + return; + } + + /* If the line of input we're reading is not null, try to find the + objectionable token. First, try to figure out what token the + parser's complaining about by looking at current_token. */ + if (current_token != 0 && EOF_Reached == 0 && (msg = error_token_from_token (current_token))) + { + if (ansic_shouldquote (msg)) + { + p = ansic_quote (msg, 0, NULL); + free (msg); + msg = p; + } + parser_error (line_number, _("syntax error near unexpected token `%s'"), msg); + free (msg); + + if (interactive == 0) + print_offending_line (); + + last_command_exit_value = (executing_builtin && parse_and_execute_level) ? EX_BADSYNTAX : EX_BADUSAGE; + set_pipestatus_from_exit (last_command_exit_value); + return; + } + + /* If looking at the current token doesn't prove fruitful, try to find the + offending token by analyzing the text of the input line near the current + input line index and report what we find. */ + if (shell_input_line && *shell_input_line) + { + msg = error_token_from_text (); + if (msg) + { + parser_error (line_number, _("syntax error near `%s'"), msg); + free (msg); + } + + /* If not interactive, print the line containing the error. */ + if (interactive == 0) + print_offending_line (); + } + else + { + if (EOF_Reached && shell_eof_token && current_token != shell_eof_token) + parser_error (line_number, _("unexpected EOF while looking for matching `%c'"), shell_eof_token); + else + { + msg = EOF_Reached ? _("syntax error: unexpected end of file") : _("syntax error"); + parser_error (line_number, "%s", msg); + } + + /* When the shell is interactive, this file uses EOF_Reached + only for error reporting. Other mechanisms are used to + decide whether or not to exit. */ + if (interactive && EOF_Reached) + EOF_Reached = 0; + } + + last_command_exit_value = (executing_builtin && parse_and_execute_level) ? EX_BADSYNTAX : EX_BADUSAGE; + set_pipestatus_from_exit (last_command_exit_value); +} + +/* ??? Needed function. ??? We have to be able to discard the constructs + created during parsing. In the case of error, we want to return + allocated objects to the memory pool. In the case of no error, we want + to throw away the information about where the allocated objects live. + (dispose_command () will actually free the command.) */ +static void +discard_parser_constructs (error_p) + int error_p; +{ +} + +/************************************************ + * * + * EOF HANDLING * + * * + ************************************************/ + +/* Do that silly `type "bye" to exit' stuff. You know, "ignoreeof". */ + +/* A flag denoting whether or not ignoreeof is set. */ +int ignoreeof = 0; + +/* The number of times that we have encountered an EOF character without + another character intervening. When this gets above the limit, the + shell terminates. */ +int eof_encountered = 0; + +/* The limit for eof_encountered. */ +int eof_encountered_limit = 10; + +/* If we have EOF as the only input unit, this user wants to leave + the shell. If the shell is not interactive, then just leave. + Otherwise, if ignoreeof is set, and we haven't done this the + required number of times in a row, print a message. */ +static void +handle_eof_input_unit () +{ + if (interactive) + { + /* shell.c may use this to decide whether or not to write out the + history, among other things. We use it only for error reporting + in this file. */ + if (EOF_Reached) + EOF_Reached = 0; + + /* If the user wants to "ignore" eof, then let her do so, kind of. */ + if (ignoreeof) + { + if (eof_encountered < eof_encountered_limit) + { + fprintf (stderr, _("Use \"%s\" to leave the shell.\n"), + login_shell ? "logout" : "exit"); + eof_encountered++; + /* Reset the parsing state. */ + last_read_token = current_token = '\n'; + /* Reset the prompt string to be $PS1. */ + prompt_string_pointer = (char **)NULL; + prompt_again (0); + return; + } + } + + /* In this case EOF should exit the shell. Do it now. */ + reset_parser (); + + last_shell_builtin = this_shell_builtin; + this_shell_builtin = exit_builtin; + exit_builtin ((WORD_LIST *)NULL); + } + else + { + /* We don't write history files, etc., for non-interactive shells. */ + EOF_Reached = 1; + } +} + +/************************************************ + * * + * STRING PARSING FUNCTIONS * + * * + ************************************************/ + +/* It's very important that these two functions treat the characters + between ( and ) identically. */ + +static WORD_LIST parse_string_error; + +/* Take a string and run it through the shell parser, returning the + resultant word list. Used by compound array assignment. */ +WORD_LIST * +parse_string_to_word_list (s, flags, whom) + char *s; + int flags; + const char *whom; +{ + WORD_LIST *wl; + int tok, orig_current_token, orig_line_number; + int orig_parser_state; + sh_parser_state_t ps; + int ea; + + orig_line_number = line_number; + save_parser_state (&ps); + +#if defined (HISTORY) + bash_history_disable (); +#endif + + push_stream (1); + if (ea = expanding_alias ()) + parser_save_alias (); + + /* WORD to avoid parsing reserved words as themselves and just parse them as + WORDs. */ + last_read_token = WORD; + + current_command_line_count = 0; + echo_input_at_read = expand_aliases = 0; + + with_input_from_string (s, whom); + wl = (WORD_LIST *)NULL; + + if (flags & 1) + { + orig_parser_state = parser_state; /* XXX - not needed? */ + /* State flags we don't want to persist into compound assignments. */ + parser_state &= ~PST_NOEXPAND; /* parse_comsub sentinel */ + /* State flags we want to set for this run through the tokenizer. */ + parser_state |= PST_COMPASSIGN|PST_REPARSE; + } + + while ((tok = read_token (READ)) != yacc_EOF) + { + if (tok == '\n' && *bash_input.location.string == '\0') + break; + if (tok == '\n') /* Allow newlines in compound assignments */ + continue; + if (tok != WORD && tok != ASSIGNMENT_WORD) + { + line_number = orig_line_number + line_number - 1; + orig_current_token = current_token; + current_token = tok; + yyerror (NULL); /* does the right thing */ + current_token = orig_current_token; + if (wl) + dispose_words (wl); + wl = &parse_string_error; + break; + } + wl = make_word_list (yylval.word, wl); + } + + last_read_token = '\n'; + pop_stream (); + + if (ea) + parser_restore_alias (); + + restore_parser_state (&ps); + + if (flags & 1) + parser_state = orig_parser_state; /* XXX - not needed? */ + + if (wl == &parse_string_error) + { + set_exit_status (EXECUTION_FAILURE); + if (interactive_shell == 0 && posixly_correct) + jump_to_top_level (FORCE_EOF); + else + jump_to_top_level (DISCARD); + } + + return (REVERSE_LIST (wl, WORD_LIST *)); +} + +static char * +parse_compound_assignment (retlenp) + int *retlenp; +{ + WORD_LIST *wl, *rl; + int tok, orig_line_number, assignok; + sh_parser_state_t ps; + char *ret; + + orig_line_number = line_number; + save_parser_state (&ps); + + /* WORD to avoid parsing reserved words as themselves and just parse them as + WORDs. Plus it means we won't be in a command position and so alias + expansion won't happen. */ + last_read_token = WORD; + + token = (char *)NULL; + token_buffer_size = 0; + wl = (WORD_LIST *)NULL; /* ( */ + + assignok = parser_state&PST_ASSIGNOK; /* XXX */ + + /* State flags we don't want to persist into compound assignments. */ + parser_state &= ~(PST_NOEXPAND|PST_CONDCMD|PST_CONDEXPR|PST_REGEXP|PST_EXTPAT); + /* State flags we want to set for this run through the tokenizer. */ + parser_state |= PST_COMPASSIGN; + + esacs_needed_count = expecting_in_token = 0; + + while ((tok = read_token (READ)) != ')') + { + if (tok == '\n') /* Allow newlines in compound assignments */ + { + if (SHOULD_PROMPT ()) + prompt_again (0); + continue; + } + if (tok != WORD && tok != ASSIGNMENT_WORD) + { + current_token = tok; /* for error reporting */ + if (tok == yacc_EOF) /* ( */ + parser_error (orig_line_number, _("unexpected EOF while looking for matching `)'")); + else + yyerror(NULL); /* does the right thing */ + if (wl) + dispose_words (wl); + wl = &parse_string_error; + break; + } + wl = make_word_list (yylval.word, wl); + } + + restore_parser_state (&ps); + + if (wl == &parse_string_error) + { + set_exit_status (EXECUTION_FAILURE); + last_read_token = '\n'; /* XXX */ + if (interactive_shell == 0 && posixly_correct) + jump_to_top_level (FORCE_EOF); + else + jump_to_top_level (DISCARD); + } + + if (wl) + { + rl = REVERSE_LIST (wl, WORD_LIST *); + ret = string_list (rl); + dispose_words (rl); + } + else + ret = (char *)NULL; + + if (retlenp) + *retlenp = (ret && *ret) ? strlen (ret) : 0; + + if (assignok) + parser_state |= PST_ASSIGNOK; + + return ret; +} + +/************************************************ + * * + * SAVING AND RESTORING PARTIAL PARSE STATE * + * * + ************************************************/ + +sh_parser_state_t * +save_parser_state (ps) + sh_parser_state_t *ps; +{ + if (ps == 0) + ps = (sh_parser_state_t *)xmalloc (sizeof (sh_parser_state_t)); + if (ps == 0) + return ((sh_parser_state_t *)NULL); + + ps->parser_state = parser_state; + ps->token_state = save_token_state (); + + ps->input_line_terminator = shell_input_line_terminator; + ps->eof_encountered = eof_encountered; + ps->eol_lookahead = eol_ungetc_lookahead; + + ps->prompt_string_pointer = prompt_string_pointer; + + ps->current_command_line_count = current_command_line_count; + +#if defined (HISTORY) + ps->remember_on_history = remember_on_history; +# if defined (BANG_HISTORY) + ps->history_expansion_inhibited = history_expansion_inhibited; +# endif +#endif + + ps->last_command_exit_value = last_command_exit_value; +#if defined (ARRAY_VARS) + ps->pipestatus = save_pipestatus_array (); +#endif + + ps->last_shell_builtin = last_shell_builtin; + ps->this_shell_builtin = this_shell_builtin; + + ps->expand_aliases = expand_aliases; + ps->echo_input_at_read = echo_input_at_read; + ps->need_here_doc = need_here_doc; + ps->here_doc_first_line = here_doc_first_line; + + ps->esacs_needed = esacs_needed_count; + ps->expecting_in = expecting_in_token; + + if (need_here_doc == 0) + ps->redir_stack[0] = 0; + else + memcpy (ps->redir_stack, redir_stack, sizeof (redir_stack[0]) * HEREDOC_MAX); + +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + ps->pushed_strings = pushed_string_list; +#endif + + ps->eof_token = shell_eof_token; + ps->token = token; + ps->token_buffer_size = token_buffer_size; + /* Force reallocation on next call to read_token_word */ + token = 0; + token_buffer_size = 0; + + return (ps); +} + +void +restore_parser_state (ps) + sh_parser_state_t *ps; +{ + int i; + + if (ps == 0) + return; + + parser_state = ps->parser_state; + if (ps->token_state) + { + restore_token_state (ps->token_state); + free (ps->token_state); + } + + shell_input_line_terminator = ps->input_line_terminator; + eof_encountered = ps->eof_encountered; + eol_ungetc_lookahead = ps->eol_lookahead; + + prompt_string_pointer = ps->prompt_string_pointer; + + current_command_line_count = ps->current_command_line_count; + +#if defined (HISTORY) + remember_on_history = ps->remember_on_history; +# if defined (BANG_HISTORY) + history_expansion_inhibited = ps->history_expansion_inhibited; +# endif +#endif + + last_command_exit_value = ps->last_command_exit_value; +#if defined (ARRAY_VARS) + restore_pipestatus_array (ps->pipestatus); +#endif + + last_shell_builtin = ps->last_shell_builtin; + this_shell_builtin = ps->this_shell_builtin; + + expand_aliases = ps->expand_aliases; + echo_input_at_read = ps->echo_input_at_read; + need_here_doc = ps->need_here_doc; + here_doc_first_line = ps->here_doc_first_line; + + esacs_needed_count = ps->esacs_needed; + expecting_in_token = ps->expecting_in; + +#if 0 + for (i = 0; i < HEREDOC_MAX; i++) + redir_stack[i] = ps->redir_stack[i]; +#else + if (need_here_doc == 0) + redir_stack[0] = 0; + else + memcpy (redir_stack, ps->redir_stack, sizeof (redir_stack[0]) * HEREDOC_MAX); +#endif + +#if defined (ALIAS) || defined (DPAREN_ARITHMETIC) + pushed_string_list = (STRING_SAVER *)ps->pushed_strings; +#endif + + FREE (token); + token = ps->token; + token_buffer_size = ps->token_buffer_size; + shell_eof_token = ps->eof_token; +} + +sh_input_line_state_t * +save_input_line_state (ls) + sh_input_line_state_t *ls; +{ + if (ls == 0) + ls = (sh_input_line_state_t *)xmalloc (sizeof (sh_input_line_state_t)); + if (ls == 0) + return ((sh_input_line_state_t *)NULL); + + ls->input_line = shell_input_line; + ls->input_line_size = shell_input_line_size; + ls->input_line_len = shell_input_line_len; + ls->input_line_index = shell_input_line_index; + +#if defined (HANDLE_MULTIBYTE) + ls->input_property = shell_input_line_property; + ls->input_propsize = shell_input_line_propsize; +#endif + + /* force reallocation */ + shell_input_line = 0; + shell_input_line_size = shell_input_line_len = shell_input_line_index = 0; + +#if defined (HANDLE_MULTIBYTE) + shell_input_line_property = 0; + shell_input_line_propsize = 0; +#endif + + return ls; +} + +void +restore_input_line_state (ls) + sh_input_line_state_t *ls; +{ + FREE (shell_input_line); + shell_input_line = ls->input_line; + shell_input_line_size = ls->input_line_size; + shell_input_line_len = ls->input_line_len; + shell_input_line_index = ls->input_line_index; + +#if defined (HANDLE_MULTIBYTE) + FREE (shell_input_line_property); + shell_input_line_property = ls->input_property; + shell_input_line_propsize = ls->input_propsize; +#endif + +#if 0 + set_line_mbstate (); +#endif +} + +/************************************************ + * * + * MULTIBYTE CHARACTER HANDLING * + * * + ************************************************/ + +#if defined (HANDLE_MULTIBYTE) + +/* We don't let the property buffer get larger than this unless the line is */ +#define MAX_PROPSIZE 32768 + +static void +set_line_mbstate () +{ + int c; + size_t i, previ, len; + mbstate_t mbs, prevs; + size_t mbclen; + int ilen; + + if (shell_input_line == NULL) + return; + len = STRLEN (shell_input_line); /* XXX - shell_input_line_len ? */ + if (len == 0) + return; + if (shell_input_line_propsize >= MAX_PROPSIZE && len < MAX_PROPSIZE>>1) + { + free (shell_input_line_property); + shell_input_line_property = 0; + shell_input_line_propsize = 0; + } + if (len+1 > shell_input_line_propsize) + { + shell_input_line_propsize = len + 1; + shell_input_line_property = (char *)xrealloc (shell_input_line_property, shell_input_line_propsize); + } + + if (locale_mb_cur_max == 1) + { + memset (shell_input_line_property, 1, len); + return; + } + + /* XXX - use whether or not we are in a UTF-8 locale to avoid calls to + mbrlen */ + if (locale_utf8locale == 0) + memset (&prevs, '\0', sizeof (mbstate_t)); + + for (i = previ = 0; i < len; i++) + { + if (locale_utf8locale == 0) + mbs = prevs; + + c = shell_input_line[i]; + if (c == EOF) + { + size_t j; + for (j = i; j < len; j++) + shell_input_line_property[j] = 1; + break; + } + + if (locale_utf8locale) + { + if ((unsigned char)shell_input_line[previ] < 128) /* i != previ */ + mbclen = 1; + else + { + ilen = utf8_mblen (shell_input_line + previ, i - previ + 1); + mbclen = (ilen == -1) ? (size_t)-1 + : ((ilen == -2) ? (size_t)-2 : (size_t)ilen); + } + } + else + mbclen = mbrlen (shell_input_line + previ, i - previ + 1, &mbs); + + if (mbclen == 1 || mbclen == (size_t)-1) + { + mbclen = 1; + previ = i + 1; + } + else if (mbclen == (size_t)-2) + mbclen = 0; + else if (mbclen > 1) + { + mbclen = 0; + previ = i + 1; + if (locale_utf8locale == 0) + prevs = mbs; + } + else + { + size_t j; + for (j = i; j < len; j++) + shell_input_line_property[j] = 1; + break; + } + + shell_input_line_property[i] = mbclen; + } +} +#endif /* HANDLE_MULTIBYTE */ diff --git a/third_party/bash/y.tab.h b/third_party/bash/y.tab.h new file mode 100644 index 000000000..3f3010777 --- /dev/null +++ b/third_party/bash/y.tab.h @@ -0,0 +1,191 @@ +/* A Bison parser, made by GNU Bison 3.8.2. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation, + Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual, + especially those whose name start with YY_ or yy_. They are + private implementation details that can be changed or removed. */ + +#ifndef YY_YY_Y_TAB_H_INCLUDED +# define YY_YY_Y_TAB_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int yydebug; +#endif + +/* Token kinds. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + YYEMPTY = -2, + YYEOF = 0, /* "end of file" */ + YYerror = 256, /* error */ + YYUNDEF = 257, /* "invalid token" */ + IF = 258, /* IF */ + THEN = 259, /* THEN */ + ELSE = 260, /* ELSE */ + ELIF = 261, /* ELIF */ + FI = 262, /* FI */ + CASE = 263, /* CASE */ + ESAC = 264, /* ESAC */ + FOR = 265, /* FOR */ + SELECT = 266, /* SELECT */ + WHILE = 267, /* WHILE */ + UNTIL = 268, /* UNTIL */ + DO = 269, /* DO */ + DONE = 270, /* DONE */ + FUNCTION = 271, /* FUNCTION */ + COPROC = 272, /* COPROC */ + COND_START = 273, /* COND_START */ + COND_END = 274, /* COND_END */ + COND_ERROR = 275, /* COND_ERROR */ + IN = 276, /* IN */ + BANG = 277, /* BANG */ + TIME = 278, /* TIME */ + TIMEOPT = 279, /* TIMEOPT */ + TIMEIGN = 280, /* TIMEIGN */ + WORD = 281, /* WORD */ + ASSIGNMENT_WORD = 282, /* ASSIGNMENT_WORD */ + REDIR_WORD = 283, /* REDIR_WORD */ + NUMBER = 284, /* NUMBER */ + ARITH_CMD = 285, /* ARITH_CMD */ + ARITH_FOR_EXPRS = 286, /* ARITH_FOR_EXPRS */ + COND_CMD = 287, /* COND_CMD */ + AND_AND = 288, /* AND_AND */ + OR_OR = 289, /* OR_OR */ + GREATER_GREATER = 290, /* GREATER_GREATER */ + LESS_LESS = 291, /* LESS_LESS */ + LESS_AND = 292, /* LESS_AND */ + LESS_LESS_LESS = 293, /* LESS_LESS_LESS */ + GREATER_AND = 294, /* GREATER_AND */ + SEMI_SEMI = 295, /* SEMI_SEMI */ + SEMI_AND = 296, /* SEMI_AND */ + SEMI_SEMI_AND = 297, /* SEMI_SEMI_AND */ + LESS_LESS_MINUS = 298, /* LESS_LESS_MINUS */ + AND_GREATER = 299, /* AND_GREATER */ + AND_GREATER_GREATER = 300, /* AND_GREATER_GREATER */ + LESS_GREATER = 301, /* LESS_GREATER */ + GREATER_BAR = 302, /* GREATER_BAR */ + BAR_AND = 303, /* BAR_AND */ + DOLPAREN = 304, /* DOLPAREN */ + yacc_EOF = 305 /* yacc_EOF */ + }; + typedef enum yytokentype yytoken_kind_t; +#endif +/* Token kinds. */ +#define YYEMPTY -2 +#define YYEOF 0 +#define YYerror 256 +#define YYUNDEF 257 +#define IF 258 +#define THEN 259 +#define ELSE 260 +#define ELIF 261 +#define FI 262 +#define CASE 263 +#define ESAC 264 +#define FOR 265 +#define SELECT 266 +#define WHILE 267 +#define UNTIL 268 +#define DO 269 +#define DONE 270 +#define FUNCTION 271 +#define COPROC 272 +#define COND_START 273 +#define COND_END 274 +#define COND_ERROR 275 +#define IN 276 +#define BANG 277 +#define TIME 278 +#define TIMEOPT 279 +#define TIMEIGN 280 +#define WORD 281 +#define ASSIGNMENT_WORD 282 +#define REDIR_WORD 283 +#define NUMBER 284 +#define ARITH_CMD 285 +#define ARITH_FOR_EXPRS 286 +#define COND_CMD 287 +#define AND_AND 288 +#define OR_OR 289 +#define GREATER_GREATER 290 +#define LESS_LESS 291 +#define LESS_AND 292 +#define LESS_LESS_LESS 293 +#define GREATER_AND 294 +#define SEMI_SEMI 295 +#define SEMI_AND 296 +#define SEMI_SEMI_AND 297 +#define LESS_LESS_MINUS 298 +#define AND_GREATER 299 +#define AND_GREATER_GREATER 300 +#define LESS_GREATER 301 +#define GREATER_BAR 302 +#define BAR_AND 303 +#define DOLPAREN 304 +#define yacc_EOF 305 + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED +union YYSTYPE +{ +#line 338 "/usr/local/src/chet/src/bash/src/parse.y" + + WORD_DESC *word; /* the word that we read. */ + int number; /* the number that we read. */ + WORD_LIST *word_list; + COMMAND *command; + REDIRECT *redirect; + ELEMENT element; + PATTERN_LIST *pattern; + +#line 177 "y.tab.h" + +}; +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + + +extern YYSTYPE yylval; + + +int yyparse (void); + + +#endif /* !YY_YY_Y_TAB_H_INCLUDED */ diff --git a/third_party/bash/zcatfd.c b/third_party/bash/zcatfd.c new file mode 100644 index 000000000..b172c1aa5 --- /dev/null +++ b/third_party/bash/zcatfd.c @@ -0,0 +1,74 @@ +/* zcatfd - copy contents of file descriptor to another */ + +/* Copyright (C) 2002-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#include "stdc.h" + +#if !defined (errno) +extern int errno; +#endif + +#ifndef ZBUFSIZ +# define ZBUFSIZ 4096 +#endif + +extern ssize_t zread PARAMS((int, char *, size_t)); +extern int zwrite PARAMS((int, char *, ssize_t)); + +/* Dump contents of file descriptor FD to OFD. FN is the filename for + error messages (not used right now). */ +int +zcatfd (fd, ofd, fn) + int fd, ofd; + char *fn; +{ + ssize_t nr; + int rval; + char lbuf[ZBUFSIZ]; + + rval = 0; + while (1) + { + nr = zread (fd, lbuf, sizeof (lbuf)); + if (nr == 0) + break; + else if (nr < 0) + { + rval = -1; + break; + } + else if (zwrite (ofd, lbuf, nr) < 0) + { + rval = -1; + break; + } + } + + return rval; +} diff --git a/third_party/bash/zgetline.c b/third_party/bash/zgetline.c new file mode 100644 index 000000000..2b4801fc6 --- /dev/null +++ b/third_party/bash/zgetline.c @@ -0,0 +1,126 @@ +/* zgetline - read a line of input from a specified file descriptor and return + a pointer to a newly-allocated buffer containing the data. */ + +/* Copyright (C) 2008-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include "xmalloc.h" + +#if !defined (errno) +extern int errno; +#endif + +extern ssize_t zread PARAMS((int, char *, size_t)); +extern ssize_t zreadc PARAMS((int, char *)); +extern ssize_t zreadintr PARAMS((int, char *, size_t)); +extern ssize_t zreadcintr PARAMS((int, char *)); + +typedef ssize_t breadfunc_t PARAMS((int, char *, size_t)); +typedef ssize_t creadfunc_t PARAMS((int, char *)); + +/* Initial memory allocation for automatic growing buffer in zreadlinec */ +#define GET_LINE_INITIAL_ALLOCATION 16 + +/* Derived from GNU libc's getline. + The behavior is almost the same as getline. See man getline. + The differences are + (1) using file descriptor instead of FILE *; + (2) the order of arguments: the file descriptor comes first; + (3) the addition of a fourth argument, DELIM; sets the delimiter to + be something other than newline if desired. If setting DELIM, + the next argument should be 1; and + (4) the addition of a fifth argument, UNBUFFERED_READ; this argument + controls whether get_line uses buffering or not to get a byte data + from FD. get_line uses zreadc if UNBUFFERED_READ is zero; and + uses zread if UNBUFFERED_READ is non-zero. + + Returns number of bytes read or -1 on error. */ + +ssize_t +zgetline (fd, lineptr, n, delim, unbuffered_read) + int fd; + char **lineptr; + size_t *n; + int delim; + int unbuffered_read; +{ + int retval; + size_t nr; + char *line, c; + + if (lineptr == 0 || n == 0 || (*lineptr == 0 && *n != 0)) + return -1; + + nr = 0; + line = *lineptr; + + while (1) + { + retval = unbuffered_read ? zread (fd, &c, 1) : zreadc(fd, &c); + + if (retval <= 0) + { + if (line && nr > 0) + line[nr] = '\0'; + break; + } + + if (nr + 2 >= *n) + { + size_t new_size; + + new_size = (*n == 0) ? GET_LINE_INITIAL_ALLOCATION : *n * 2; + line = (*n >= new_size) ? NULL : xrealloc (*lineptr, new_size); + + if (line) + { + *lineptr = line; + *n = new_size; + } + else + { + if (*n > 0) + { + (*lineptr)[*n - 1] = '\0'; + nr = *n - 2; + } + break; + } + } + + line[nr] = c; + nr++; + + if (c == delim) + { + line[nr] = '\0'; + break; + } + } + + return nr - 1; +} diff --git a/third_party/bash/zmapfd.c b/third_party/bash/zmapfd.c new file mode 100644 index 000000000..19a861a62 --- /dev/null +++ b/third_party/bash/zmapfd.c @@ -0,0 +1,93 @@ +/* zmapfd - read contents of file descriptor into a newly-allocated buffer */ + +/* Copyright (C) 2006-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#include "bashansi.h" +#include "command.h" +#include "general.h" + +#if !defined (errno) +extern int errno; +#endif + +#ifndef ZBUFSIZ +# define ZBUFSIZ 4096 +#endif + +extern ssize_t zread PARAMS((int, char *, size_t)); + +/* Dump contents of file descriptor FD to *OSTR. FN is the filename for + error messages (not used right now). */ +int +zmapfd (fd, ostr, fn) + int fd; + char **ostr; + char *fn; +{ + ssize_t nr; + int rval; + char lbuf[ZBUFSIZ]; + char *result; + size_t rsize, rind; + + rval = 0; + result = (char *)xmalloc (rsize = ZBUFSIZ); + rind = 0; + + while (1) + { + nr = zread (fd, lbuf, sizeof (lbuf)); + if (nr == 0) + { + rval = rind; + break; + } + else if (nr < 0) + { + free (result); + if (ostr) + *ostr = (char *)NULL; + return -1; + } + + RESIZE_MALLOCED_BUFFER (result, rind, nr, rsize, ZBUFSIZ); + memcpy (result+rind, lbuf, nr); + rind += nr; + } + + RESIZE_MALLOCED_BUFFER (result, rind, 1, rsize, 128); + result[rind] = '\0'; + + if (ostr) + *ostr = result; + else + free (result); + + return rval; +} diff --git a/third_party/bash/zread.c b/third_party/bash/zread.c new file mode 100644 index 000000000..ea38f6690 --- /dev/null +++ b/third_party/bash/zread.c @@ -0,0 +1,228 @@ +/* zread - read data from file descriptor into buffer with retries */ + +/* Copyright (C) 1999-2020 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include + +#if !defined (errno) +extern int errno; +#endif + +#ifndef SEEK_CUR +# define SEEK_CUR 1 +#endif + +#ifndef ZBUFSIZ +# define ZBUFSIZ 4096 +#endif + +extern int executing_builtin; + +extern void check_signals_and_traps (void); +extern void check_signals (void); +extern int signal_is_trapped (int); +extern int read_builtin_timeout (int); + +/* Read LEN bytes from FD into BUF. Retry the read on EINTR. Any other + error causes the loop to break. */ +ssize_t +zread (fd, buf, len) + int fd; + char *buf; + size_t len; +{ + ssize_t r; + + check_signals (); /* check for signals before a blocking read */ + /* should generalize into a mechanism where different parts of the shell can + `register' timeouts and have them checked here. */ + while (((r = read_builtin_timeout (fd)) < 0 || (r = read (fd, buf, len)) < 0) && + errno == EINTR) + { + int t; + t = errno; + /* XXX - bash-5.0 */ + /* We check executing_builtin and run traps here for backwards compatibility */ + if (executing_builtin) + check_signals_and_traps (); /* XXX - should it be check_signals()? */ + else + check_signals (); + errno = t; + } + + return r; +} + +/* Read LEN bytes from FD into BUF. Retry the read on EINTR, up to three + interrupts. Any other error causes the loop to break. */ + +#ifdef NUM_INTR +# undef NUM_INTR +#endif +#define NUM_INTR 3 + +ssize_t +zreadretry (fd, buf, len) + int fd; + char *buf; + size_t len; +{ + ssize_t r; + int nintr; + + for (nintr = 0; ; ) + { + r = read (fd, buf, len); + if (r >= 0) + return r; + if (r == -1 && errno == EINTR) + { + if (++nintr >= NUM_INTR) + return -1; + continue; + } + return r; + } +} + +/* Call read(2) and allow it to be interrupted. Just a stub for now. */ +ssize_t +zreadintr (fd, buf, len) + int fd; + char *buf; + size_t len; +{ + check_signals (); + return (read (fd, buf, len)); +} + +/* Read one character from FD and return it in CP. Return values are as + in read(2). This does some local buffering to avoid many one-character + calls to read(2), like those the `read' builtin performs. */ + +static char lbuf[ZBUFSIZ]; +static size_t lind, lused; + +ssize_t +zreadc (fd, cp) + int fd; + char *cp; +{ + ssize_t nr; + + if (lind == lused || lused == 0) + { + nr = zread (fd, lbuf, sizeof (lbuf)); + lind = 0; + if (nr <= 0) + { + lused = 0; + return nr; + } + lused = nr; + } + if (cp) + *cp = lbuf[lind++]; + return 1; +} + +/* Don't mix calls to zreadc and zreadcintr in the same function, since they + use the same local buffer. */ +ssize_t +zreadcintr (fd, cp) + int fd; + char *cp; +{ + ssize_t nr; + + if (lind == lused || lused == 0) + { + nr = zreadintr (fd, lbuf, sizeof (lbuf)); + lind = 0; + if (nr <= 0) + { + lused = 0; + return nr; + } + lused = nr; + } + if (cp) + *cp = lbuf[lind++]; + return 1; +} + +/* Like zreadc, but read a specified number of characters at a time. Used + for `read -N'. */ +ssize_t +zreadn (fd, cp, len) + int fd; + char *cp; + size_t len; +{ + ssize_t nr; + + if (lind == lused || lused == 0) + { + if (len > sizeof (lbuf)) + len = sizeof (lbuf); + nr = zread (fd, lbuf, len); + lind = 0; + if (nr <= 0) + { + lused = 0; + return nr; + } + lused = nr; + } + if (cp) + *cp = lbuf[lind++]; + return 1; +} + +void +zreset () +{ + lind = lused = 0; +} + +/* Sync the seek pointer for FD so that the kernel's idea of the last char + read is the last char returned by zreadc. */ +void +zsyncfd (fd) + int fd; +{ + off_t off, r; + + off = lused - lind; + r = 0; + if (off > 0) + r = lseek (fd, -off, SEEK_CUR); + + if (r != -1) + lused = lind = 0; +} diff --git a/third_party/bash/zwrite.c b/third_party/bash/zwrite.c new file mode 100644 index 000000000..db04750d7 --- /dev/null +++ b/third_party/bash/zwrite.c @@ -0,0 +1,64 @@ +/* zwrite - write contents of buffer to file descriptor, retrying on error */ + +/* Copyright (C) 1999-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +#include "config.h" + +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include + +#if !defined (errno) +extern int errno; +#endif + +/* Write NB bytes from BUF to file descriptor FD, retrying the write if + it is interrupted. We retry three times if we get a zero-length + write. Any other signal causes this function to return prematurely. */ +int +zwrite (fd, buf, nb) + int fd; + char *buf; + size_t nb; +{ + int n, i, nt; + + for (n = nb, nt = 0;;) + { + i = write (fd, buf, n); + if (i > 0) + { + n -= i; + if (n <= 0) + return nb; + buf += i; + } + else if (i == 0) + { + if (++nt > 3) + return (nb - n); + } + else if (errno != EINTR) + return -1; + } +}