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