summaryrefslogtreecommitdiffstats
path: root/builtins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:38:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:38:56 +0000
commit6c20c8ed2cb9ab69a1a57ccb2b9b79969a808321 (patch)
treef63ce19d57fad3ac4a15bc26dbfbfa2b834111b5 /builtins
parentInitial commit. (diff)
downloadbash-6c20c8ed2cb9ab69a1a57ccb2b9b79969a808321.tar.xz
bash-6c20c8ed2cb9ab69a1a57ccb2b9b79969a808321.zip
Adding upstream version 5.2.15.upstream/5.2.15upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--builtins.h68
-rw-r--r--builtins/Makefile.in723
-rw-r--r--builtins/alias.def241
-rw-r--r--builtins/bashgetopt.c194
-rw-r--r--builtins/bashgetopt.h43
-rw-r--r--builtins/bind.def405
-rw-r--r--builtins/break.def143
-rw-r--r--builtins/builtin.def85
-rw-r--r--builtins/caller.def156
-rw-r--r--builtins/cd.def685
-rw-r--r--builtins/colon.def76
-rw-r--r--builtins/command.def143
-rw-r--r--builtins/common.c1131
-rw-r--r--builtins/common.h289
-rw-r--r--builtins/complete.def892
-rw-r--r--builtins/declare.def1048
-rw-r--r--builtins/echo.def202
-rw-r--r--builtins/enable.def586
-rw-r--r--builtins/eval.def57
-rw-r--r--builtins/evalfile.c384
-rw-r--r--builtins/evalstring.c840
-rw-r--r--builtins/exec.def276
-rw-r--r--builtins/exit.def169
-rw-r--r--builtins/fc.def785
-rw-r--r--builtins/fg_bg.def189
-rw-r--r--builtins/gen-helpfiles.c198
-rw-r--r--builtins/getopt.c355
-rw-r--r--builtins/getopt.h82
-rw-r--r--builtins/getopts.def343
-rw-r--r--builtins/hash.def305
-rw-r--r--builtins/help.def552
-rw-r--r--builtins/history.def464
-rw-r--r--builtins/jobs.def300
-rw-r--r--builtins/kill.def276
-rw-r--r--builtins/let.def131
-rw-r--r--builtins/mapfile.def364
-rw-r--r--builtins/mkbuiltins.c1692
-rw-r--r--builtins/printf.def1355
-rw-r--r--builtins/psize.c79
-rw-r--r--builtins/psize.sh45
-rw-r--r--builtins/pushd.def796
-rw-r--r--builtins/read.def1273
-rw-r--r--builtins/reserved.def288
-rw-r--r--builtins/return.def71
-rw-r--r--builtins/set.def1028
-rw-r--r--builtins/setattr.def672
-rw-r--r--builtins/shift.def90
-rw-r--r--builtins/shopt.def966
-rw-r--r--builtins/source.def200
-rw-r--r--builtins/suspend.def129
-rw-r--r--builtins/test.def159
-rw-r--r--builtins/times.def119
-rw-r--r--builtins/trap.def316
-rw-r--r--builtins/type.def420
-rw-r--r--builtins/ulimit.def809
-rw-r--r--builtins/umask.def317
-rw-r--r--builtins/wait.def381
57 files changed, 24385 insertions, 0 deletions
diff --git a/builtins.h b/builtins.h
new file mode 100644
index 0000000..0156593
--- /dev/null
+++ b/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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BUILTINS_H
+#define BUILTINS_H
+
+#include "config.h"
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#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/builtins/Makefile.in b/builtins/Makefile.in
new file mode 100644
index 0000000..642878c
--- /dev/null
+++ b/builtins/Makefile.in
@@ -0,0 +1,723 @@
+# This Makefile for building libbuiltins.a is in -*- text -*- for Emacs.
+#
+# Copyright (C) 1996-2022 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+PACKAGE = @PACKAGE_NAME@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+
+#
+SHELL = @MAKE_SHELL@
+RANLIB = @RANLIB@
+CC = @CC@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+AR = @AR@
+ARFLAGS = @ARFLAGS@
+RM = rm -f
+CP = cp
+
+EXEEXT = @EXEEXT@
+
+prefix = @prefix@
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+topdir = @top_srcdir@
+
+datarootdir = @datarootdir@
+
+includedir = @includedir@
+datadir = @datadir@
+localedir = @localedir@
+
+loadablesdir = @loadablesdir@
+
+# Support an alternate destination root directory for package building
+DESTDIR =
+
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+BUILD_DIR = @BUILD_DIR@
+
+LIBBUILD = ${BUILD_DIR}/lib
+
+PROFILE_FLAGS = @PROFILE_FLAGS@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@ @CROSS_COMPILE@
+STYLE_CFLAGS = @STYLE_CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+LOCAL_CFLAGS = @LOCAL_CFLAGS@ ${DEBUG}
+DEFS = @DEFS@
+LOCAL_DEFS = @LOCAL_DEFS@
+
+LIBS = @LIBS@
+LDFLAGS = @LDFLAGS@ $(LOCAL_LDFLAGS) $(CFLAGS)
+LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@ $(LOCAL_LDFLAGS) $(CFLAGS_FOR_BUILD)
+LOCAL_LDFLAGS = @LOCAL_LDFLAGS@
+LIBS_FOR_BUILD = @LIBS_FOR_BUILD@
+#LIBS_FOR_BUILD = $(LIBS)
+
+BASHINCDIR = ${topdir}/include
+
+RL_INCLUDEDIR = @RL_INCLUDEDIR@
+
+INTL_LIBSRC = ${topdir}/lib/intl
+INTL_BUILDDIR = ${LIBBUILD}/intl
+INTL_LIBDIR = ${INTL_BUILDDIR}
+INTL_LIBRARY = ${INTL_BUILDDIR}/libintl.a
+INTL_INC = @INTL_INC@
+INTL_DEP = @INTL_DEP@
+LIBINTL_H = @LIBINTL_H@
+
+HELPDIR = @HELPDIR@
+MKDIRS = ${topdir}/support/mkdirs
+
+HELPFILES_TARGET = @HELPFILES_TARGET@
+
+INCLUDES = -I. -I.. @RL_INCLUDE@ -I$(topdir) -I$(BASHINCDIR) -I$(topdir)/lib -I$(srcdir) ${INTL_INC}
+
+BASE_CCFLAGS = ${PROFILE_FLAGS} $(DEFS) $(LOCAL_DEFS) $(SYSTEM_FLAGS) \
+ ${INCLUDES} $(STYLE_CFLAGS) $(LOCAL_CFLAGS)
+
+CCFLAGS = ${ADDON_CFLAGS} $(BASE_CCFLAGS) $(CPPFLAGS) $(CFLAGS)
+
+CCFLAGS_FOR_BUILD = $(BASE_CCFLAGS) $(CPPFLAGS_FOR_BUILD) $(CFLAGS_FOR_BUILD)
+
+GCC_LINT_FLAGS = -Wall -Wshadow -Wpointer-arith -Wcast-qual \
+ -Wcast-align -Wstrict-prototypes -Wconversion \
+ -Wmissing-prototypes -Wtraditional -Wredundant-decls -pedantic
+
+MKBUILTINS = mkbuiltins$(EXEEXT)
+DIRECTDEFINE = -D $(srcdir)
+HELPDIRDEFINE = @HELPDIRDEFINE@
+HELPSTRINGS = @HELPSTRINGS@
+
+# xxx this is bad style
+RL_LIBSRC = $(topdir)/lib/readline
+
+.SUFFIXES:
+.SUFFIXES: .def .c .o
+# How to make a .o file from a .def file.
+.def.o:
+ $(RM) $@
+ ./$(MKBUILTINS) $(DIRECTDEFINE) $<
+ $(CC) -c $(CCFLAGS) $*.c || ( $(RM) $*.c ; exit 1 )
+ $(RM) $*.c
+
+# How to make a .c file from a .def file.
+.def.c:
+ $(RM) $@
+ ./$(MKBUILTINS) $(DIRECTDEFINE) $<
+
+# default rule for making a .o file from a .c file
+.c.o:
+ $(RM) $@
+ $(CC) -c $(CCFLAGS) $<
+
+DEFSRC = $(srcdir)/alias.def $(srcdir)/bind.def $(srcdir)/break.def \
+ $(srcdir)/builtin.def $(srcdir)/caller.def \
+ $(srcdir)/cd.def $(srcdir)/colon.def \
+ $(srcdir)/command.def $(srcdir)/declare.def $(srcdir)/echo.def \
+ $(srcdir)/enable.def $(srcdir)/eval.def $(srcdir)/getopts.def \
+ $(srcdir)/exec.def $(srcdir)/exit.def $(srcdir)/fc.def \
+ $(srcdir)/fg_bg.def $(srcdir)/hash.def $(srcdir)/help.def \
+ $(srcdir)/history.def $(srcdir)/jobs.def $(srcdir)/kill.def \
+ $(srcdir)/let.def $(srcdir)/read.def $(srcdir)/return.def \
+ $(srcdir)/set.def $(srcdir)/setattr.def $(srcdir)/shift.def \
+ $(srcdir)/source.def $(srcdir)/suspend.def $(srcdir)/test.def \
+ $(srcdir)/times.def $(srcdir)/trap.def $(srcdir)/type.def \
+ $(srcdir)/ulimit.def $(srcdir)/umask.def $(srcdir)/wait.def \
+ $(srcdir)/reserved.def $(srcdir)/pushd.def $(srcdir)/shopt.def \
+ $(srcdir)/printf.def $(srcdir)/complete.def $(srcdir)/mapfile.def
+
+STATIC_SOURCE = common.c evalstring.c evalfile.c getopt.c bashgetopt.c \
+ getopt.h
+
+OFILES = builtins.o \
+ alias.o bind.o break.o builtin.o caller.o cd.o colon.o command.o \
+ common.o declare.o echo.o enable.o eval.o evalfile.o \
+ evalstring.o exec.o exit.o fc.o fg_bg.o hash.o help.o history.o \
+ jobs.o kill.o let.o mapfile.o \
+ pushd.o read.o return.o set.o setattr.o shift.o source.o \
+ suspend.o test.o times.o trap.o type.o ulimit.o umask.o \
+ wait.o getopts.o shopt.o printf.o getopt.o bashgetopt.o complete.o
+
+CREATED_FILES = builtext.h builtins.c psize.aux pipesize.h tmpbuiltins.c \
+ tmpbuiltins.h
+CREATED_OBJECTS = tmpbuiltins.o gen-helpfiles.o mkbuiltins.o
+
+all: $(MKBUILTINS) libbuiltins.a $(HELPFILES_TARGET)
+targets: libbuiltins.a $(HELPFILES_TARGET)
+
+libbuiltins.a: $(MKBUILTINS) $(OFILES) builtins.o
+ $(RM) $@
+ $(AR) $(ARFLAGS) $@ $(OFILES)
+ -$(RANLIB) $@
+
+tmpbuiltins.c: $(MKBUILTINS) $(DEFSRC)
+ ./$(MKBUILTINS) -externfile tmpbuiltins.h -structfile $@ \
+ -noproduction -nofunctions \
+ $(DIRECTDEFINE) $(HELPSTRINGS) $(DEFSRC)
+
+tmpbuiltins.h: tmpbuiltins.c
+
+gen-helpfiles.o: ../config.h
+gen-helpfiles.o: gen-helpfiles.c
+ $(RM) $@
+ $(CC_FOR_BUILD) -c $(CCFLAGS_FOR_BUILD) $<
+
+gen-helpfiles: tmpbuiltins.o gen-helpfiles.o
+ $(CC_FOR_BUILD) $(LDFLAGS_FOR_BUILD) -o $@ gen-helpfiles.o tmpbuiltins.o $(LIBS_FOR_BUILD)
+
+builtext.h builtins.c: $(MKBUILTINS) $(DEFSRC)
+ @RECPID=$$$$ ; \
+ NEW_BUILTEXT=builtext-$$RECPID.h NEW_BUILTINS=builtins-$$RECPID.c ; \
+ ./$(MKBUILTINS) -externfile $$NEW_BUILTEXT \
+ -includefile builtext.h \
+ -structfile $$NEW_BUILTINS \
+ -noproduction $(DIRECTDEFINE) $(HELPDIRDEFINE) $(HELPSTRINGS) $(DEFSRC) ; \
+ if cmp -s $$NEW_BUILTEXT builtext.h 2>/dev/null; then \
+ $(RM) $$NEW_BUILTEXT; \
+ else \
+ mv $$NEW_BUILTEXT builtext.h; \
+ fi ; \
+ if cmp -s $$NEW_BUILTINS builtins.c 2>/dev/null; then \
+ $(RM) $$NEW_BUILTINS ; \
+ else \
+ mv $$NEW_BUILTINS builtins.c; \
+ fi
+
+helpdoc: gen-helpfiles
+ ./gen-helpfiles ${HELPDIRDEFINE}
+
+install-help: $(HELPFILES_TARGET)
+ @-if test -n "${HELPDIR}" && test -d helpfiles ; then \
+ test -d $(DESTDIR)${HELPDIR} || ${SHELL} ${MKDIRS} $(DESTDIR)$(HELPDIR) ;\
+ ( for f in helpfiles/*; do \
+ echo installing $$f; \
+ ${INSTALL_DATA} $$f $(DESTDIR)$(HELPDIR); \
+ done; ) ; \
+ fi
+
+install: @HELPINSTALL@
+
+mkbuiltins.o: ../config.h
+mkbuiltins.o: mkbuiltins.c
+ $(RM) $@
+ $(CC_FOR_BUILD) -c $(CCFLAGS_FOR_BUILD) $<
+
+mkbuiltins$(EXEEXT): mkbuiltins.o
+ $(CC_FOR_BUILD) $(LDFLAGS_FOR_BUILD) -o $(MKBUILTINS) mkbuiltins.o $(LIBS_FOR_BUILD)
+
+# rules for deficient makes, like SunOS
+mkbuiltins.o: mkbuiltins.c
+builtins.o: builtins.c
+common.o: common.c
+bashgetopt.o: bashgetopt.c
+getopt.o: getopt.c
+evalstring.o: evalstring.c
+evalfile.o: evalfile.c
+
+tmpbuiltins.o: tmpbuiltins.c
+gen-helpfiles.o: gen-helpfiles.c
+
+ulimit.o: pipesize.h
+
+pipesize.h: psize.aux
+ $(SHELL) $(srcdir)/psize.sh > $@
+
+# Technically this is wrong; the pipe size should be for the target system,
+# not the build host.
+psize.aux: psize.c
+ $(CC_FOR_BUILD) $(CCFLAGS_FOR_BUILD) ${LDFLAGS_FOR_BUILD} -o $@ $(srcdir)/psize.c
+
+documentation: builtins.texi
+
+builtins.texi: $(MKBUILTINS)
+ ./$(MKBUILTINS) -documentonly $(DEFSRC)
+
+clean:
+ $(RM) $(OFILES) $(CREATED_FILES) libbuiltins.a
+ $(RM) $(MKBUILTINS) gen-helpfiles $(CREATED_OBJECTS)
+ -test -d helpfiles && $(RM) -r helpfiles
+
+mostlyclean:
+ $(RM) $(OFILES) libbuiltins.a
+
+distclean maintainer-clean: clean
+ $(RM) Makefile
+
+$(OFILES): $(MKBUILTINS) ../config.h
+
+../version.h: ../config.h ../Makefile Makefile
+ -( cd ${BUILD_DIR} && ${MAKE} ${MFLAGS} version.h )
+
+# maintainer special - for now
+po: builtins.c
+ xgettext -L C -o $(topdir)/po/builtins.pot --keyword='N_' builtins.c 2>/dev/null
+
+${LIBINTL_H}:
+ @echo making $@ in ${INTL_BUILDDIR}
+ @(cd ${INTL_BUILDDIR} && \
+ $(MAKE) $(MFLAGS) libintl.h) || exit 1
+
+# dependencies
+
+alias.o: alias.def
+bind.o: bind.def
+break.o: break.def
+builtin.o: builtin.def
+caller.o: caller.def
+cd.o: cd.def
+colon.o: colon.def
+command.o: command.def
+declare.o: declare.def
+echo.o: echo.def
+enable.o: enable.def
+eval.o: eval.def
+exec.o: exec.def
+exit.o: exit.def
+fc.o: fc.def
+fg_bg.o: fg_bg.def
+hash.o: hash.def
+help.o: help.def
+history.o: history.def
+jobs.o: jobs.def
+kill.o: kill.def
+let.o: let.def
+mapfile.o: mapfile.def
+printf.o: printf.def
+pushd.o: pushd.def
+read.o: read.def
+return.o: return.def
+set.o: set.def
+setattr.o: setattr.def
+shift.o: shift.def
+shopt.o: shopt.def
+source.o: source.def
+suspend.o: suspend.def
+test.o: test.def
+times.o: times.def
+trap.o: trap.def
+type.o: type.def
+ulimit.o: ulimit.def
+umask.o: umask.def
+wait.o: wait.def
+getopts.o: getopts.def
+reserved.o: reserved.def
+complete.o: complete.def
+
+# C files
+bashgetopt.o: ../config.h $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+bashgetopt.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/bashjmp.h
+bashgetopt.o: $(topdir)/command.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/error.h
+bashgetopt.o: $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/quit.h $(BASHINCDIR)/maxpath.h
+bashgetopt.o: $(topdir)/unwind_prot.h $(topdir)/dispose_cmd.h
+bashgetopt.o: $(topdir)/make_cmd.h $(topdir)/subst.h $(topdir)/sig.h
+bashgetopt.o: ../pathnames.h $(topdir)/externs.h $(srcdir)/common.h
+bashgetopt.o: $(BASHINCDIR)/chartypes.h
+common.o: $(topdir)/bashtypes.h $(BASHINCDIR)/posixstat.h $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+common.o: $(topdir)/shell.h $(topdir)/syntax.h ../config.h $(topdir)/bashjmp.h $(BASHINCDIR)/posixjmp.h
+common.o: $(topdir)/sig.h $(topdir)/command.h $(topdir)/parser.h
+common.o: $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/stdc.h $(BASHINCDIR)/memalloc.h
+common.o: $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/input.h
+common.o: $(topdir)/siglist.h $(topdir)/bashhist.h $(topdir)/quit.h
+common.o: $(topdir)/unwind_prot.h $(BASHINCDIR)/maxpath.h $(topdir)/jobs.h
+common.o: $(topdir)/builtins.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+common.o: $(topdir)/subst.h $(topdir)/execute_cmd.h $(topdir)/error.h
+common.o: $(topdir)/externs.h ../pathnames.h ./builtext.h
+common.o: $(BASHINCDIR)/chartypes.h
+evalfile.o: $(topdir)/bashtypes.h $(BASHINCDIR)/posixstat.h ${BASHINCDIR}/filecntl.h
+evalfile.o: $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+evalfile.o: $(topdir)/shell.h $(topdir)/syntax.h ../config.h $(topdir)/bashjmp.h
+evalfile.o: $(topdir)/command.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/error.h
+evalfile.o: $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/quit.h $(BASHINCDIR)/maxpath.h
+evalfile.o: $(topdir)/unwind_prot.h $(topdir)/dispose_cmd.h
+evalfile.o: $(topdir)/make_cmd.h $(topdir)/subst.h $(topdir)/sig.h
+evalfile.o: ../pathnames.h $(topdir)/externs.h $(topdir)/parser.h
+evalfile.o: $(topdir)/jobs.h $(topdir)/builtins.h $(topdir)/flags.h
+evalfile.o: $(topdir)/input.h $(topdir)/execute_cmd.h
+evalfile.o: $(topdir)/bashhist.h $(srcdir)/common.h
+evalstring.o: ../config.h $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+evalstring.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/bashjmp.h $(BASHINCDIR)/posixjmp.h
+evalstring.o: $(topdir)/sig.h $(topdir)/command.h $(topdir)/siglist.h
+evalstring.o: $(BASHINCDIR)/memalloc.h $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/input.h
+evalstring.o: $(topdir)/quit.h $(topdir)/unwind_prot.h
+evalstring.o: $(BASHINCDIR)/maxpath.h $(topdir)/jobs.h $(topdir)/builtins.h
+evalstring.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+evalstring.o: $(topdir)/externs.h $(topdir)/jobs.h $(topdir)/builtins.h
+evalstring.o: $(topdir)/flags.h $(topdir)/input.h $(topdir)/execute_cmd.h
+evalstring.o: $(topdir)/bashhist.h $(srcdir)/common.h
+evalstring.o: $(topdir)/trap.h $(topdir)/redir.h ../pathnames.h ./builtext.h
+#evalstring.o: $(topdir)/y.tab.h
+getopt.o: ../config.h $(BASHINCDIR)/memalloc.h
+getopt.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/bashjmp.h $(topdir)/command.h
+getopt.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/error.h $(topdir)/variables.h $(topdir)/conftypes.h
+getopt.o: $(topdir)/quit.h $(BASHINCDIR)/maxpath.h $(topdir)/unwind_prot.h
+getopt.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+getopt.o: $(topdir)/sig.h ../pathnames.h $(topdir)/externs.h
+getopt.o: $(srcdir)/getopt.h
+mkbuiltins.o: ../config.h $(topdir)/bashtypes.h $(BASHINCDIR)/posixstat.h
+mkbuiltins.o: ${BASHINCDIR}/filecntl.h
+mkbuiltins.o: $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h
+
+# def files
+alias.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+alias.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/maxpath.h
+alias.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+alias.o: $(topdir)/subst.h $(topdir)/externs.h $(srcdir)/common.h
+alias.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+alias.o: ../pathnames.h
+bind.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+bind.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+bind.o: $(topdir)/subst.h $(topdir)/externs.h $(srcdir)/bashgetopt.h
+bind.o: $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/maxpath.h $(topdir)/bashline.h
+bind.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+break.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+break.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+break.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+break.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+break.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+break.o: ../pathnames.h $(topdir)/execute_cmd.h
+builtin.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+builtin.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/externs.h
+builtin.o: $(topdir)/quit.h $(srcdir)/common.h $(BASHINCDIR)/maxpath.h $(topdir)/sig.h
+builtin.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+builtin.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+builtin.o: $(srcdir)/bashgetopt.h ../pathnames.h $(topdir)/execute_cmd.h
+caller.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+caller.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/quit.h $(topdir)/dispose_cmd.h
+caller.o: $(topdir)/make_cmd.h $(topdir)/subst.h $(topdir)/externs.h $(topdir)/sig.h
+caller.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+caller.o: $(srcdir)/common.h $(BASHINCDIR)/maxpath.h ./builtext.h
+caller.o: ${BASHINCDIR}/chartypes.h $(topdir)/bashtypes.h ../pathnames.h
+cd.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+cd.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/quit.h $(topdir)/dispose_cmd.h
+cd.o: $(topdir)/make_cmd.h $(topdir)/subst.h $(topdir)/externs.h
+cd.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+cd.o: $(srcdir)/common.h $(BASHINCDIR)/maxpath.h ../pathnames.h
+cd.o: $(topdir)/sig.h
+colon.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+colon.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/subst.h $(topdir)/externs.h
+colon.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+colon.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+colon.o: $(BASHINCDIR)/maxpath.h ../pathnames.h
+colon.o: $(srcdir)/common.h
+command.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+command.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/externs.h
+command.o: $(topdir)/quit.h $(srcdir)/bashgetopt.h $(BASHINCDIR)/maxpath.h
+command.o: $(topdir)/sig.h
+command.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+command.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+command.o: ../pathnames.h
+declare.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+declare.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+declare.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+declare.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+declare.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+declare.o: $(topdir)/arrayfunc.h $(srcdir)/bashgetopt.h $(topdir)/flags.h
+declare.o: ./builtext.h ../pathnames.h
+echo.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+echo.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/subst.h $(topdir)/externs.h
+echo.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+echo.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+echo.o: $(BASHINCDIR)/maxpath.h ../pathnames.h
+echo.o: $(srcdir)/common.h
+enable.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+enable.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+enable.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+enable.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/sig.h
+enable.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+enable.o: $(BASHINCDIR)/maxpath.h ../pathnames.h
+enable.o: $(topdir)/pcomplete.h
+eval.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+eval.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+eval.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+eval.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/sig.h
+eval.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+eval.o: $(BASHINCDIR)/maxpath.h ../pathnames.h
+exec.o: $(topdir)/bashtypes.h
+exec.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+exec.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+exec.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+exec.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/flags.h
+exec.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+exec.o: $(srcdir)/common.h $(topdir)/execute_cmd.h $(BASHINCDIR)/maxpath.h
+exec.o: $(topdir)/findcmd.h $(topdir)/jobs.h ../pathnames.h
+exit.o: $(topdir)/bashtypes.h
+exit.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+exit.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+exit.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+exit.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/jobs.h
+exit.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+exit.o: $(topdir)/execute_cmd.h
+exit.o: $(BASHINCDIR)/maxpath.h ./builtext.h ../pathnames.h
+fc.o: $(topdir)/bashtypes.h $(BASHINCDIR)/posixstat.h
+fc.o: $(topdir)/builtins.h $(topdir)/command.h $(srcdir)/bashgetopt.h
+fc.o: $(topdir)/bashhist.h $(topdir)/parser.h
+fc.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+fc.o: $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/maxpath.h
+fc.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+fc.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/shell.h $(topdir)/syntax.h
+fc.o: $(topdir)/flags.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+fc.o: $(topdir)/bashansi.h $(BASHINCDIR)/ansi_stdlib.h $(BASHINCDIR)/chartypes.h
+fc.o: ../pathnames.h
+fg_bg.o: $(topdir)/bashtypes.h $(srcdir)/bashgetopt.h
+fg_bg.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+fg_bg.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+fg_bg.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+fg_bg.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+fg_bg.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+fg_bg.o: $(topdir)/execute_cmd.h
+fg_bg.o: $(topdir)/jobs.h ../pathnames.h
+getopts.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+getopts.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+getopts.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+getopts.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+getopts.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+getopts.o: $(topdir)/execute_cmd.h
+getopts.o: ../pathnames.h
+hash.o: $(topdir)/builtins.h $(topdir)/command.h $(topdir)/quit.h
+hash.o: $(topdir)/findcmd.h $(topdir)/hashlib.h $(topdir)/sig.h
+hash.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+hash.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+hash.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h
+hash.o: $(topdir)/conftypes.h $(topdir)/execute_cmd.h
+hash.o: $(srcdir)/common.h $(BASHINCDIR)/maxpath.h ../pathnames.h
+help.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+help.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+help.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+help.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+help.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h
+help.o: $(topdir)/conftypes.h $(topdir)/execute_cmd.h
+help.o: ${srcdir}/common.h $(topdir)/sig.h ../pathnames.h
+history.o: $(topdir)/bashtypes.h
+history.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+history.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+history.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+history.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/sig.h $(topdir)/parser.h
+history.o: ${BASHINCDIR}/filecntl.h $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h
+history.o: $(topdir)/variables.h $(topdir)/conftypes.h $(topdir)/bashhist.h $(BASHINCDIR)/maxpath.h
+history.o: ../pathnames.h
+inlib.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+inlib.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+inlib.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+inlib.o: $(BASHINCDIR)/maxpath.h $(topdir)/subst.h $(topdir)/externs.h
+inlib.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h ../pathnames.h
+jobs.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+jobs.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/quit.h $(srcdir)/bashgetopt.h
+jobs.o: $(BASHINCDIR)/maxpath.h $(topdir)/externs.h $(topdir)/jobs.h
+jobs.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+jobs.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+jobs.o: $(topdir)/sig.h ../pathnames.h
+kill.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/error.h
+kill.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/subst.h $(topdir)/externs.h
+kill.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+kill.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/trap.h $(topdir)/unwind_prot.h
+kill.o: $(topdir)/variables.h $(topdir)/conftypes.h $(BASHINCDIR)/maxpath.h
+kill.o: $(topdir)/jobs.h ../pathnames.h
+let.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+let.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+let.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+let.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+let.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+let.o: ../pathnames.h
+printf.o: ../config.h $(BASHINCDIR)/memalloc.h $(topdir)/bashjmp.h
+printf.o: $(topdir)/command.h $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+printf.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+printf.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/sig.h
+printf.o: ../pathnames.h $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h
+printf.o: $(topdir)/variables.h $(topdir)/conftypes.h $(BASHINCDIR)/stdc.h $(srcdir)/bashgetopt.h
+printf.o: $(topdir)/bashtypes.h ${srcdir}/common.h $(BASHINCDIR)/chartypes.h
+printf.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
+printf.o: ../pathnames.h
+pushd.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+pushd.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+pushd.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+pushd.o: $(topdir)/subst.h $(topdir)/externs.h $(topdir)/sig.h
+pushd.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+pushd.o: $(BASHINCDIR)/maxpath.h $(srcdir)/common.h ./builtext.h
+pushd.o: ../pathnames.h
+read.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+read.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+read.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+read.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+read.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+read.o: $(BASHINCDIR)/shtty.h $(topdir)/sig.h
+read.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
+read.o: $(topdir)/arrayfunc.h ../pathnames.h
+return.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+return.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+return.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+return.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+return.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h
+return.o: $(topdir)/conftypes.h $(topdir)/execute_cmd.h
+return.o: ../pathnames.h
+set.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+set.o: $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/subst.h $(topdir)/externs.h
+set.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+set.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+set.o: $(BASHINCDIR)/maxpath.h $(topdir)/error.h $(topdir)/sig.h
+set.o: $(topdir)/arrayfunc.h ../pathnames.h $(topdir)/parser.h
+setattr.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+setattr.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(BASHINCDIR)/maxpath.h
+setattr.o: $(topdir)/quit.h $(srcdir)/common.h $(srcdir)/bashgetopt.h
+setattr.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+setattr.o: $(topdir)/externs.h $(topdir)/flags.h $(topdir)/sig.h
+setattr.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h
+setattr.o: $(topdir)/conftypes.h $(topdir)/execute_cmd.h
+setattr.o: $(topdir)/arrayfunc.h ../pathnames.h
+shift.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+shift.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+shift.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+shift.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+shift.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+shift.o: ../pathnames.h
+shopt.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+shopt.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+shopt.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+shopt.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+shopt.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+shopt.o: $(srcdir)/common.h $(srcdir)/bashgetopt.h ../pathnames.h
+shopt.o: $(topdir)/bashhist.h $(topdir)/bashline.h $(topdir)/sig.h
+source.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+source.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/findcmd.h
+source.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+source.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+source.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+source.o: $(srcdir)/bashgetopt.h $(topdir)/flags.h $(topdir)/trap.h
+source.o: $(topdir)/execute_cmd.h
+source.o: ../pathnames.h
+suspend.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+suspend.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+suspend.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+suspend.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+suspend.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+suspend.o: $(topdir)/jobs.h ../pathnames.h
+test.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+test.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+test.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+test.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+test.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+test.o: $(topdir)/execute_cmd.h $(topdir)/test.h ../pathnames.h
+times.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+times.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+times.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+times.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+times.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+times.o: $(BASHINCDIR)/posixtime.h ../pathnames.h
+trap.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+trap.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h $(topdir)/externs.h
+trap.o: $(topdir)/quit.h $(srcdir)/common.h $(BASHINCDIR)/maxpath.h $(topdir)/sig.h
+trap.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+trap.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+trap.o: $(topdir)/findcmd.h ../pathnames.h
+type.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+type.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+type.o: $(topdir)/quit.h $(srcdir)/common.h $(BASHINCDIR)/maxpath.h $(topdir)/sig.h
+type.o: $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/subst.h
+type.o: $(topdir)/execute_cmd.h $(topdir)/parser.h
+type.o: $(topdir)/externs.h $(topdir)/hashcmd.h ../pathnames.h
+type.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+ulimit.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+ulimit.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+ulimit.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+ulimit.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+ulimit.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+ulimit.o: ../pathnames.h
+umask.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+umask.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+umask.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+umask.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+umask.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+umask.o: $(BASHINCDIR)/chartypes.h ../pathnames.h
+wait.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+wait.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+wait.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h
+wait.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+wait.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/unwind_prot.h $(topdir)/variables.h $(topdir)/conftypes.h
+wait.o: $(topdir)/jobs.h $(topdir)/sig.h $(topdir)/execute_cmd.h
+wait.o: $(BASHINCDIR)/chartypes.h ../pathnames.h
+
+complete.o: ../config.h ../pathnames.h
+complete.o: ${topdir}/shell.h $(topdir)/syntax.h ${topdir}/bashjmp.h ${BASHINCDIR}/posixjmp.h ${topdir}/sig.h
+complete.o: ${topdir}/unwind_prot.h ${topdir}/variables.h
+complete.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+complete.o: ${topdir}/bashtypes.h ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
+complete.o: ${topdir}/builtins.h ${topdir}/general.h
+complete.o: ${topdir}/bashtypes.h ${BASHINCDIR}/chartypes.h ${topdir}/xmalloc.h
+complete.o: ${topdir}/pcomplete.h
+complete.o: ${srcdir}/common.h ${srcdir}/bashgetopt.h
+mapfile.o: $(topdir)/command.h ../config.h $(BASHINCDIR)/memalloc.h
+mapfile.o: $(topdir)/error.h $(topdir)/general.h $(topdir)/xmalloc.h
+mapfile.o: $(topdir)/quit.h $(topdir)/dispose_cmd.h $(topdir)/make_cmd.h $(topdir)/sig.h
+mapfile.o: $(topdir)/subst.h $(topdir)/externs.h $(BASHINCDIR)/maxpath.h
+mapfile.o: $(topdir)/shell.h $(topdir)/syntax.h $(topdir)/variables.h $(topdir)/conftypes.h
+mapfile.o: $(topdir)/arrayfunc.h ../pathnames.h
+
+#bind.o: $(RL_LIBSRC)chardefs.h $(RL_LIBSRC)readline.h $(RL_LIBSRC)keymaps.h
+
+# libintl dependencies
+bind.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+break.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+caller.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+cd.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+common.c: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+complete.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+declare.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+enable.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+evalfile.c: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+exec.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+exit.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+fc.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+fg_bg.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+getopt.c: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+hash.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+help.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+history.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+inlib.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+jobs.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+kill.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+let.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+mapfile.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+mkbuiltins.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+printf.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+pushd.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+read.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+return.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+set.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+setattr.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+shift.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+shopt.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+source.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+suspend.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+type.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+ulimit.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+umask.o: ${topdir}/bashintl.h ${LIBINTL_H} $(BASHINCDIR)/gettext.h
+
+cd.o: $(topdir)/config-top.h
+command.o: $(topdir)/config-top.h
+common.o: $(topdir)/config-top.h
+declare.o: $(topdir)/config-top.h
+break.o: $(topdir)/config-top.h
+echo.o: $(topdir)/config-top.h
+evalstring.o: $(topdir)/config-top.h
+exit.o: $(topdir)/config-top.h
+kill.o: $(topdir)/config-top.h
+shopt.o: $(topdir)/config-top.h
diff --git a/builtins/alias.def b/builtins/alias.def
new file mode 100644
index 0000000..0ab9069
--- /dev/null
+++ b/builtins/alias.def
@@ -0,0 +1,241 @@
+This file is alias.def, from which is created alias.c
+It implements the builtins "alias" and "unalias" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$BUILTIN alias
+$FUNCTION alias_builtin
+$DEPENDS_ON ALIAS
+$PRODUCES alias.c
+$SHORT_DOC alias [-p] [name[=value] ... ]
+Define or display aliases.
+
+Without arguments, `alias' prints the list of aliases in the reusable
+form `alias NAME=VALUE' on standard output.
+
+Otherwise, an alias is defined for each NAME whose VALUE is given.
+A trailing space in VALUE causes the next word to be checked for
+alias substitution when the alias is expanded.
+
+Options:
+ -p print all defined aliases in a reusable format
+
+Exit Status:
+alias returns true unless a NAME is supplied for which no alias has been
+defined.
+$END
+
+#include <config.h>
+
+#if defined (ALIAS)
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+# include "../bashansi.h"
+# include "../bashintl.h"
+
+# include <stdio.h>
+# 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 */
+
+$BUILTIN unalias
+$FUNCTION unalias_builtin
+$DEPENDS_ON ALIAS
+$SHORT_DOC unalias [-a] name [name ...]
+Remove each NAME from the list of defined aliases.
+
+Options:
+ -a remove all alias definitions
+
+Return success unless a NAME is not an existing alias.
+$END
+
+#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/builtins/bashgetopt.c b/builtins/bashgetopt.c
new file mode 100644
index 0000000..d40ffa2
--- /dev/null
+++ b/builtins/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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "../bashansi.h"
+#include <chartypes.h>
+#include <errno.h>
+
+#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/builtins/bashgetopt.h b/builtins/bashgetopt.h
new file mode 100644
index 0000000..79be343
--- /dev/null
+++ b/builtins/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 <http://www.gnu.org/licenses/>.
+*/
+
+/* 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/builtins/bind.def b/builtins/bind.def
new file mode 100644
index 0000000..53a65ac
--- /dev/null
+++ b/builtins/bind.def
@@ -0,0 +1,405 @@
+This file is bind.def, from which is created bind.c.
+It implements the builtin "bind" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES bind.c
+
+#include <config.h>
+
+$BUILTIN bind
+$DEPENDS_ON READLINE
+$FUNCTION bind_builtin
+$SHORT_DOC bind [-lpsvPSVX] [-m keymap] [-f filename] [-q name] [-u name] [-r keyseq] [-x keyseq:shell-command] [keyseq:readline-function or readline-command]
+Set Readline key bindings and variables.
+
+Bind a key sequence to a Readline function or a macro, or set a
+Readline variable. The non-option argument syntax is equivalent to
+that found in ~/.inputrc, but must be passed as a single argument:
+e.g., bind '"\C-x\C-r": re-read-init-file'.
+
+Options:
+ -m keymap Use KEYMAP as the keymap for the duration of this
+ command. Acceptable keymap names are emacs,
+ emacs-standard, emacs-meta, emacs-ctlx, vi, vi-move,
+ vi-command, and vi-insert.
+ -l List names of functions.
+ -P List function names and bindings.
+ -p List functions and bindings in a form that can be
+ reused as input.
+ -S List key sequences that invoke macros and their values
+ -s List key sequences that invoke macros and their values
+ in a form that can be reused as input.
+ -V List variable names and values
+ -v List variable names and values in a form that can
+ be reused as input.
+ -q function-name Query about which keys invoke the named function.
+ -u function-name Unbind all keys which are bound to the named function.
+ -r keyseq Remove the binding for KEYSEQ.
+ -f filename Read key bindings from FILENAME.
+ -x keyseq:shell-command Cause SHELL-COMMAND to be executed when
+ KEYSEQ is entered.
+ -X List key sequences bound with -x and associated commands
+ in a form that can be reused as input.
+
+Exit Status:
+bind returns 0 unless an unrecognized option is given or an error occurs.
+$END
+
+#if defined (READLINE)
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+#include <readline/readline.h>
+#include <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/builtins/break.def b/builtins/break.def
new file mode 100644
index 0000000..b73ed5e
--- /dev/null
+++ b/builtins/break.def
@@ -0,0 +1,143 @@
+This file is break.def, from which is created break.c.
+It implements the builtins "break" and "continue" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES break.c
+
+$BUILTIN break
+$FUNCTION break_builtin
+$SHORT_DOC break [n]
+Exit for, while, or until loops.
+
+Exit a FOR, WHILE or UNTIL loop. If N is specified, break N enclosing
+loops.
+
+Exit Status:
+The exit status is 0 unless N is not greater than or equal to 1.
+$END
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#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);
+}
+
+$BUILTIN continue
+$FUNCTION continue_builtin
+$SHORT_DOC continue [n]
+Resume for, while, or until loops.
+
+Resumes the next iteration of the enclosing FOR, WHILE or UNTIL loop.
+If N is specified, resumes the Nth enclosing loop.
+
+Exit Status:
+The exit status is 0 unless N is not greater than or equal to 1.
+$END
+
+/* 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/builtins/builtin.def b/builtins/builtin.def
new file mode 100644
index 0000000..74060ee
--- /dev/null
+++ b/builtins/builtin.def
@@ -0,0 +1,85 @@
+This file is builtin.def, from which is created builtin.c.
+It implements the builtin "builtin" in Bash.
+
+Copyright (C) 1987-2017 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES builtin.c
+
+$BUILTIN builtin
+$FUNCTION builtin_builtin
+$SHORT_DOC builtin [shell-builtin [arg ...]]
+Execute shell builtins.
+
+Execute SHELL-BUILTIN with arguments ARGs without performing command
+lookup. This is useful when you wish to reimplement a shell builtin
+as a shell function, but need to execute the builtin within the function.
+
+Exit Status:
+Returns the exit status of SHELL-BUILTIN, or false if SHELL-BUILTIN is
+not a shell builtin.
+$END
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#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/builtins/caller.def b/builtins/caller.def
new file mode 100644
index 0000000..1000979
--- /dev/null
+++ b/builtins/caller.def
@@ -0,0 +1,156 @@
+This file is caller.def, from which is created caller.c. It implements the
+builtin "caller" in Bash.
+
+Copyright (C) 2002-2008 Rocky Bernstein for Free Software Foundation, Inc.
+Copyright (C) 2008-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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES caller.c
+
+$BUILTIN caller
+$FUNCTION caller_builtin
+$DEPENDS_ON DEBUGGER
+$SHORT_DOC caller [expr]
+Return the context of the current subroutine call.
+
+Without EXPR, returns "$line $filename". With EXPR, returns
+"$line $subroutine $filename"; this extra information can be used to
+provide a stack trace.
+
+The value of EXPR indicates how many call frames to go back before the
+current one; the top frame is frame 0.
+
+Exit Status:
+Returns 0 unless the shell is not executing a shell function or EXPR
+is invalid.
+$END
+
+#include <config.h>
+#include <stdio.h>
+#include "chartypes.h"
+#include "bashtypes.h"
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <errno.h>
+
+#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/builtins/cd.def b/builtins/cd.def
new file mode 100644
index 0000000..b87c5d9
--- /dev/null
+++ b/builtins/cd.def
@@ -0,0 +1,685 @@
+This file is cd.def, from which is created cd.c. It implements the
+builtins "cd" and "pwd" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES cd.c
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include "../bashtypes.h"
+#include "posixdir.h"
+#include "posixstat.h"
+#if defined (HAVE_SYS_PARAM_H)
+#include <sys/param.h>
+#endif
+#include <fcntl.h>
+
+#include <stdio.h>
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include <errno.h>
+#include <tilde/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;
+
+$BUILTIN cd
+$FUNCTION cd_builtin
+$SHORT_DOC cd [-L|[-P [-e]] [-@]] [dir]
+Change the shell working directory.
+
+Change the current directory to DIR. The default DIR is the value of the
+HOME shell variable. If DIR is "-", it is converted to $OLDPWD.
+
+The variable CDPATH defines the search path for the directory containing
+DIR. Alternative directory names in CDPATH are separated by a colon (:).
+A null directory name is the same as the current directory. If DIR begins
+with a slash (/), then CDPATH is not used.
+
+If the directory is not found, and the shell option `cdable_vars' is set,
+the word is assumed to be a variable name. If that variable has a value,
+its value is used for DIR.
+
+Options:
+ -L force symbolic links to be followed: resolve symbolic
+ links in DIR after processing instances of `..'
+ -P use the physical directory structure without following
+ symbolic links: resolve symbolic links in DIR before
+ processing instances of `..'
+ -e if the -P option is supplied, and the current working
+ directory cannot be determined successfully, exit with
+ a non-zero status
+#if defined (O_XATTR)
+ -@ on systems that support it, present a file with extended
+ attributes as a directory containing the file attributes
+#endif
+
+The default is to follow symbolic links, as if `-L' were specified.
+`..' is processed by removing the immediately previous pathname component
+back to a slash or the beginning of DIR.
+
+Exit Status:
+Returns 0 if the directory is changed, and if $PWD is set successfully when
+-P is used; non-zero otherwise.
+$END
+
+/* 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);
+}
+
+$BUILTIN pwd
+$FUNCTION pwd_builtin
+$SHORT_DOC pwd [-LP]
+Print the name of the current working directory.
+
+Options:
+ -L print the value of $PWD if it names the current working
+ directory
+ -P print the physical directory, without any symbolic links
+
+By default, `pwd' behaves as if `-L' were specified.
+
+Exit Status:
+Returns 0 unless an invalid option is given or the current directory
+cannot be read.
+$END
+
+/* 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/builtins/colon.def b/builtins/colon.def
new file mode 100644
index 0000000..6891b07
--- /dev/null
+++ b/builtins/colon.def
@@ -0,0 +1,76 @@
+This file is colon.def, from which is created colon.c.
+It implements the builtin ":" in Bash.
+
+Copyright (C) 1987-2019 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES colon.c
+
+$BUILTIN :
+$DOCNAME colon
+$FUNCTION colon_builtin
+$SHORT_DOC :
+Null command.
+
+No effect; the command does nothing.
+
+Exit Status:
+Always succeeds.
+$END
+
+$BUILTIN true
+$FUNCTION colon_builtin
+$SHORT_DOC true
+Return a successful result.
+
+Exit Status:
+Always succeeds.
+$END
+
+$BUILTIN false
+$FUNCTION false_builtin
+$SHORT_DOC false
+Return an unsuccessful result.
+
+Exit Status:
+Always fails.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#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/builtins/command.def b/builtins/command.def
new file mode 100644
index 0000000..3efdbe6
--- /dev/null
+++ b/builtins/command.def
@@ -0,0 +1,143 @@
+This file is command.def, from which is created command.c.
+It implements the builtin "command" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES command.c
+
+$BUILTIN command
+$FUNCTION command_builtin
+$SHORT_DOC command [-pVv] command [arg ...]
+Execute a simple command or display information about commands.
+
+Runs COMMAND with ARGS suppressing shell function lookup, or display
+information about the specified COMMANDs. Can be used to invoke commands
+on disk when a function with the same name exists.
+
+Options:
+ -p use a default value for PATH that is guaranteed to find all of
+ the standard utilities
+ -v print a description of COMMAND similar to the `type' builtin
+ -V print a more verbose description of each COMMAND
+
+Exit Status:
+Returns exit status of COMMAND, or failure if COMMAND is not found.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#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/builtins/common.c b/builtins/common.c
new file mode 100644
index 0000000..19b00c4
--- /dev/null
+++ b/builtins/common.c
@@ -0,0 +1,1131 @@
+/* common.c - utility functions for all builtins */
+
+/* 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <chartypes.h>
+#include "../bashtypes.h"
+#include "posixstat.h"
+#include <signal.h>
+
+#include <errno.h>
+
+#if defined (PREFER_STDARG)
+# include <stdarg.h>
+#else
+# include <varargs.h>
+#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/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/builtins/common.h b/builtins/common.h
new file mode 100644
index 0000000..a170f8f
--- /dev/null
+++ b/builtins/common.h
@@ -0,0 +1,289 @@
+/* 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 <http://www.gnu.org/licenses/>.
+*/
+
+#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 */
+#define SEVAL_NOOPTIMIZE 0x400 /* don't try to set optimization flags */
+
+/* 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
+
+#if defined (EXTENDED_GLOB)
+extern int extglob_flag;
+#endif
+
+extern int expaliases_flag;
+
+/* 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/builtins/complete.def b/builtins/complete.def
new file mode 100644
index 0000000..6d600ef
--- /dev/null
+++ b/builtins/complete.def
@@ -0,0 +1,892 @@
+This file is complete.def, from which is created complete.c.
+It implements the builtins "complete", "compgen", and "compopt" in Bash.
+
+Copyright (C) 1999-2021 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES complete.c
+
+$BUILTIN complete
+$DEPENDS_ON PROGRAMMABLE_COMPLETION
+$FUNCTION complete_builtin
+$SHORT_DOC complete [-abcdefgjksuv] [-pr] [-DEI] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [name ...]
+Specify how arguments are to be completed by Readline.
+
+For each NAME, specify how arguments are to be completed. If no options
+are supplied, existing completion specifications are printed in a way that
+allows them to be reused as input.
+
+Options:
+ -p print existing completion specifications in a reusable format
+ -r remove a completion specification for each NAME, or, if no
+ NAMEs are supplied, all completion specifications
+ -D apply the completions and actions as the default for commands
+ without any specific completion defined
+ -E apply the completions and actions to "empty" commands --
+ completion attempted on a blank line
+ -I apply the completions and actions to the initial (usually the
+ command) word
+
+When completion is attempted, the actions are applied in the order the
+uppercase-letter options are listed above. If multiple options are supplied,
+the -D option takes precedence over -E, and both take precedence over -I.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
+$END
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include "../bashtypes.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#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 <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));
+}
+
+$BUILTIN compgen
+$DEPENDS_ON PROGRAMMABLE_COMPLETION
+$FUNCTION compgen_builtin
+$SHORT_DOC compgen [-abcdefgjksuv] [-o option] [-A action] [-G globpat] [-W wordlist] [-F function] [-C command] [-X filterpat] [-P prefix] [-S suffix] [word]
+Display possible completions depending on the options.
+
+Intended to be used from within a shell function generating possible
+completions. If the optional WORD argument is supplied, matches against
+WORD are generated.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
+$END
+
+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);
+}
+
+$BUILTIN compopt
+$DEPENDS_ON PROGRAMMABLE_COMPLETION
+$FUNCTION compopt_builtin
+$SHORT_DOC compopt [-o|+o option] [-DEI] [name ...]
+Modify or display completion options.
+
+Modify the completion options for each NAME, or, if no NAMEs are supplied,
+the completion currently being executed. If no OPTIONs are given, print
+the completion options for each NAME or the current completion specification.
+
+Options:
+ -o option Set completion option OPTION for each NAME
+ -D Change options for the "default" command completion
+ -E Change options for the "empty" command completion
+ -I Change options for completion on the initial word
+
+Using `+o' instead of `-o' turns off the specified option.
+
+Arguments:
+
+Each NAME refers to a command for which a completion specification must
+have previously been defined using the `complete' builtin. If no NAMEs
+are supplied, compopt must be called by a function currently generating
+completions, and the options for that currently-executing completion
+generator are modified.
+
+Exit Status:
+Returns success unless an invalid option is supplied or NAME does not
+have a completion specification defined.
+$END
+
+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/builtins/declare.def b/builtins/declare.def
new file mode 100644
index 0000000..54db59c
--- /dev/null
+++ b/builtins/declare.def
@@ -0,0 +1,1048 @@
+This file is declare.def, from which is created declare.c.
+It implements the builtins "declare" and "local" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES declare.c
+
+$BUILTIN declare
+$FUNCTION declare_builtin
+$SHORT_DOC declare [-aAfFgiIlnrtux] [name[=value] ...] or declare -p [-aAfFilnrtux] [name ...]
+Set variable values and attributes.
+
+Declare variables and give them attributes. If no NAMEs are given,
+display the attributes and values of all variables.
+
+Options:
+ -f restrict action or display to function names and definitions
+ -F restrict display to function names only (plus line number and
+ source file when debugging)
+ -g create global variables when used in a shell function; otherwise
+ ignored
+ -I if creating a local variable, inherit the attributes and value
+ of a variable with the same name at a previous scope
+ -p display the attributes and value of each NAME
+
+Options which set attributes:
+ -a to make NAMEs indexed arrays (if supported)
+ -A to make NAMEs associative arrays (if supported)
+ -i to make NAMEs have the `integer' attribute
+ -l to convert the value of each NAME to lower case on assignment
+ -n make NAME a reference to the variable named by its value
+ -r to make NAMEs readonly
+ -t to make NAMEs have the `trace' attribute
+ -u to convert the value of each NAME to upper case on assignment
+ -x to make NAMEs export
+
+Using `+' instead of `-' turns off the given attribute.
+
+Variables with the integer attribute have arithmetic evaluation (see
+the `let' command) performed when the variable is assigned a value.
+
+When used in a function, `declare' makes NAMEs local, as with the `local'
+command. The `-g' option suppresses this behavior.
+
+Exit Status:
+Returns success unless an invalid option is supplied or a variable
+assignment error occurs.
+$END
+
+$BUILTIN typeset
+$FUNCTION declare_builtin
+$SHORT_DOC typeset [-aAfFgiIlnrtux] name[=value] ... or typeset -p [-aAfFilnrtux] [name ...]
+Set variable values and attributes.
+
+A synonym for `declare'. See `help declare'.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+#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));
+}
+
+$BUILTIN local
+$FUNCTION local_builtin
+$SHORT_DOC local [option] name[=value] ...
+Define local variables.
+
+Create a local variable called NAME, and give it VALUE. OPTION can
+be any option accepted by `declare'.
+
+Local variables can only be used within a function; they are visible
+only to the function where they are defined and its children.
+
+Exit Status:
+Returns success unless an invalid option is supplied, a variable
+assignment error occurs, or the shell is not executing a function.
+$END
+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/builtins/echo.def b/builtins/echo.def
new file mode 100644
index 0000000..4e2243d
--- /dev/null
+++ b/builtins/echo.def
@@ -0,0 +1,202 @@
+This file is echo.def, from which is created echo.c.
+It implements the builtin "echo" in Bash.
+
+Copyright (C) 1987-2018 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES echo.c
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "../bashansi.h"
+
+#include <stdio.h>
+#include "../shell.h"
+
+#include "common.h"
+
+$BUILTIN echo
+$FUNCTION echo_builtin
+$DEPENDS_ON V9_ECHO
+$SHORT_DOC echo [-neE] [arg ...]
+Write arguments to the standard output.
+
+Display the ARGs, separated by a single space character and followed by a
+newline, on the standard output.
+
+Options:
+ -n do not append a newline
+ -e enable interpretation of the following backslash escapes
+ -E explicitly suppress interpretation of backslash escapes
+
+`echo' interprets the following backslash-escaped characters:
+ \a alert (bell)
+ \b backspace
+ \c suppress further output
+ \e escape character
+ \E escape character
+ \f form feed
+ \n new line
+ \r carriage return
+ \t horizontal tab
+ \v vertical tab
+ \\ backslash
+ \0nnn the character whose ASCII code is NNN (octal). NNN can be
+ 0 to 3 octal digits
+ \xHH the eight-bit character whose value is HH (hexadecimal). HH
+ can be one or two hex digits
+ \uHHHH the Unicode character whose value is the hexadecimal value HHHH.
+ HHHH can be one to four hex digits.
+ \UHHHHHHHH the Unicode character whose value is the hexadecimal value
+ HHHHHHHH. HHHHHHHH can be one to eight hex digits.
+
+Exit Status:
+Returns success unless a write error occurs.
+$END
+
+$BUILTIN echo
+$FUNCTION echo_builtin
+$DEPENDS_ON !V9_ECHO
+$SHORT_DOC echo [-n] [arg ...]
+Write arguments to the standard output.
+
+Display the ARGs on the standard output followed by a newline.
+
+Options:
+ -n do not append a newline
+
+Exit Status:
+Returns success unless a write error occurs.
+$END
+
+#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 -<nonopt> 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/builtins/enable.def b/builtins/enable.def
new file mode 100644
index 0000000..27d341a
--- /dev/null
+++ b/builtins/enable.def
@@ -0,0 +1,586 @@
+This file is enable.def, from which is created enable.c.
+It implements the builtin "enable" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES enable.c
+
+$BUILTIN enable
+$FUNCTION enable_builtin
+$SHORT_DOC enable [-a] [-dnps] [-f filename] [name ...]
+Enable and disable shell builtins.
+
+Enables and disables builtin shell commands. Disabling allows you to
+execute a disk command which has the same name as a shell builtin
+without using a full pathname.
+
+Options:
+ -a print a list of builtins showing whether or not each is enabled
+ -n disable each NAME or display a list of disabled builtins
+ -p print the list of builtins in a reusable format
+ -s print only the names of Posix `special' builtins
+
+Options controlling dynamic loading:
+ -f Load builtin NAME from shared object FILENAME
+ -d Remove a builtin loaded with -f
+
+Without options, each NAME is enabled.
+
+To use the `test' found in $PATH instead of the shell builtin
+version, type `enable -n test'.
+
+Exit Status:
+Returns success unless NAME is not a shell builtin or an error occurs.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#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 <dlfcn.h>
+#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/builtins/eval.def b/builtins/eval.def
new file mode 100644
index 0000000..f459bce
--- /dev/null
+++ b/builtins/eval.def
@@ -0,0 +1,57 @@
+This file is eval.def, from which is created eval.c.
+It implements the builtin "eval" in Bash.
+
+Copyright (C) 1987-2016 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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES eval.c
+
+$BUILTIN eval
+$FUNCTION eval_builtin
+$SHORT_DOC eval [arg ...]
+Execute arguments as a shell command.
+
+Combine ARGs into a single string, use the result as input to the shell,
+and execute the resulting commands.
+
+Exit Status:
+Returns exit status of command or success if command is null.
+$END
+
+#include <config.h>
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#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|SEVAL_NOOPTIMIZE) : EXECUTION_SUCCESS);
+}
diff --git a/builtins/evalfile.c b/builtins/evalfile.c
new file mode 100644
index 0000000..fc3975e
--- /dev/null
+++ b/builtins/evalfile.c
@@ -0,0 +1,384 @@
+/* evalfile.c - read and evaluate commands from a file or file descriptor */
+
+/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
+
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "../bashtypes.h"
+#include "posixstat.h"
+#include "filecntl.h"
+
+#include <stdio.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "../parser.h"
+#include "../jobs.h"
+#include "../builtins.h"
+#include "../flags.h"
+#include "../input.h"
+#include "../execute_cmd.h"
+#include "../trap.h"
+
+#include <y.tab.h>
+
+#if defined (HISTORY)
+# include "../bashhist.h"
+#endif
+
+#include <typemax.h>
+
+#include "common.h"
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+/* Flags for _evalfile() */
+#define FEVAL_ENOENTOK 0x001
+#define FEVAL_BUILTIN 0x002
+#define FEVAL_UNWINDPROT 0x004
+#define FEVAL_NONINT 0x008
+#define FEVAL_LONGJMP 0x010
+#define FEVAL_HISTORY 0x020
+#define FEVAL_CHECKBINARY 0x040
+#define FEVAL_REGFILE 0x080
+#define FEVAL_NOPUSHARGS 0x100
+
+/* How many `levels' of sourced files we have. */
+int sourcelevel = 0;
+
+static int
+_evalfile (filename, flags)
+ const char *filename;
+ int flags;
+{
+ volatile int old_interactive;
+ procenv_t old_return_catch;
+ int return_val, fd, result, pflags, i, nnull;
+ ssize_t nr; /* return value from read(2) */
+ char *string;
+ struct stat finfo;
+ size_t file_size;
+ sh_vmsg_func_t *errfunc;
+#if defined (ARRAY_VARS)
+ SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v;
+ ARRAY *funcname_a, *bash_source_a, *bash_lineno_a;
+ struct func_array_state *fa;
+# if defined (DEBUGGER)
+ SHELL_VAR *bash_argv_v, *bash_argc_v;
+ ARRAY *bash_argv_a, *bash_argc_a;
+# endif
+ char *t, tt[2];
+#endif
+
+ USE_VAR(pflags);
+
+#if defined (ARRAY_VARS)
+ GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a);
+ GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a);
+ GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a);
+# if defined (DEBUGGER)
+ GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a);
+ GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a);
+# endif
+#endif
+
+ fd = open (filename, O_RDONLY);
+
+ if (fd < 0 || (fstat (fd, &finfo) == -1))
+ {
+ i = errno;
+ if (fd >= 0)
+ close (fd);
+ errno = i;
+
+file_error_and_exit:
+ if (((flags & FEVAL_ENOENTOK) == 0) || errno != ENOENT)
+ file_error (filename);
+
+ if (flags & FEVAL_LONGJMP)
+ {
+ last_command_exit_value = EXECUTION_FAILURE;
+ jump_to_top_level (EXITPROG);
+ }
+
+ return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE
+ : ((errno == ENOENT && (flags & FEVAL_ENOENTOK) != 0) ? 0 : -1));
+ }
+
+ errfunc = ((flags & FEVAL_BUILTIN) ? builtin_error : internal_error);
+
+ if (S_ISDIR (finfo.st_mode))
+ {
+ (*errfunc) (_("%s: is a directory"), filename);
+ close (fd);
+ return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1);
+ }
+ else if ((flags & FEVAL_REGFILE) && S_ISREG (finfo.st_mode) == 0)
+ {
+ (*errfunc) (_("%s: not a regular file"), filename);
+ close (fd);
+ return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1);
+ }
+
+ file_size = (size_t)finfo.st_size;
+ /* Check for overflow with large files. */
+ if (file_size != finfo.st_size || file_size + 1 < file_size)
+ {
+ (*errfunc) (_("%s: file is too large"), filename);
+ close (fd);
+ return ((flags & FEVAL_BUILTIN) ? EXECUTION_FAILURE : -1);
+ }
+
+ if (S_ISREG (finfo.st_mode) && file_size <= SSIZE_MAX)
+ {
+ string = (char *)xmalloc (1 + file_size);
+ nr = read (fd, string, file_size);
+ if (nr >= 0)
+ string[nr] = '\0';
+ }
+ else
+ nr = zmapfd (fd, &string, 0);
+
+ return_val = errno;
+ close (fd);
+ errno = return_val;
+
+ if (nr < 0) /* XXX was != file_size, not < 0 */
+ {
+ free (string);
+ goto file_error_and_exit;
+ }
+
+ if (nr == 0)
+ {
+ free (string);
+ return ((flags & FEVAL_BUILTIN) ? EXECUTION_SUCCESS : 1);
+ }
+
+ if ((flags & FEVAL_CHECKBINARY) &&
+ check_binary_file (string, (nr > 80) ? 80 : nr))
+ {
+ free (string);
+ (*errfunc) (_("%s: cannot execute binary file"), filename);
+ return ((flags & FEVAL_BUILTIN) ? EX_BINARY_FILE : -1);
+ }
+
+ i = strlen (string);
+ if (i < nr)
+ {
+ for (nnull = i = 0; i < nr; i++)
+ if (string[i] == '\0')
+ {
+ memmove (string+i, string+i+1, nr - i);
+ nr--;
+ /* Even if the `check binary' flag is not set, we want to avoid
+ sourcing files with more than 256 null characters -- that
+ probably indicates a binary file. */
+ if ((flags & FEVAL_BUILTIN) && ++nnull > 256)
+ {
+ free (string);
+ (*errfunc) (_("%s: cannot execute binary file"), filename);
+ return ((flags & FEVAL_BUILTIN) ? EX_BINARY_FILE : -1);
+ }
+ }
+ }
+
+ if (flags & FEVAL_UNWINDPROT)
+ {
+ begin_unwind_frame ("_evalfile");
+
+ unwind_protect_int (return_catch_flag);
+ unwind_protect_jmp_buf (return_catch);
+ if (flags & FEVAL_NONINT)
+ unwind_protect_int (interactive);
+ unwind_protect_int (sourcelevel);
+ }
+ else
+ {
+ COPY_PROCENV (return_catch, old_return_catch);
+ if (flags & FEVAL_NONINT)
+ old_interactive = interactive;
+ }
+
+ if (flags & FEVAL_NONINT)
+ interactive = 0;
+
+ return_catch_flag++;
+ sourcelevel++;
+
+#if defined (ARRAY_VARS)
+ array_push (bash_source_a, (char *)filename);
+ t = itos (executing_line_number ());
+ array_push (bash_lineno_a, t);
+ free (t);
+ array_push (funcname_a, "source"); /* not exactly right */
+
+ fa = (struct func_array_state *)xmalloc (sizeof (struct func_array_state));
+ fa->source_a = bash_source_a;
+ fa->source_v = bash_source_v;
+ fa->lineno_a = bash_lineno_a;
+ fa->lineno_v = bash_lineno_v;
+ fa->funcname_a = funcname_a;
+ fa->funcname_v = funcname_v;
+ if (flags & FEVAL_UNWINDPROT)
+ add_unwind_protect (restore_funcarray_state, fa);
+
+# if defined (DEBUGGER)
+ /* Have to figure out a better way to do this when `source' is supplied
+ arguments */
+ if ((flags & FEVAL_NOPUSHARGS) == 0)
+ {
+ if (shell_compatibility_level <= 44)
+ init_bash_argv ();
+ array_push (bash_argv_a, (char *)filename); /* XXX - unconditionally? */
+ tt[0] = '1'; tt[1] = '\0';
+ array_push (bash_argc_a, tt);
+ if (flags & FEVAL_UNWINDPROT)
+ add_unwind_protect (pop_args, 0);
+ }
+# endif
+#endif
+
+ /* set the flags to be passed to parse_and_execute */
+ pflags = SEVAL_RESETLINE;
+ pflags |= (flags & FEVAL_HISTORY) ? 0 : SEVAL_NOHIST;
+
+ if (flags & FEVAL_BUILTIN)
+ result = EXECUTION_SUCCESS;
+
+ return_val = setjmp_nosigs (return_catch);
+
+ /* If `return' was seen outside of a function, but in the script, then
+ force parse_and_execute () to clean up. */
+ if (return_val)
+ {
+ parse_and_execute_cleanup (-1);
+ result = return_catch_value;
+ }
+ else
+ result = parse_and_execute (string, filename, pflags);
+
+ if (flags & FEVAL_UNWINDPROT)
+ run_unwind_frame ("_evalfile");
+ else
+ {
+ if (flags & FEVAL_NONINT)
+ interactive = old_interactive;
+#if defined (ARRAY_VARS)
+ restore_funcarray_state (fa);
+# if defined (DEBUGGER)
+ if ((flags & FEVAL_NOPUSHARGS) == 0)
+ {
+ /* Don't need to call pop_args here until we do something better
+ when source is passed arguments (see above). */
+ array_pop (bash_argc_a);
+ array_pop (bash_argv_a);
+ }
+# endif
+#endif
+ return_catch_flag--;
+ sourcelevel--;
+ COPY_PROCENV (old_return_catch, return_catch);
+ }
+
+ /* If we end up with EOF after sourcing a file, which can happen when the file
+ doesn't end with a newline, pretend that it did. */
+ if (current_token == yacc_EOF)
+ push_token ('\n'); /* XXX */
+
+ return ((flags & FEVAL_BUILTIN) ? result : 1);
+}
+
+int
+maybe_execute_file (fname, force_noninteractive)
+ const char *fname;
+ int force_noninteractive;
+{
+ char *filename;
+ int result, flags;
+
+ filename = bash_tilde_expand (fname, 0);
+ flags = FEVAL_ENOENTOK;
+ if (force_noninteractive)
+ flags |= FEVAL_NONINT;
+ result = _evalfile (filename, flags);
+ free (filename);
+ return result;
+}
+
+int
+force_execute_file (fname, force_noninteractive)
+ const char *fname;
+ int force_noninteractive;
+{
+ char *filename;
+ int result, flags;
+
+ filename = bash_tilde_expand (fname, 0);
+ flags = 0;
+ if (force_noninteractive)
+ flags |= FEVAL_NONINT;
+ result = _evalfile (filename, flags);
+ free (filename);
+ return result;
+}
+
+#if defined (HISTORY)
+int
+fc_execute_file (filename)
+ const char *filename;
+{
+ int flags;
+
+ /* We want these commands to show up in the history list if
+ remember_on_history is set. We use FEVAL_BUILTIN to return
+ the result of parse_and_execute. */
+ flags = FEVAL_ENOENTOK|FEVAL_HISTORY|FEVAL_REGFILE|FEVAL_BUILTIN;
+ return (_evalfile (filename, flags));
+}
+#endif /* HISTORY */
+
+int
+source_file (filename, sflags)
+ const char *filename;
+ int sflags;
+{
+ int flags, rval;
+
+ flags = FEVAL_BUILTIN|FEVAL_UNWINDPROT|FEVAL_NONINT;
+ if (sflags)
+ flags |= FEVAL_NOPUSHARGS;
+ /* POSIX shells exit if non-interactive and file error. */
+ if (posixly_correct && interactive_shell == 0 && executing_command_builtin == 0)
+ flags |= FEVAL_LONGJMP;
+ rval = _evalfile (filename, flags);
+
+ run_return_trap ();
+ return rval;
+}
diff --git a/builtins/evalstring.c b/builtins/evalstring.c
new file mode 100644
index 0000000..df3dd68
--- /dev/null
+++ b/builtins/evalstring.c
@@ -0,0 +1,840 @@
+/* evalstring.c - evaluate a string as one or more shell commands. */
+
+/* Copyright (C) 1996-2022 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
+
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <signal.h>
+
+#include <errno.h>
+
+#include "filecntl.h"
+#include "../bashansi.h"
+
+#include "../shell.h"
+#include "../jobs.h"
+#include "../builtins.h"
+#include "../flags.h"
+#include "../parser.h"
+#include "../input.h"
+#include "../execute_cmd.h"
+#include "../redir.h"
+#include "../trap.h"
+#include "../bashintl.h"
+
+#include <y.tab.h>
+
+#if defined (HISTORY)
+# include "../bashhist.h"
+#endif
+
+#include "common.h"
+#include "builtext.h"
+
+#if !defined (errno)
+extern int errno;
+#endif
+
+#define IS_BUILTIN(s) (builtin_address_internal(s, 0) != (struct builtin *)NULL)
+
+int parse_and_execute_level = 0;
+
+static int cat_file PARAMS((REDIRECT *));
+
+#define PE_TAG "parse_and_execute top"
+#define PS_TAG "parse_string top"
+
+#if defined (HISTORY)
+static void
+set_history_remembering ()
+{
+ remember_on_history = enable_history_list;
+}
+#endif
+
+static void
+restore_lastcom (x)
+ char *x;
+{
+ FREE (the_printed_command_except_trap);
+ the_printed_command_except_trap = x;
+}
+
+int
+should_optimize_fork (command, subshell)
+ COMMAND *command;
+ int subshell;
+{
+ return (running_trap == 0 &&
+ command->type == cm_simple &&
+ signal_is_trapped (EXIT_TRAP) == 0 &&
+ signal_is_trapped (ERROR_TRAP) == 0 &&
+ any_signals_trapped () < 0 &&
+ (subshell || (command->redirects == 0 && command->value.Simple->redirects == 0)) &&
+ ((command->flags & CMD_TIME_PIPELINE) == 0) &&
+ ((command->flags & CMD_INVERT_RETURN) == 0));
+}
+
+/* This has extra tests to account for STARTUP_STATE == 2, which is for
+ -c command but has been extended to command and process substitution
+ (basically any time you call parse_and_execute in a subshell). */
+int
+should_suppress_fork (command)
+ COMMAND *command;
+{
+ int subshell;
+
+ subshell = subshell_environment & SUBSHELL_PROCSUB; /* salt to taste */
+ return (startup_state == 2 && parse_and_execute_level == 1 &&
+ *bash_input.location.string == '\0' &&
+ parser_expanding_alias () == 0 &&
+ should_optimize_fork (command, subshell));
+}
+
+int
+can_optimize_connection (command)
+ COMMAND *command;
+{
+ return (*bash_input.location.string == '\0' &&
+ parser_expanding_alias () == 0 &&
+ (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') &&
+ command->value.Connection->second->type == cm_simple);
+}
+
+void
+optimize_connection_fork (command)
+ COMMAND *command;
+{
+ if (command->type == cm_connection &&
+ (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') &&
+ (command->value.Connection->second->flags & CMD_TRY_OPTIMIZING) &&
+ (should_suppress_fork (command->value.Connection->second) ||
+ ((subshell_environment & SUBSHELL_PAREN) && should_optimize_fork (command->value.Connection->second, 0))))
+ {
+ command->value.Connection->second->flags |= CMD_NO_FORK;
+ command->value.Connection->second->value.Simple->flags |= CMD_NO_FORK;
+ }
+}
+
+void
+optimize_subshell_command (command)
+ COMMAND *command;
+{
+ if (should_optimize_fork (command, 0))
+ {
+ command->flags |= CMD_NO_FORK;
+ command->value.Simple->flags |= CMD_NO_FORK;
+ }
+ else if (command->type == cm_connection &&
+ (command->value.Connection->connector == AND_AND || command->value.Connection->connector == OR_OR || command->value.Connection->connector == ';') &&
+ command->value.Connection->second->type == cm_simple &&
+ parser_expanding_alias () == 0)
+ {
+ command->value.Connection->second->flags |= CMD_TRY_OPTIMIZING;
+ command->value.Connection->second->value.Simple->flags |= CMD_TRY_OPTIMIZING;
+ }
+}
+
+void
+optimize_shell_function (command)
+ COMMAND *command;
+{
+ COMMAND *fc;
+
+ fc = (command->type == cm_group) ? command->value.Group->command : command;
+
+ if (fc->type == cm_simple && should_suppress_fork (fc))
+ {
+ fc->flags |= CMD_NO_FORK;
+ fc->value.Simple->flags |= CMD_NO_FORK;
+ }
+ else if (fc->type == cm_connection && can_optimize_connection (fc) && should_suppress_fork (fc->value.Connection->second))
+ {
+ fc->value.Connection->second->flags |= CMD_NO_FORK;
+ fc->value.Connection->second->value.Simple->flags |= CMD_NO_FORK;
+ }
+}
+
+int
+can_optimize_cat_file (command)
+ COMMAND *command;
+{
+ return (command->type == cm_simple && !command->redirects &&
+ (command->flags & CMD_TIME_PIPELINE) == 0 &&
+ command->value.Simple->words == 0 &&
+ command->value.Simple->redirects &&
+ command->value.Simple->redirects->next == 0 &&
+ command->value.Simple->redirects->instruction == r_input_direction &&
+ command->value.Simple->redirects->redirector.dest == 0);
+}
+
+/* How to force parse_and_execute () to clean up after itself. */
+void
+parse_and_execute_cleanup (old_running_trap)
+ int old_running_trap;
+{
+ if (running_trap > 0)
+ {
+ /* We assume if we have a different value for running_trap than when
+ we started (the only caller that cares is evalstring()), the
+ original caller will perform the cleanup, and we should not step
+ on them. */
+ if (running_trap != old_running_trap)
+ run_trap_cleanup (running_trap - 1);
+ unfreeze_jobs_list ();
+ }
+
+ if (have_unwind_protects ())
+ run_unwind_frame (PE_TAG);
+ else
+ parse_and_execute_level = 0; /* XXX */
+}
+
+static void
+parse_prologue (string, flags, tag)
+ char *string;
+ int flags;
+ char *tag;
+{
+ char *orig_string, *lastcom;
+ int x;
+
+ orig_string = string;
+ /* Unwind protect this invocation of parse_and_execute (). */
+ begin_unwind_frame (tag);
+ unwind_protect_int (parse_and_execute_level);
+ unwind_protect_jmp_buf (top_level);
+ unwind_protect_int (indirection_level);
+ unwind_protect_int (line_number);
+ unwind_protect_int (line_number_for_err_trap);
+ unwind_protect_int (loop_level);
+ unwind_protect_int (executing_list);
+ unwind_protect_int (comsub_ignore_return);
+ if (flags & (SEVAL_NONINT|SEVAL_INTERACT))
+ unwind_protect_int (interactive);
+
+#if defined (HISTORY)
+ if (parse_and_execute_level == 0)
+ add_unwind_protect (set_history_remembering, (char *)NULL);
+ else
+ unwind_protect_int (remember_on_history); /* can be used in scripts */
+# if defined (BANG_HISTORY)
+ unwind_protect_int (history_expansion_inhibited);
+# endif /* BANG_HISTORY */
+#endif /* HISTORY */
+
+ if (interactive_shell)
+ {
+ x = get_current_prompt_level ();
+ add_unwind_protect (set_current_prompt_level, x);
+ }
+
+ if (the_printed_command_except_trap)
+ {
+ lastcom = savestring (the_printed_command_except_trap);
+ add_unwind_protect (restore_lastcom, lastcom);
+ }
+
+ add_unwind_protect (pop_stream, (char *)NULL);
+ if (parser_expanding_alias ())
+ add_unwind_protect (parser_restore_alias, (char *)NULL);
+
+ if (orig_string && ((flags & SEVAL_NOFREE) == 0))
+ add_unwind_protect (xfree, orig_string);
+ end_unwind_frame ();
+
+ if (flags & (SEVAL_NONINT|SEVAL_INTERACT))
+ interactive = (flags & SEVAL_NONINT) ? 0 : 1;
+
+#if defined (HISTORY)
+ if (flags & SEVAL_NOHIST)
+ bash_history_disable ();
+# if defined (BANG_HISTORY)
+ if (flags & SEVAL_NOHISTEXP)
+ history_expansion_inhibited = 1;
+# endif /* BANG_HISTORY */
+#endif /* HISTORY */
+}
+
+/* Parse and execute the commands in STRING. Returns whatever
+ execute_command () returns. This frees STRING. FLAGS is a
+ flags word; look in common.h for the possible values. Actions
+ are:
+ (flags & SEVAL_NONINT) -> interactive = 0;
+ (flags & SEVAL_INTERACT) -> interactive = 1;
+ (flags & SEVAL_NOHIST) -> call bash_history_disable ()
+ (flags & SEVAL_NOFREE) -> don't free STRING when finished
+ (flags & SEVAL_RESETLINE) -> reset line_number to 1
+ (flags & SEVAL_NOHISTEXP) -> history_expansion_inhibited -> 1
+ (flags & SEVAL_NOOPTIMIZE) -> don't try to turn on optimizing flags
+*/
+
+int
+parse_and_execute (string, from_file, flags)
+ char *string;
+ const char *from_file;
+ int flags;
+{
+ int code, lreset;
+ volatile int should_jump_to_top_level, last_result;
+ COMMAND *volatile command;
+ volatile sigset_t pe_sigmask;
+
+ parse_prologue (string, flags, PE_TAG);
+
+ parse_and_execute_level++;
+
+ lreset = flags & SEVAL_RESETLINE;
+
+#if defined (HAVE_POSIX_SIGNALS)
+ /* If we longjmp and are going to go on, use this to restore signal mask */
+ sigemptyset ((sigset_t *)&pe_sigmask);
+ sigprocmask (SIG_BLOCK, (sigset_t *)NULL, (sigset_t *)&pe_sigmask);
+#endif
+
+ /* Reset the line number if the caller wants us to. If we don't reset the
+ line number, we have to subtract one, because we will add one just
+ before executing the next command (resetting the line number sets it to
+ 0; the first line number is 1). */
+ push_stream (lreset);
+ if (parser_expanding_alias ())
+ /* push current shell_input_line */
+ parser_save_alias ();
+
+ if (lreset == 0)
+ line_number--;
+
+ indirection_level++;
+
+ code = should_jump_to_top_level = 0;
+ last_result = EXECUTION_SUCCESS;
+
+ /* We need to reset enough of the token state so we can start fresh. */
+ if (current_token == yacc_EOF)
+ current_token = '\n'; /* reset_parser() ? */
+
+ with_input_from_string (string, from_file);
+ clear_shell_input_line ();
+ while (*(bash_input.location.string) || parser_expanding_alias ())
+ {
+ command = (COMMAND *)NULL;
+
+ if (interrupt_state)
+ {
+ last_result = EXECUTION_FAILURE;
+ break;
+ }
+
+ /* Provide a location for functions which `longjmp (top_level)' to
+ jump to. This prevents errors in substitution from restarting
+ the reader loop directly, for example. */
+ code = setjmp_nosigs (top_level);
+
+ if (code)
+ {
+ should_jump_to_top_level = 0;
+ switch (code)
+ {
+ case ERREXIT:
+ /* variable_context -> 0 is what eval.c:reader_loop() does in
+ these circumstances. Don't bother with cleanup here because
+ we don't want to run the function execution cleanup stuff
+ that will cause pop_context and other functions to run.
+ We call reset_local_contexts() instead, which just frees
+ context memory.
+ XXX - change that if we want the function context to be
+ unwound. */
+ if (exit_immediately_on_error && variable_context)
+ {
+ discard_unwind_frame ("pe_dispose");
+ reset_local_contexts (); /* not in a function */
+ }
+ should_jump_to_top_level = 1;
+ goto out;
+ case FORCE_EOF:
+ case EXITPROG:
+ if (command)
+ run_unwind_frame ("pe_dispose");
+ /* Remember to call longjmp (top_level) after the old
+ value for it is restored. */
+ should_jump_to_top_level = 1;
+ goto out;
+
+ case EXITBLTIN:
+ if (command)
+ {
+ if (variable_context && signal_is_trapped (0))
+ {
+ /* Let's make sure we run the exit trap in the function
+ context, as we do when not running parse_and_execute.
+ The pe_dispose unwind frame comes before any unwind-
+ protects installed by the string we're evaluating, so
+ it will undo the current function scope. */
+ dispose_command (command);
+ discard_unwind_frame ("pe_dispose");
+ }
+ else
+ run_unwind_frame ("pe_dispose");
+ }
+ should_jump_to_top_level = 1;
+ goto out;
+
+ case DISCARD:
+ if (command)
+ run_unwind_frame ("pe_dispose");
+ last_result = last_command_exit_value = EXECUTION_FAILURE; /* XXX */
+ set_pipestatus_from_exit (last_command_exit_value);
+ if (subshell_environment)
+ {
+ should_jump_to_top_level = 1;
+ goto out;
+ }
+ else
+ {
+#if 0
+ dispose_command (command); /* pe_dispose does this */
+#endif
+#if defined (HAVE_POSIX_SIGNALS)
+ sigprocmask (SIG_SETMASK, (sigset_t *)&pe_sigmask, (sigset_t *)NULL);
+#endif
+ continue;
+ }
+
+ default:
+ command_error ("parse_and_execute", CMDERR_BADJUMP, code, 0);
+ break;
+ }
+ }
+
+ if (parse_command () == 0)
+ {
+ int local_expalias, local_alflag;
+
+ if ((flags & SEVAL_PARSEONLY) || (interactive_shell == 0 && read_but_dont_execute))
+ {
+ last_result = EXECUTION_SUCCESS;
+ dispose_command (global_command);
+ global_command = (COMMAND *)NULL;
+ }
+ else if (command = global_command)
+ {
+ struct fd_bitmap *bitmap;
+
+ if (flags & SEVAL_FUNCDEF)
+ {
+ char *x;
+
+ /* If the command parses to something other than a straight
+ function definition, or if we have not consumed the entire
+ string, or if the parser has transformed the function
+ name (as parsing will if it begins or ends with shell
+ whitespace, for example), reject the attempt */
+ if (command->type != cm_function_def ||
+ ((x = parser_remaining_input ()) && *x) ||
+ (STREQ (from_file, command->value.Function_def->name->word) == 0))
+ {
+ internal_warning (_("%s: ignoring function definition attempt"), from_file);
+ should_jump_to_top_level = 0;
+ last_result = last_command_exit_value = EX_BADUSAGE;
+ set_pipestatus_from_exit (last_command_exit_value);
+ reset_parser ();
+ break;
+ }
+ }
+
+ bitmap = new_fd_bitmap (FD_BITMAP_SIZE);
+ begin_unwind_frame ("pe_dispose");
+ add_unwind_protect (dispose_fd_bitmap, bitmap);
+ add_unwind_protect (dispose_command, command); /* XXX */
+
+ global_command = (COMMAND *)NULL;
+
+ if ((subshell_environment & SUBSHELL_COMSUB) && comsub_ignore_return)
+ command->flags |= CMD_IGNORE_RETURN;
+
+#if defined (ONESHOT)
+ /*
+ * IF
+ * we were invoked as `bash -c' (startup_state == 2) AND
+ * parse_and_execute has not been called recursively AND
+ * we're not running a trap AND
+ * we have parsed the full command (string == '\0') AND
+ * we're not going to run the exit trap AND
+ * we have a simple command without redirections AND
+ * the command is not being timed AND
+ * the command's return status is not being inverted AND
+ * there aren't any traps in effect
+ * THEN
+ * tell the execution code that we don't need to fork
+ */
+ if (should_suppress_fork (command))
+ {
+ command->flags |= CMD_NO_FORK;
+ command->value.Simple->flags |= CMD_NO_FORK;
+ }
+
+ /* Can't optimize forks out here execept for simple commands.
+ This knows that the parser sets up commands as left-side heavy
+ (&& and || are left-associative) and after the single parse,
+ if we are at the end of the command string, the last in a
+ series of connection commands is
+ command->value.Connection->second. */
+ else if (command->type == cm_connection &&
+ (flags & SEVAL_NOOPTIMIZE) == 0 &&
+ can_optimize_connection (command))
+ {
+ command->value.Connection->second->flags |= CMD_TRY_OPTIMIZING;
+ command->value.Connection->second->value.Simple->flags |= CMD_TRY_OPTIMIZING;
+ }
+#endif /* ONESHOT */
+
+ /* We play tricks in the parser and command_substitute() turning
+ expand_aliases on and off depending on which parsing pass and
+ whether or not we're in posix mode. This only matters for
+ parsing, and we let the higher layers deal with that. We just
+ want to ensure that expand_aliases is set to the appropriate
+ global value when we go to execute this command, so we save
+ and restore it around the execution (we don't restore it if
+ the global value of the flag (expaliases_flag) changes). */
+ local_expalias = expand_aliases;
+ local_alflag = expaliases_flag;
+ if (subshell_environment & SUBSHELL_COMSUB)
+ expand_aliases = expaliases_flag;
+
+ /* See if this is a candidate for $( <file ). */
+ if (startup_state == 2 &&
+ (subshell_environment & SUBSHELL_COMSUB) &&
+ *bash_input.location.string == '\0' &&
+ can_optimize_cat_file (command))
+ {
+ int r;
+ r = cat_file (command->value.Simple->redirects);
+ last_result = (r < 0) ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
+ }
+ else
+ last_result = execute_command_internal
+ (command, 0, NO_PIPE, NO_PIPE, bitmap);
+ dispose_command (command);
+ dispose_fd_bitmap (bitmap);
+ discard_unwind_frame ("pe_dispose");
+
+ /* If the global value didn't change, we restore what we had. */
+ if ((subshell_environment & SUBSHELL_COMSUB) && local_alflag == expaliases_flag)
+ expand_aliases = local_expalias;
+
+ if (flags & SEVAL_ONECMD)
+ {
+ reset_parser ();
+ break;
+ }
+ }
+ }
+ else
+ {
+ last_result = EX_BADUSAGE; /* was EXECUTION_FAILURE */
+
+ if (interactive_shell == 0 && this_shell_builtin &&
+ (this_shell_builtin == source_builtin || this_shell_builtin == eval_builtin) &&
+ last_command_exit_value == EX_BADSYNTAX && posixly_correct && executing_command_builtin == 0)
+ {
+ should_jump_to_top_level = 1;
+ code = ERREXIT;
+ last_command_exit_value = EX_BADUSAGE;
+ }
+
+ /* Since we are shell compatible, syntax errors in a script
+ abort the execution of the script. Right? */
+ break;
+ }
+ }
+
+ out:
+
+ run_unwind_frame (PE_TAG);
+
+ if (interrupt_state && parse_and_execute_level == 0)
+ {
+ /* An interrupt during non-interactive execution in an
+ interactive shell (e.g. via $PROMPT_COMMAND) should
+ not cause the shell to exit. */
+ interactive = interactive_shell;
+ throw_to_top_level ();
+ }
+
+ CHECK_TERMSIG;
+
+ if (should_jump_to_top_level)
+ jump_to_top_level (code);
+
+ return (last_result);
+}
+
+/* Parse a command contained in STRING according to FLAGS and return the
+ number of characters consumed from the string. If non-NULL, set *ENDP
+ to the position in the string where the parse ended. Used to validate
+ command substitutions during parsing to obey Posix rules about finding
+ the end of the command and balancing parens. */
+int
+parse_string (string, from_file, flags, cmdp, endp)
+ char *string;
+ const char *from_file;
+ int flags;
+ COMMAND **cmdp;
+ char **endp;
+{
+ int code, nc;
+ volatile int should_jump_to_top_level;
+ COMMAND *volatile command, *oglobal;
+ char *ostring;
+ volatile sigset_t ps_sigmask;
+
+ parse_prologue (string, flags, PS_TAG);
+
+#if defined (HAVE_POSIX_SIGNALS)
+ /* If we longjmp and are going to go on, use this to restore signal mask */
+ sigemptyset ((sigset_t *)&ps_sigmask);
+ sigprocmask (SIG_BLOCK, (sigset_t *)NULL, (sigset_t *)&ps_sigmask);
+#endif
+
+ /* Reset the line number if the caller wants us to. If we don't reset the
+ line number, we have to subtract one, because we will add one just
+ before executing the next command (resetting the line number sets it to
+ 0; the first line number is 1). */
+ push_stream (0);
+ if (parser_expanding_alias ())
+ /* push current shell_input_line */
+ parser_save_alias ();
+
+ code = should_jump_to_top_level = 0;
+ oglobal = global_command;
+
+ with_input_from_string (string, from_file);
+ ostring = bash_input.location.string;
+ while (*(bash_input.location.string)) /* XXX - parser_expanding_alias () ? */
+ {
+ command = (COMMAND *)NULL;
+
+#if 0
+ if (interrupt_state)
+ break;
+#endif
+
+ /* Provide a location for functions which `longjmp (top_level)' to
+ jump to. */
+ code = setjmp_nosigs (top_level);
+
+ if (code)
+ {
+ INTERNAL_DEBUG(("parse_string: longjmp executed: code = %d", code));
+
+ should_jump_to_top_level = 0;
+ switch (code)
+ {
+ case FORCE_EOF:
+ case ERREXIT:
+ case EXITPROG:
+ case EXITBLTIN:
+ case DISCARD: /* XXX */
+ if (command)
+ dispose_command (command);
+ /* Remember to call longjmp (top_level) after the old
+ value for it is restored. */
+ should_jump_to_top_level = 1;
+ goto out;
+
+ default:
+#if defined (HAVE_POSIX_SIGNALS)
+ sigprocmask (SIG_SETMASK, (sigset_t *)&ps_sigmask, (sigset_t *)NULL);
+#endif
+ command_error ("parse_string", CMDERR_BADJUMP, code, 0);
+ break;
+ }
+ }
+
+ if (parse_command () == 0)
+ {
+ if (cmdp)
+ *cmdp = global_command;
+ else
+ dispose_command (global_command);
+ global_command = (COMMAND *)NULL;
+ }
+ else
+ {
+ if ((flags & SEVAL_NOLONGJMP) == 0)
+ {
+ should_jump_to_top_level = 1;
+ code = DISCARD;
+ }
+ else
+ reset_parser (); /* XXX - sets token_to_read */
+ break;
+ }
+
+ if (current_token == yacc_EOF || current_token == shell_eof_token)
+ {
+ if (current_token == shell_eof_token)
+ rewind_input_string ();
+ break;
+ }
+ }
+
+out:
+
+ global_command = oglobal;
+ nc = bash_input.location.string - ostring;
+ if (endp)
+ *endp = bash_input.location.string;
+
+ run_unwind_frame (PS_TAG);
+
+ /* If we return < 0, the caller (xparse_dolparen) will jump_to_top_level for
+ us, after doing cleanup */
+ if (should_jump_to_top_level)
+ {
+ if (parse_and_execute_level == 0)
+ top_level_cleanup ();
+ if (code == DISCARD)
+ return -DISCARD;
+ jump_to_top_level (code);
+ }
+
+ return (nc);
+}
+
+int
+open_redir_file (r, fnp)
+ REDIRECT *r;
+ char **fnp;
+{
+ char *fn;
+ int fd, rval;
+
+ if (r->instruction != r_input_direction)
+ return -1;
+
+ /* Get the filename. */
+ if (posixly_correct && !interactive_shell)
+ disallow_filename_globbing++;
+ fn = redirection_expand (r->redirectee.filename);
+ if (posixly_correct && !interactive_shell)
+ disallow_filename_globbing--;
+
+ if (fn == 0)
+ {
+ redirection_error (r, AMBIGUOUS_REDIRECT, fn);
+ return -1;
+ }
+
+ fd = open(fn, O_RDONLY);
+ if (fd < 0)
+ {
+ file_error (fn);
+ free (fn);
+ if (fnp)
+ *fnp = 0;
+ return -1;
+ }
+
+ if (fnp)
+ *fnp = fn;
+ return fd;
+}
+
+/* Handle a $( < file ) command substitution. This expands the filename,
+ returning errors as appropriate, then just cats the file to the standard
+ output. */
+static int
+cat_file (r)
+ REDIRECT *r;
+{
+ char *fn;
+ int fd, rval;
+
+ fd = open_redir_file (r, &fn);
+ if (fd < 0)
+ return -1;
+
+ rval = zcatfd (fd, 1, fn);
+
+ free (fn);
+ close (fd);
+
+ return (rval);
+}
+
+int
+evalstring (string, from_file, flags)
+ char *string;
+ const char *from_file;
+ int flags;
+{
+ volatile int r, rflag, rcatch;
+ volatile int was_trap;
+
+ /* Are we running a trap when we execute this function? */
+ was_trap = running_trap;
+
+ rcatch = 0;
+ rflag = return_catch_flag;
+ /* If we are in a place where `return' is valid, we have to catch
+ `eval "... return"' and make sure parse_and_execute cleans up. Then
+ we can trampoline to the previous saved return_catch location. */
+ if (rflag)
+ {
+ begin_unwind_frame ("evalstring");
+
+ unwind_protect_int (return_catch_flag);
+ unwind_protect_jmp_buf (return_catch);
+
+ return_catch_flag++; /* increment so we have a counter */
+ rcatch = setjmp_nosigs (return_catch);
+ }
+
+ if (rcatch)
+ {
+ /* We care about whether or not we are running the same trap we were
+ when we entered this function. */
+ parse_and_execute_cleanup (was_trap);
+ r = return_catch_value;
+ }
+ else
+ /* Note that parse_and_execute () frees the string it is passed. */
+ r = parse_and_execute (string, from_file, flags);
+
+ if (rflag)
+ {
+ run_unwind_frame ("evalstring");
+ if (rcatch && return_catch_flag)
+ {
+ return_catch_value = r;
+ sh_longjmp (return_catch, 1);
+ }
+ }
+
+ return (r);
+}
diff --git a/builtins/exec.def b/builtins/exec.def
new file mode 100644
index 0000000..add9082
--- /dev/null
+++ b/builtins/exec.def
@@ -0,0 +1,276 @@
+This file is exec.def, from which is created exec.c.
+It implements the builtin "exec" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES exec.c
+
+$BUILTIN exec
+$FUNCTION exec_builtin
+$SHORT_DOC exec [-cl] [-a name] [command [argument ...]] [redirection ...]
+Replace the shell with the given command.
+
+Execute COMMAND, replacing this shell with the specified program.
+ARGUMENTS become the arguments to COMMAND. If COMMAND is not specified,
+any redirections take effect in the current shell.
+
+Options:
+ -a name pass NAME as the zeroth argument to COMMAND
+ -c execute COMMAND with an empty environment
+ -l place a dash in the zeroth argument to COMMAND
+
+If the command cannot be executed, a non-interactive shell exits, unless
+the shell option `execfail' is set.
+
+Exit Status:
+Returns success unless COMMAND is not found or a redirection error occurs.
+$END
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#include "posixstat.h"
+#include <signal.h>
+#include <errno.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+#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/builtins/exit.def b/builtins/exit.def
new file mode 100644
index 0000000..9c85bc5
--- /dev/null
+++ b/builtins/exit.def
@@ -0,0 +1,169 @@
+This file is exit.def, from which is created exit.c.
+It implements the builtins "exit", and "logout" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES exit.c
+
+$BUILTIN exit
+$FUNCTION exit_builtin
+$SHORT_DOC exit [n]
+Exit the shell.
+
+Exits the shell with a status of N. If N is omitted, the exit status
+is that of the last command executed.
+$END
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#include <stdio.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#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));
+}
+
+$BUILTIN logout
+$FUNCTION logout_builtin
+$SHORT_DOC logout [n]
+Exit a login shell.
+
+Exits a login shell with exit status N. Returns an error if not executed
+in a login shell.
+$END
+
+/* 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/builtins/fc.def b/builtins/fc.def
new file mode 100644
index 0000000..9b8a997
--- /dev/null
+++ b/builtins/fc.def
@@ -0,0 +1,785 @@
+This file is fc.def, from which is created fc.c.
+It implements the builtin "fc" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES fc.c
+
+$BUILTIN fc
+$FUNCTION fc_builtin
+$DEPENDS_ON HISTORY
+$SHORT_DOC fc [-e ename] [-lnr] [first] [last] or fc -s [pat=rep] [command]
+Display or execute commands from the history list.
+
+fc is used to list or edit and re-execute commands from the history list.
+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.
+
+Options:
+ -e ENAME select which editor to use. Default is FCEDIT, then EDITOR,
+ then vi
+ -l list lines instead of editing
+ -n omit line numbers when listing
+ -r reverse the order of the lines (newest listed first)
+
+With the `fc -s [pat=rep ...] [command]' format, COMMAND is
+re-executed after the substitution OLD=NEW is performed.
+
+A useful alias to use with this is r='fc -s', so that typing `r cc'
+runs the last command beginning with `cc' and typing `r' re-executes
+the last command.
+
+Exit Status:
+Returns success or status of executed command; non-zero if an error occurs.
+$END
+
+#include <config.h>
+
+#if defined (HISTORY)
+#if defined (HAVE_SYS_PARAM_H)
+# include <sys/param.h>
+#endif
+#include "../bashtypes.h"
+#include "posixstat.h"
+#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H)
+# include <sys/file.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <chartypes.h>
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+#include <errno.h>
+
+#include "../shell.h"
+#include "../builtins.h"
+#include "../flags.h"
+#include "../parser.h"
+#include "../bashhist.h"
+#include "maxpath.h"
+#include <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/builtins/fg_bg.def b/builtins/fg_bg.def
new file mode 100644
index 0000000..0fb5322
--- /dev/null
+++ b/builtins/fg_bg.def
@@ -0,0 +1,189 @@
+This file is fg_bg.def, from which is created fg_bg.c.
+It implements the builtins "bg" and "fg" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES fg_bg.c
+
+$BUILTIN fg
+$FUNCTION fg_builtin
+$DEPENDS_ON JOB_CONTROL
+$SHORT_DOC fg [job_spec]
+Move job to the foreground.
+
+Place the job identified by JOB_SPEC in the foreground, making it the
+current job. If JOB_SPEC is not present, the shell's notion of the
+current job is used.
+
+Exit Status:
+Status of command placed in foreground, or failure if an error occurs.
+$END
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#include <signal.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#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 */
+
+$BUILTIN bg
+$FUNCTION bg_builtin
+$DEPENDS_ON JOB_CONTROL
+$SHORT_DOC bg [job_spec ...]
+Move jobs to the background.
+
+Place the jobs identified by each JOB_SPEC in the background, as if they
+had been started with `&'. If JOB_SPEC is not present, the shell's notion
+of the current job is used.
+
+Exit Status:
+Returns success unless job control is not enabled or an error occurs.
+$END
+
+#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/builtins/gen-helpfiles.c b/builtins/gen-helpfiles.c
new file mode 100644
index 0000000..d27d306
--- /dev/null
+++ b/builtins/gen-helpfiles.c
@@ -0,0 +1,198 @@
+/* gen-helpfiles - create files containing builtin help text */
+
+/* Copyright (C) 2012-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 <http://www.gnu.org/licenses/>.
+*/
+
+/* This links with a specially-generated version of builtins.c and takes
+ the long_doc members of each struct builtin element and writes those to
+ the file named by the `handle' member of the struct builtin element. */
+
+#if !defined (CROSS_COMPILING)
+# include <config.h>
+#else /* CROSS_COMPILING */
+/* A conservative set of defines based on POSIX/SUS3/XPG6 */
+# define HAVE_UNISTD_H
+# define HAVE_STRING_H
+# define HAVE_STDLIB_H
+
+# define HAVE_RENAME
+#endif /* CROSS_COMPILING */
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#ifndef _MINIX
+# include "../bashtypes.h"
+# if defined (HAVE_SYS_FILE_H)
+# include <sys/file.h>
+# endif
+#endif
+
+#include "posixstat.h"
+#include "filecntl.h"
+
+#include "../bashansi.h"
+#include <stdio.h>
+#include <errno.h>
+
+#include "stdc.h"
+
+#include "../builtins.h"
+#include "tmpbuiltins.h"
+
+#if defined (USING_BASH_MALLOC)
+#undef xmalloc
+#undef xrealloc
+#undef xfree
+
+#undef malloc
+#undef free /* defined in xmalloc.h */
+#endif
+
+#ifndef errno
+extern int errno;
+#endif
+
+#if !defined (__STDC__) && !defined (strcpy)
+extern char *strcpy ();
+#endif /* !__STDC__ && !strcpy */
+
+#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
+
+/* Flag values that builtins can have. */
+#define BUILTIN_FLAG_SPECIAL 0x01
+#define BUILTIN_FLAG_ASSIGNMENT 0x02
+#define BUILTIN_FLAG_POSIX_BUILTIN 0x04
+
+#define BASE_INDENT 4
+
+/* Non-zero means to produce separate help files for each builtin, named by
+ the builtin name, in `./helpfiles'. */
+int separate_helpfiles = 0;
+
+/* Non-zero means to create single C strings for each `longdoc', with
+ embedded newlines, for ease of translation. */
+int single_longdoc_strings = 1;
+
+/* The name of a directory into which the separate external help files will
+ eventually be installed. */
+char *helpfile_directory;
+
+/* Forward declarations. */
+
+int write_helpfiles PARAMS((struct builtin *));
+
+/* For each file mentioned on the command line, process it and
+ write the information to STRUCTFILE and EXTERNFILE, while
+ creating the production file if necessary. */
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ int arg_index = 1;
+
+ while (arg_index < argc && argv[arg_index][0] == '-')
+ {
+ char *arg = argv[arg_index++];
+
+ if (strcmp (arg, "-noproduction") == 0)
+ ;
+ else if (strcmp (arg, "-H") == 0)
+ helpfile_directory = argv[arg_index++];
+ else if (strcmp (arg, "-S") == 0)
+ single_longdoc_strings = 0;
+ else
+ {
+ fprintf (stderr, "%s: Unknown flag %s.\n", argv[0], arg);
+ exit (2);
+ }
+ }
+
+ write_helpfiles(shell_builtins);
+
+ exit (0);
+}
+
+/* Write DOCUMENTATION to STREAM, perhaps surrounding it with double-quotes
+ and quoting special characters in the string. Handle special things for
+ internationalization (gettext) and the single-string vs. multiple-strings
+ issues. */
+void
+write_documentation (stream, documentation, indentation)
+ FILE *stream;
+ char *documentation;
+ int indentation;
+{
+ if (stream == 0)
+ return;
+
+ if (documentation)
+ fprintf (stream, "%*s%s\n", indentation, " ", documentation);
+}
+
+int
+write_helpfiles (builtins)
+ struct builtin *builtins;
+{
+ char *helpfile, *bname, *fname;
+ FILE *helpfp;
+ int i, hdlen;
+ struct builtin b;
+
+ i = mkdir ("helpfiles", 0777);
+ if (i < 0 && errno != EEXIST)
+ {
+ fprintf (stderr, "write_helpfiles: helpfiles: cannot create directory\n");
+ return -1;
+ }
+
+ hdlen = strlen ("helpfiles/");
+ for (i = 0; i < num_shell_builtins; i++)
+ {
+ b = builtins[i];
+
+ fname = (char *)b.handle;
+ helpfile = (char *)malloc (hdlen + strlen (fname) + 1);
+ if (helpfile == 0)
+ {
+ fprintf (stderr, "gen-helpfiles: cannot allocate memory\n");
+ exit (1);
+ }
+ sprintf (helpfile, "helpfiles/%s", fname);
+
+ helpfp = fopen (helpfile, "w");
+ if (helpfp == 0)
+ {
+ fprintf (stderr, "write_helpfiles: cannot open %s\n", helpfile);
+ free (helpfile);
+ continue;
+ }
+
+ write_documentation (helpfp, b.long_doc[0], 4);
+
+ fflush (helpfp);
+ fclose (helpfp);
+ free (helpfile);
+ }
+ return 0;
+}
diff --git a/builtins/getopt.c b/builtins/getopt.c
new file mode 100644
index 0000000..9529bc3
--- /dev/null
+++ b/builtins/getopt.c
@@ -0,0 +1,355 @@
+/* getopt.c - getopt for Bash. Used by the getopt builtin. */
+
+/* Copyright (C) 1993-2009 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
+
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "memalloc.h"
+#include "../bashintl.h"
+#include "../shell.h"
+#include "getopt.h"
+
+/* For communication from `sh_getopt' to the caller.
+ When `sh_getopt' finds an option that takes an argument,
+ the argument value is returned here. */
+char *sh_optarg = 0;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `sh_getopt'.
+
+ On entry to `sh_getopt', zero means this is the first call; initialize.
+
+ When `sh_getopt' returns EOF, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `sh_optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+/* XXX 1003.2 says this must be 1 before any call. */
+int sh_optind = 0;
+
+/* Index of the current argument. */
+static int sh_curopt;
+
+/* The next char to be scanned in the option-element
+ in which the last option character we returned was found.
+ This allows us to pick up the scan where we left off.
+
+ If this is zero, or a null string, it means resume the scan
+ by advancing to the next ARGV-element. */
+
+static char *nextchar;
+static int sh_charindex;
+
+/* Callers store zero here to inhibit the error message
+ for unrecognized options. */
+
+int sh_opterr = 1;
+
+/* Set to an option character which was unrecognized.
+ This must be initialized on some systems to avoid linking in the
+ system's own getopt implementation. */
+
+int sh_optopt = '?';
+
+/* Set to 1 when we see an invalid option; public so getopts can reset it. */
+int sh_badopt = 0;
+
+/* Scan elements of ARGV (whose length is ARGC) for option characters
+ given in OPTSTRING.
+
+ If an element of ARGV starts with '-', and is not exactly "-" or "--",
+ then it is an option element. The characters of this element
+ (aside from the initial '-') are option characters. If `sh_getopt'
+ is called repeatedly, it returns successively each of the option characters
+ from each of the option elements.
+
+ If `sh_getopt' finds another option character, it returns that character,
+ updating `sh_optind' and `nextchar' so that the next call to `sh_getopt' can
+ resume the scan with the following option character or ARGV-element.
+
+ If there are no more option characters, `sh_getopt' returns `EOF'.
+ Then `sh_optind' is the index in ARGV of the first ARGV-element
+ that is not an option.
+
+ OPTSTRING is a string containing the legitimate option characters.
+ If an option character is seen that is not listed in OPTSTRING,
+ return '?' after printing an error message. If you set `sh_opterr' to
+ zero, the error message is suppressed but we still return '?'.
+
+ If a char in OPTSTRING is followed by a colon, that means it wants an arg,
+ so the following text in the same ARGV-element, or the text of the following
+ ARGV-element, is returned in `sh_optarg'. */
+
+/* 1003.2 specifies the format of this message. */
+#define BADOPT(x) fprintf (stderr, _("%s: illegal option -- %c\n"), argv[0], x)
+#define NEEDARG(x) fprintf (stderr, _("%s: option requires an argument -- %c\n"), argv[0], x)
+
+int
+sh_getopt (argc, argv, optstring)
+ int argc;
+ char *const *argv;
+ const char *optstring;
+{
+ char c, *temp;
+
+ sh_optarg = 0;
+
+ if (sh_optind >= argc || sh_optind < 0) /* XXX was sh_optind > argc */
+ {
+ sh_optind = argc;
+ return (EOF);
+ }
+
+ /* Initialize the internal data when the first call is made.
+ Start processing options with ARGV-element 1 (since ARGV-element 0
+ is the program name); the sequence of previously skipped
+ non-option ARGV-elements is empty. */
+
+ if (sh_optind == 0)
+ {
+ sh_optind = 1;
+ nextchar = (char *)NULL;
+ }
+
+ if (nextchar == 0 || *nextchar == '\0')
+ {
+ /* If we have done all the ARGV-elements, stop the scan. */
+ if (sh_optind >= argc)
+ return EOF;
+
+ temp = argv[sh_optind];
+
+ /* Special ARGV-element `--' means premature end of options.
+ Skip it like a null option, and return EOF. */
+ if (temp[0] == '-' && temp[1] == '-' && temp[2] == '\0')
+ {
+ sh_optind++;
+ return EOF;
+ }
+
+ /* If we have come to a non-option, either stop the scan or describe
+ it to the caller and pass it by. This makes the pseudo-option
+ `-' mean the end of options, but does not skip over it. */
+ if (temp[0] != '-' || temp[1] == '\0')
+ return EOF;
+
+ /* We have found another option-ARGV-element.
+ Start decoding its characters. */
+ nextchar = argv[sh_curopt = sh_optind] + 1;
+ sh_charindex = 1;
+ }
+
+ /* Look at and handle the next option-character. */
+
+ c = *nextchar++; sh_charindex++;
+ temp = strchr (optstring, c);
+
+ sh_optopt = c;
+
+ /* Increment `sh_optind' when we start to process its last character. */
+ if (nextchar == 0 || *nextchar == '\0')
+ {
+ sh_optind++;
+ nextchar = (char *)NULL;
+ }
+
+ if (sh_badopt = (temp == NULL || c == ':'))
+ {
+ if (sh_opterr)
+ BADOPT (c);
+
+ return '?';
+ }
+
+ if (temp[1] == ':')
+ {
+ if (nextchar && *nextchar)
+ {
+ /* This is an option that requires an argument. */
+ sh_optarg = nextchar;
+ /* If we end this ARGV-element by taking the rest as an arg,
+ we must advance to the next element now. */
+ sh_optind++;
+ }
+ else if (sh_optind == argc)
+ {
+ if (sh_opterr)
+ NEEDARG (c);
+
+ sh_optopt = c;
+ sh_optarg = ""; /* Needed by getopts. */
+ c = (optstring[0] == ':') ? ':' : '?';
+ }
+ else
+ /* We already incremented `sh_optind' once;
+ increment it again when taking next ARGV-elt as argument. */
+ sh_optarg = argv[sh_optind++];
+ nextchar = (char *)NULL;
+ }
+ return c;
+}
+
+void
+sh_getopt_restore_state (argv)
+ char **argv;
+{
+ if (nextchar)
+ nextchar = argv[sh_curopt] + sh_charindex;
+}
+
+sh_getopt_state_t *
+sh_getopt_alloc_istate ()
+{
+ sh_getopt_state_t *ret;
+
+ ret = (sh_getopt_state_t *)xmalloc (sizeof (sh_getopt_state_t));
+ return ret;
+}
+
+void
+sh_getopt_dispose_istate (gs)
+ sh_getopt_state_t *gs;
+{
+ free (gs);
+}
+
+sh_getopt_state_t *
+sh_getopt_save_istate ()
+{
+ sh_getopt_state_t *ret;
+
+ ret = sh_getopt_alloc_istate ();
+
+ ret->gs_optarg = sh_optarg;
+ ret->gs_optind = sh_optind;
+ ret->gs_curopt = sh_curopt;
+ ret->gs_nextchar = nextchar; /* XXX */
+ ret->gs_charindex = sh_charindex;
+ ret->gs_flags = 0; /* XXX for later use */
+
+ return ret;
+}
+
+void
+sh_getopt_restore_istate (state)
+ sh_getopt_state_t *state;
+{
+ sh_optarg = state->gs_optarg;
+ sh_optind = state->gs_optind;
+ sh_curopt = state->gs_curopt;
+ nextchar = state->gs_nextchar; /* XXX - probably not usable */
+ sh_charindex = state->gs_charindex;
+
+ sh_getopt_dispose_istate (state);
+}
+
+#if 0
+void
+sh_getopt_debug_restore_state (argv)
+ char **argv;
+{
+ if (nextchar && nextchar != argv[sh_curopt] + sh_charindex)
+ {
+ itrace("sh_getopt_debug_restore_state: resetting nextchar");
+ nextchar = argv[sh_curopt] + sh_charindex;
+ }
+}
+#endif
+
+#ifdef TEST
+
+/* Compile with -DTEST to make an executable for use in testing
+ the above definition of `sh_getopt'. */
+
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ int c;
+ int digit_sh_optind = 0;
+
+ while (1)
+ {
+ int this_option_sh_optind = sh_optind ? sh_optind : 1;
+
+ c = sh_getopt (argc, argv, "abc:d:0123456789");
+ if (c == EOF)
+ break;
+
+ switch (c)
+ {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if (digit_sh_optind != 0 && digit_sh_optind != this_option_sh_optind)
+ printf ("digits occur in two different argv-elements.\n");
+ digit_sh_optind = this_option_sh_optind;
+ printf ("option %c\n", c);
+ break;
+
+ case 'a':
+ printf ("option a\n");
+ break;
+
+ case 'b':
+ printf ("option b\n");
+ break;
+
+ case 'c':
+ printf ("option c with value `%s'\n", sh_optarg);
+ break;
+
+ case '?':
+ break;
+
+ default:
+ printf ("?? sh_getopt returned character code 0%o ??\n", c);
+ }
+ }
+
+ if (sh_optind < argc)
+ {
+ printf ("non-option ARGV-elements: ");
+ while (sh_optind < argc)
+ printf ("%s ", argv[sh_optind++]);
+ printf ("\n");
+ }
+
+ exit (0);
+}
+
+#endif /* TEST */
diff --git a/builtins/getopt.h b/builtins/getopt.h
new file mode 100644
index 0000000..fd97859
--- /dev/null
+++ b/builtins/getopt.h
@@ -0,0 +1,82 @@
+/* getopt.h - declarations for getopt. */
+
+/* Copyright (C) 1989-2020 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
+
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* XXX THIS HAS BEEN MODIFIED FOR INCORPORATION INTO BASH XXX */
+
+#ifndef _SH_GETOPT_H
+#define _SH_GETOPT_H 1
+
+#include "stdc.h"
+
+/* For communication from `getopt' to the caller.
+ When `getopt' finds an option that takes an argument,
+ the argument value is returned here.
+ Also, when `ordering' is RETURN_IN_ORDER,
+ each non-option ARGV-element is returned here. */
+
+extern char *sh_optarg;
+
+/* Index in ARGV of the next element to be scanned.
+ This is used for communication to and from the caller
+ and for communication between successive calls to `getopt'.
+
+ On entry to `getopt', zero means this is the first call; initialize.
+
+ When `getopt' returns EOF, this is the index of the first of the
+ non-option elements that the caller should itself scan.
+
+ Otherwise, `sh_optind' communicates from one call to the next
+ how much of ARGV has been scanned so far. */
+
+extern int sh_optind;
+
+/* Callers store zero here to inhibit the error message `getopt' prints
+ for unrecognized options. */
+
+extern int sh_opterr;
+
+/* Set to an option character which was unrecognized. */
+
+extern int sh_optopt;
+
+/* Set to 1 when an unrecognized option is encountered. */
+extern int sh_badopt;
+
+extern int sh_getopt PARAMS((int, char *const *, const char *));
+
+typedef struct sh_getopt_state
+{
+ char *gs_optarg;
+ int gs_optind;
+ int gs_curopt;
+ char *gs_nextchar;
+ int gs_charindex;
+ int gs_flags;
+} sh_getopt_state_t;
+
+extern void sh_getopt_restore_state PARAMS((char **));
+
+extern sh_getopt_state_t *sh_getopt_alloc_istate PARAMS((void));
+extern void sh_getopt_dispose_istate PARAMS((sh_getopt_state_t *));
+
+extern sh_getopt_state_t *sh_getopt_save_istate PARAMS((void));
+extern void sh_getopt_restore_istate PARAMS((sh_getopt_state_t *));
+
+#endif /* _SH_GETOPT_H */
diff --git a/builtins/getopts.def b/builtins/getopts.def
new file mode 100644
index 0000000..4c39c47
--- /dev/null
+++ b/builtins/getopts.def
@@ -0,0 +1,343 @@
+This file is getopts.def, from which is created getopts.c.
+It implements the builtin "getopts" in Bash.
+
+Copyright (C) 1987-2019 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES getopts.c
+
+$BUILTIN getopts
+$FUNCTION getopts_builtin
+$SHORT_DOC getopts optstring name [arg ...]
+Parse option arguments.
+
+Getopts is used by shell procedures to parse positional parameters
+as options.
+
+OPTSTRING contains the option letters to be recognized; if a letter
+is followed by a colon, the option is expected to have an argument,
+which should be separated from it by white space.
+
+Each time it is invoked, getopts will place the next option in the
+shell variable $name, initializing name if it does not exist, and
+the index of the next argument to be processed into the shell
+variable OPTIND. OPTIND is initialized to 1 each time the shell or
+a shell script is invoked. When an option requires an argument,
+getopts places that argument into the shell variable OPTARG.
+
+getopts reports errors in one of two ways. If the first character
+of OPTSTRING is a colon, getopts uses silent error reporting. In
+this mode, no error messages are printed. If an invalid option is
+seen, getopts places the option character found into OPTARG. If a
+required argument is not found, getopts places a ':' into NAME and
+sets OPTARG to the option character found. If getopts is not in
+silent mode, and an invalid option is seen, getopts places '?' into
+NAME and unsets OPTARG. If a required argument is not found, a '?'
+is placed in NAME, OPTARG is unset, and a diagnostic message is
+printed.
+
+If the shell variable OPTERR has the value 0, getopts disables the
+printing of error messages, even if the first character of
+OPTSTRING is not a colon. OPTERR has the value 1 by default.
+
+Getopts normally parses the positional parameters, but if arguments
+are supplied as ARG values, they are parsed instead.
+
+Exit Status:
+Returns success if an option is found; fails if the end of options is
+encountered or an error occurs.
+$END
+
+#include <config.h>
+
+#include <stdio.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#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/builtins/hash.def b/builtins/hash.def
new file mode 100644
index 0000000..03ae547
--- /dev/null
+++ b/builtins/hash.def
@@ -0,0 +1,305 @@
+This file is hash.def, from which is created hash.c.
+It implements the builtin "hash" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES hash.c
+
+$BUILTIN hash
+$FUNCTION hash_builtin
+$SHORT_DOC hash [-lr] [-p pathname] [-dt] [name ...]
+Remember or display program locations.
+
+Determine and remember the full pathname of each command NAME. If
+no arguments are given, information about remembered commands is displayed.
+
+Options:
+ -d forget the remembered location of each NAME
+ -l display in a format that may be reused as input
+ -p pathname use PATHNAME as the full pathname of NAME
+ -r forget all remembered locations
+ -t print the remembered location of each NAME, preceding
+ each location with the corresponding NAME if multiple
+ NAMEs are given
+Arguments:
+ NAME Each NAME is searched for in $PATH and added to the list
+ of remembered commands.
+
+Exit Status:
+Returns success unless NAME is not found or an invalid option is given.
+$END
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include "../bashtypes.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <errno.h>
+
+#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/builtins/help.def b/builtins/help.def
new file mode 100644
index 0000000..346d93f
--- /dev/null
+++ b/builtins/help.def
@@ -0,0 +1,552 @@
+This file is help.def, from which is created help.c.
+It implements the builtin "help" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES help.c
+
+$BUILTIN help
+$FUNCTION help_builtin
+$DEPENDS_ON HELP_BUILTIN
+$SHORT_DOC help [-dms] [pattern ...]
+Display information about builtin commands.
+
+Displays brief summaries of builtin commands. If PATTERN is
+specified, gives detailed help on all commands matching PATTERN,
+otherwise the list of help topics is printed.
+
+Options:
+ -d output short description for each topic
+ -m display usage in pseudo-manpage format
+ -s output only a short usage synopsis for each topic matching
+ PATTERN
+
+Arguments:
+ PATTERN Pattern specifying a help topic
+
+Exit Status:
+Returns success unless PATTERN is not found or an invalid option is given.
+$END
+
+#include <config.h>
+
+#if defined (HELP_BUILTIN)
+#include <stdio.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <errno.h>
+
+#include <filecntl.h>
+#include <stddef.h>
+
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "../builtins.h"
+#include "../execute_cmd.h"
+#include "../pathexp.h"
+#include "common.h"
+#include "bashgetopt.h"
+
+#include <glob/strmatch.h>
+#include <glob/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/builtins/history.def b/builtins/history.def
new file mode 100644
index 0000000..6918d8a
--- /dev/null
+++ b/builtins/history.def
@@ -0,0 +1,464 @@
+This file is history.def, from which is created history.c.
+It implements the builtin "history" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES history.c
+
+$BUILTIN history
+$FUNCTION history_builtin
+$DEPENDS_ON HISTORY
+$SHORT_DOC history [-c] [-d offset] [n] or history -anrw [filename] or history -ps arg [arg...]
+Display or manipulate the history list.
+
+Display the history list with line numbers, prefixing each modified
+entry with a `*'. An argument of N lists only the last N entries.
+
+Options:
+ -c clear the history list by deleting all of the entries
+ -d offset delete the history entry at position OFFSET. Negative
+ offsets count back from the end of the history list
+
+ -a append history lines from this session to the history file
+ -n read all history lines not already read from the history file
+ and append them to the history list
+ -r read the history file and append the contents to the history
+ list
+ -w write the current history to the history file
+
+ -p perform history expansion on each ARG and display the result
+ without storing it in the history list
+ -s append the ARGs to the history list as a single entry
+
+If FILENAME is given, it is used as the history file. Otherwise,
+if HISTFILE has a value, that is used, else ~/.bash_history.
+
+If the HISTTIMEFORMAT variable is set and not null, its value is used
+as a format string for strftime(3) to print the time stamp associated
+with each displayed history entry. No time stamps are printed otherwise.
+
+Exit Status:
+Returns success unless an invalid option is given or an error occurs.
+$END
+
+#include <config.h>
+
+#if defined (HISTORY)
+#include "../bashtypes.h"
+#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H)
+# include <sys/file.h>
+#endif
+#include "posixstat.h"
+#include "filecntl.h"
+#include <errno.h>
+#include <stdio.h>
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "../flags.h"
+#include "../parser.h"
+#include "../bashhist.h"
+#include <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/builtins/jobs.def b/builtins/jobs.def
new file mode 100644
index 0000000..1ce098d
--- /dev/null
+++ b/builtins/jobs.def
@@ -0,0 +1,300 @@
+This file is jobs.def, from which is created jobs.c.
+It implements the builtins "jobs" and "disown" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES jobs.c
+
+$BUILTIN jobs
+$FUNCTION jobs_builtin
+$DEPENDS_ON JOB_CONTROL
+$SHORT_DOC jobs [-lnprs] [jobspec ...] or jobs -x command [args]
+Display status of jobs.
+
+Lists the active jobs. JOBSPEC restricts output to that job.
+Without options, the status of all active jobs is displayed.
+
+Options:
+ -l lists process IDs in addition to the normal information
+ -n lists only processes that have changed status since the last
+ notification
+ -p lists process IDs only
+ -r restrict output to running jobs
+ -s restrict output to stopped jobs
+
+If -x is supplied, COMMAND is run after all job specifications that
+appear in ARGS have been replaced with the process ID of that job's
+process group leader.
+
+Exit Status:
+Returns success unless an invalid option is given or an error occurs.
+If -x is used, returns the exit status of COMMAND.
+$END
+
+#include <config.h>
+
+#if defined (JOB_CONTROL)
+#include "../bashtypes.h"
+#include <signal.h>
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#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 */
+
+$BUILTIN disown
+$FUNCTION disown_builtin
+$DEPENDS_ON JOB_CONTROL
+$SHORT_DOC disown [-h] [-ar] [jobspec ... | pid ...]
+Remove jobs from current shell.
+
+Removes each JOBSPEC argument from the table of active jobs. Without
+any JOBSPECs, the shell uses its notion of the current job.
+
+Options:
+ -a remove all jobs if JOBSPEC is not supplied
+ -h mark each JOBSPEC so that SIGHUP is not sent to the job if the
+ shell receives a SIGHUP
+ -r remove only running jobs
+
+Exit Status:
+Returns success unless an invalid option or JOBSPEC is given.
+$END
+
+#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/builtins/kill.def b/builtins/kill.def
new file mode 100644
index 0000000..c655092
--- /dev/null
+++ b/builtins/kill.def
@@ -0,0 +1,276 @@
+This file is kill.def, from which is created kill.c.
+It implements the builtin "kill" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES kill.c
+
+$BUILTIN kill
+$FUNCTION kill_builtin
+$SHORT_DOC kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
+Send a signal to a job.
+
+Send the processes identified by PID or JOBSPEC the signal named by
+SIGSPEC or SIGNUM. If neither SIGSPEC nor SIGNUM is present, then
+SIGTERM is assumed.
+
+Options:
+ -s sig SIG is a signal name
+ -n sig SIG is a signal number
+ -l list the signal names; if arguments follow `-l' they are
+ assumed to be signal numbers for which names should be listed
+ -L synonym for -l
+
+Kill is a shell builtin for two reasons: it allows job IDs to be used
+instead of process IDs, and allows processes to be killed if the limit
+on processes that you can create is reached.
+
+Exit Status:
+Returns success unless an invalid option is given or an error occurs.
+$END
+
+#include <config.h>
+
+#include <stdio.h>
+#include <errno.h>
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include <signal.h>
+
+#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/builtins/let.def b/builtins/let.def
new file mode 100644
index 0000000..d090a45
--- /dev/null
+++ b/builtins/let.def
@@ -0,0 +1,131 @@
+This file is let.def, from which is created let.c.
+It implements the builtin "let" in Bash.
+
+Copyright (C) 1987-2009 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$BUILTIN let
+$FUNCTION let_builtin
+$PRODUCES let.c
+$SHORT_DOC let arg [arg ...]
+Evaluate arithmetic expressions.
+
+Evaluate each ARG as an arithmetic expression. Evaluation is done in
+fixed-width integers with no check for overflow, though division by 0
+is trapped and flagged as an error. The following list of operators is
+grouped into levels of equal-precedence operators. The levels are listed
+in order of decreasing precedence.
+
+ id++, id-- variable post-increment, post-decrement
+ ++id, --id variable pre-increment, pre-decrement
+ -, + unary minus, plus
+ !, ~ logical and bitwise negation
+ ** exponentiation
+ *, /, % multiplication, division, remainder
+ +, - addition, subtraction
+ <<, >> left and right bitwise shifts
+ <=, >=, <, > comparison
+ ==, != equality, inequality
+ & bitwise AND
+ ^ bitwise XOR
+ | bitwise OR
+ && logical AND
+ || logical OR
+ expr ? expr : expr
+ conditional operator
+ =, *=, /=, %=,
+ +=, -=, <<=, >>=,
+ &=, ^=, |= assignment
+
+Shell variables are allowed as operands. The name of the variable
+is replaced by its value (coerced to a fixed-width integer) within
+an expression. The variable need not have its integer attribute
+turned on to be used in an expression.
+
+Operators are evaluated in order of precedence. Sub-expressions in
+parentheses are evaluated first and may override the precedence
+rules above.
+
+Exit Status:
+If the last ARG evaluates to 0, let returns 1; let returns 0 otherwise.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#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/builtins/mapfile.def b/builtins/mapfile.def
new file mode 100644
index 0000000..4ddd723
--- /dev/null
+++ b/builtins/mapfile.def
@@ -0,0 +1,364 @@
+This file is mapfile.def, from which is created mapfile.c.
+It implements the builtin "mapfile" in Bash.
+
+Copyright (C) 2005-2006 Rocky Bernstein for Free Software Foundation, Inc.
+Copyright (C) 2008-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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES mapfile.c
+
+$BUILTIN mapfile
+$FUNCTION mapfile_builtin
+$SHORT_DOC mapfile [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
+Read lines from the standard input into an indexed array variable.
+
+Read lines from the standard input into the indexed array variable ARRAY, or
+from file descriptor FD if the -u option is supplied. The variable MAPFILE
+is the default ARRAY.
+
+Options:
+ -d delim Use DELIM to terminate lines, instead of newline
+ -n count Copy at most COUNT lines. If COUNT is 0, all lines are copied
+ -O origin Begin assigning to ARRAY at index ORIGIN. The default index is 0
+ -s count Discard the first COUNT lines read
+ -t Remove a trailing DELIM from each line read (default newline)
+ -u fd Read lines from file descriptor FD instead of the standard input
+ -C callback Evaluate CALLBACK each time QUANTUM lines are read
+ -c quantum Specify the number of lines read between each call to
+ CALLBACK
+
+Arguments:
+ ARRAY Array variable name to use for file data
+
+If -C is supplied without -c, the default quantum is 5000. When
+CALLBACK is evaluated, it is supplied the index of the next array
+element to be assigned and the line to be assigned to that element
+as additional arguments.
+
+If not supplied with an explicit origin, mapfile will clear ARRAY before
+assigning to it.
+
+Exit Status:
+Returns success unless an invalid option is given or ARRAY is readonly or
+not an indexed array.
+$END
+
+$BUILTIN readarray
+$FUNCTION mapfile_builtin
+$SHORT_DOC readarray [-d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array]
+Read lines from a file into an array variable.
+
+A synonym for `mapfile'.
+$END
+
+#include <config.h>
+
+#include "builtins.h"
+#include "../bashtypes.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include "bashansi.h"
+#include "bashintl.h"
+
+#include <stdio.h>
+#include <errno.h>
+
+#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/builtins/mkbuiltins.c b/builtins/mkbuiltins.c
new file mode 100644
index 0000000..f505ebd
--- /dev/null
+++ b/builtins/mkbuiltins.c
@@ -0,0 +1,1692 @@
+/* mkbuiltins.c - Create builtins.c, builtext.h, and builtdoc.c from
+ a single source file called builtins.def. */
+
+/* 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 <http://www.gnu.org/licenses/>.
+*/
+
+#if !defined (CROSS_COMPILING)
+# include <config.h>
+#else /* CROSS_COMPILING */
+/* A conservative set of defines based on POSIX/SUS3/XPG6 */
+# define HAVE_UNISTD_H
+# define HAVE_STRING_H
+# define HAVE_STDLIB_H
+
+# define HAVE_RENAME
+#endif /* CROSS_COMPILING */
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#ifndef _MINIX
+# include "../bashtypes.h"
+# if defined (HAVE_SYS_FILE_H)
+# include <sys/file.h>
+# endif
+#endif
+
+#include "posixstat.h"
+#include "filecntl.h"
+
+#include "../bashansi.h"
+#include <stdio.h>
+#include <errno.h>
+
+#include "stdc.h"
+
+#define DOCFILE "builtins.texi"
+
+#ifndef errno
+extern int errno;
+#endif
+
+static char *xmalloc (), *xrealloc ();
+
+#if !defined (__STDC__) && !defined (strcpy)
+extern char *strcpy ();
+#endif /* !__STDC__ && !strcpy */
+
+#define savestring(x) strcpy (xmalloc (1 + strlen (x)), (x))
+#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
+
+/* Flag values that builtins can have. */
+#define BUILTIN_FLAG_SPECIAL 0x01
+#define BUILTIN_FLAG_ASSIGNMENT 0x02
+#define BUILTIN_FLAG_LOCALVAR 0x04
+#define BUILTIN_FLAG_POSIX_BUILTIN 0x08
+#define BUILTIN_FLAG_ARRAYREF_ARG 0x10
+
+#define BASE_INDENT 4
+
+/* If this stream descriptor is non-zero, then write
+ texinfo documentation to it. */
+FILE *documentation_file = (FILE *)NULL;
+
+/* Non-zero means to only produce documentation. */
+int only_documentation = 0;
+
+/* Non-zero means to not do any productions. */
+int inhibit_production = 0;
+
+/* Non-zero means to not add functions (xxx_builtin) to the members of the
+ produced `struct builtin []' */
+int inhibit_functions = 0;
+
+/* Non-zero means to produce separate help files for each builtin, named by
+ the builtin name, in `./helpfiles'. */
+int separate_helpfiles = 0;
+
+/* Non-zero means to create single C strings for each `longdoc', with
+ embedded newlines, for ease of translation. */
+int single_longdoc_strings = 1;
+
+/* The name of a directory into which the separate external help files will
+ eventually be installed. */
+char *helpfile_directory;
+
+/* The name of a directory to precede the filename when reporting
+ errors. */
+char *error_directory = (char *)NULL;
+
+/* The name of the structure file. */
+char *struct_filename = (char *)NULL;
+
+/* The name of the external declaration file. */
+char *extern_filename = (char *)NULL;
+
+/* The name of the include file to write into the structure file, if it's
+ different from extern_filename. */
+char *include_filename = (char *)NULL;
+
+/* The name of the include file to put into the generated struct filename. */
+
+/* Here is a structure for manipulating arrays of data. */
+typedef struct {
+ int size; /* Number of slots allocated to array. */
+ int sindex; /* Current location in array. */
+ int width; /* Size of each element. */
+ int growth_rate; /* How fast to grow. */
+ char **array; /* The array itself. */
+} ARRAY;
+
+/* Here is a structure defining a single BUILTIN. */
+typedef struct {
+ char *name; /* The name of this builtin. */
+ char *function; /* The name of the function to call. */
+ char *shortdoc; /* The short documentation for this builtin. */
+ char *docname; /* Possible name for documentation string. */
+ ARRAY *longdoc; /* The long documentation for this builtin. */
+ ARRAY *dependencies; /* Null terminated array of #define names. */
+ int flags; /* Flags for this builtin. */
+} BUILTIN_DESC;
+
+/* Here is a structure which defines a DEF file. */
+typedef struct {
+ char *filename; /* The name of the input def file. */
+ ARRAY *lines; /* The contents of the file. */
+ int line_number; /* The current line number. */
+ char *production; /* The name of the production file. */
+ FILE *output; /* Open file stream for PRODUCTION. */
+ ARRAY *builtins; /* Null terminated array of BUILTIN_DESC *. */
+} DEF_FILE;
+
+/* The array of all builtins encountered during execution of this code. */
+ARRAY *saved_builtins = (ARRAY *)NULL;
+
+/* The Posix.2 so-called `special' builtins. */
+char *special_builtins[] =
+{
+ ":", ".", "source", "break", "continue", "eval", "exec", "exit",
+ "export", "readonly", "return", "set", "shift", "times", "trap", "unset",
+ (char *)NULL
+};
+
+/* The builtin commands that take assignment statements as arguments. */
+char *assignment_builtins[] =
+{
+ "alias", "declare", "export", "local", "readonly", "typeset",
+ (char *)NULL
+};
+
+char *localvar_builtins[] =
+{
+ "declare", "local", "typeset", (char *)NULL
+};
+
+/* The builtin commands that are special to the POSIX search order. */
+char *posix_builtins[] =
+{
+ "alias", "bg", "cd", "command", "false", "fc", "fg", "getopts", "jobs",
+ "kill", "newgrp", "pwd", "read", "true", "umask", "unalias", "wait",
+ (char *)NULL
+};
+
+/* The builtin commands that can take array references as arguments and pay
+ attention to `assoc_expand_once'. These are the ones that don't assign
+ values, but need to avoid double expansions. */
+char *arrayvar_builtins[] =
+{
+ "declare", "let", "local", "printf", "read", "test", "[",
+ "typeset", "unset", "wait", /*]*/
+ (char *)NULL
+};
+
+/* Forward declarations. */
+static int is_special_builtin ();
+static int is_assignment_builtin ();
+static int is_localvar_builtin ();
+static int is_posix_builtin ();
+static int is_arrayvar_builtin ();
+
+#if !defined (HAVE_RENAME)
+static int rename ();
+#endif
+
+void extract_info ();
+
+void file_error ();
+void line_error ();
+
+void write_file_headers ();
+void write_file_footers ();
+void write_ifdefs ();
+void write_endifs ();
+void write_documentation ();
+void write_longdocs ();
+void write_builtins ();
+
+int write_helpfiles ();
+
+void free_defs ();
+void add_documentation ();
+
+void must_be_building ();
+void remove_trailing_whitespace ();
+
+#define document_name(b) ((b)->docname ? (b)->docname : (b)->name)
+
+
+/* For each file mentioned on the command line, process it and
+ write the information to STRUCTFILE and EXTERNFILE, while
+ creating the production file if necessary. */
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ int arg_index = 1;
+ FILE *structfile, *externfile;
+ char *documentation_filename, *temp_struct_filename;
+
+ structfile = externfile = (FILE *)NULL;
+ documentation_filename = DOCFILE;
+ temp_struct_filename = (char *)NULL;
+
+ while (arg_index < argc && argv[arg_index][0] == '-')
+ {
+ char *arg = argv[arg_index++];
+
+ if (strcmp (arg, "-externfile") == 0)
+ extern_filename = argv[arg_index++];
+ else if (strcmp (arg, "-includefile") == 0)
+ include_filename = argv[arg_index++];
+ else if (strcmp (arg, "-structfile") == 0)
+ struct_filename = argv[arg_index++];
+ else if (strcmp (arg, "-noproduction") == 0)
+ inhibit_production = 1;
+ else if (strcmp (arg, "-nofunctions") == 0)
+ inhibit_functions = 1;
+ else if (strcmp (arg, "-document") == 0)
+ documentation_file = fopen (documentation_filename, "w");
+ else if (strcmp (arg, "-D") == 0)
+ {
+ int len;
+
+ if (error_directory)
+ free (error_directory);
+
+ error_directory = xmalloc (2 + strlen (argv[arg_index]));
+ strcpy (error_directory, argv[arg_index]);
+ len = strlen (error_directory);
+
+ if (len && error_directory[len - 1] != '/')
+ strcat (error_directory, "/");
+
+ arg_index++;
+ }
+ else if (strcmp (arg, "-documentonly") == 0)
+ {
+ only_documentation = 1;
+ documentation_file = fopen (documentation_filename, "w");
+ }
+ else if (strcmp (arg, "-H") == 0)
+ {
+ separate_helpfiles = 1;
+ helpfile_directory = argv[arg_index++];
+ }
+ else if (strcmp (arg, "-S") == 0)
+ single_longdoc_strings = 0;
+ else
+ {
+ fprintf (stderr, "%s: Unknown flag %s.\n", argv[0], arg);
+ exit (2);
+ }
+ }
+
+ if (include_filename == 0)
+ include_filename = extern_filename;
+
+ /* If there are no files to process, just quit now. */
+ if (arg_index == argc)
+ exit (0);
+
+ if (!only_documentation)
+ {
+ /* Open the files. */
+ if (struct_filename)
+ {
+ temp_struct_filename = xmalloc (15);
+ sprintf (temp_struct_filename, "mk-%ld", (long) getpid ());
+ structfile = fopen (temp_struct_filename, "w");
+
+ if (!structfile)
+ file_error (temp_struct_filename);
+ }
+
+ if (extern_filename)
+ {
+ externfile = fopen (extern_filename, "w");
+
+ if (!externfile)
+ file_error (extern_filename);
+ }
+
+ /* Write out the headers. */
+ write_file_headers (structfile, externfile);
+ }
+
+ if (documentation_file)
+ {
+ fprintf (documentation_file, "@c Table of builtins created with %s.\n",
+ argv[0]);
+ fprintf (documentation_file, "@ftable @asis\n");
+ }
+
+ /* Process the .def files. */
+ while (arg_index < argc)
+ {
+ register char *arg;
+
+ arg = argv[arg_index++];
+
+ extract_info (arg, structfile, externfile);
+ }
+
+ /* Close the files. */
+ if (!only_documentation)
+ {
+ /* Write the footers. */
+ write_file_footers (structfile, externfile);
+
+ if (structfile)
+ {
+ write_longdocs (structfile, saved_builtins);
+ fclose (structfile);
+ rename (temp_struct_filename, struct_filename);
+ }
+
+ if (externfile)
+ fclose (externfile);
+ }
+
+#if 0
+ /* This is now done by a different program */
+ if (separate_helpfiles)
+ {
+ write_helpfiles (saved_builtins);
+ }
+#endif
+
+ if (documentation_file)
+ {
+ fprintf (documentation_file, "@end ftable\n");
+ fclose (documentation_file);
+ }
+
+ exit (0);
+}
+
+/* **************************************************************** */
+/* */
+/* Array Functions and Manipulators */
+/* */
+/* **************************************************************** */
+
+/* Make a new array, and return a pointer to it. The array will
+ contain elements of size WIDTH, and is initialized to no elements. */
+ARRAY *
+array_create (width)
+ int width;
+{
+ ARRAY *array;
+
+ array = (ARRAY *)xmalloc (sizeof (ARRAY));
+ array->size = 0;
+ array->sindex = 0;
+ array->width = width;
+
+ /* Default to increasing size in units of 20. */
+ array->growth_rate = 20;
+
+ array->array = (char **)NULL;
+
+ return (array);
+}
+
+/* Copy the array of strings in ARRAY. */
+ARRAY *
+copy_string_array (array)
+ ARRAY *array;
+{
+ register int i;
+ ARRAY *copy;
+
+ if (!array)
+ return (ARRAY *)NULL;
+
+ copy = array_create (sizeof (char *));
+
+ copy->size = array->size;
+ copy->sindex = array->sindex;
+ copy->width = array->width;
+
+ copy->array = (char **)xmalloc ((1 + array->sindex) * sizeof (char *));
+
+ for (i = 0; i < array->sindex; i++)
+ copy->array[i] = savestring (array->array[i]);
+
+ copy->array[i] = (char *)NULL;
+
+ return (copy);
+}
+
+/* Add ELEMENT to ARRAY, growing the array if necessary. */
+void
+array_add (element, array)
+ char *element;
+ ARRAY *array;
+{
+ if (array->sindex + 2 > array->size)
+ array->array = (char **)xrealloc
+ (array->array, (array->size += array->growth_rate) * array->width);
+
+ array->array[array->sindex++] = element;
+ array->array[array->sindex] = (char *)NULL;
+}
+
+/* Free an allocated array and data pointer. */
+void
+array_free (array)
+ ARRAY *array;
+{
+ if (array->array)
+ free (array->array);
+
+ free (array);
+}
+
+/* **************************************************************** */
+/* */
+/* Processing a DEF File */
+/* */
+/* **************************************************************** */
+
+/* The definition of a function. */
+typedef int Function ();
+typedef int mk_handler_func_t PARAMS((char *, DEF_FILE *, char *));
+
+/* Structure handles processor directives. */
+typedef struct {
+ char *directive;
+ mk_handler_func_t *function;
+} HANDLER_ENTRY;
+
+extern int builtin_handler PARAMS((char *, DEF_FILE *, char *));
+extern int function_handler PARAMS((char *, DEF_FILE *, char *));
+extern int short_doc_handler PARAMS((char *, DEF_FILE *, char *));
+extern int comment_handler PARAMS((char *, DEF_FILE *, char *));
+extern int depends_on_handler PARAMS((char *, DEF_FILE *, char *));
+extern int produces_handler PARAMS((char *, DEF_FILE *, char *));
+extern int end_handler PARAMS((char *, DEF_FILE *, char *));
+extern int docname_handler PARAMS((char *, DEF_FILE *, char *));
+
+HANDLER_ENTRY handlers[] = {
+ { "BUILTIN", builtin_handler },
+ { "DOCNAME", docname_handler },
+ { "FUNCTION", function_handler },
+ { "SHORT_DOC", short_doc_handler },
+ { "$", comment_handler },
+ { "COMMENT", comment_handler },
+ { "DEPENDS_ON", depends_on_handler },
+ { "PRODUCES", produces_handler },
+ { "END", end_handler },
+ { (char *)NULL, (mk_handler_func_t *)NULL }
+};
+
+/* Return the entry in the table of handlers for NAME. */
+HANDLER_ENTRY *
+find_directive (directive)
+ char *directive;
+{
+ register int i;
+
+ for (i = 0; handlers[i].directive; i++)
+ if (strcmp (handlers[i].directive, directive) == 0)
+ return (&handlers[i]);
+
+ return ((HANDLER_ENTRY *)NULL);
+}
+
+/* Non-zero indicates that a $BUILTIN has been seen, but not
+ the corresponding $END. */
+static int building_builtin = 0;
+
+/* Non-zero means to output cpp line and file information before
+ printing the current line to the production file. */
+int output_cpp_line_info = 0;
+
+/* The main function of this program. Read FILENAME and act on what is
+ found. Lines not starting with a dollar sign are copied to the
+ $PRODUCES target, if one is present. Lines starting with a dollar sign
+ are directives to this program, specifying the name of the builtin, the
+ function to call, the short documentation and the long documentation
+ strings. FILENAME can contain multiple $BUILTINs, but only one $PRODUCES
+ target. After the file has been processed, write out the names of
+ builtins found in each $BUILTIN. Plain text found before the $PRODUCES
+ is ignored, as is "$$ comment text". */
+void
+extract_info (filename, structfile, externfile)
+ char *filename;
+ FILE *structfile, *externfile;
+{
+ register int i;
+ DEF_FILE *defs;
+ struct stat finfo;
+ size_t file_size;
+ char *buffer, *line;
+ int fd, nr;
+
+ if (stat (filename, &finfo) == -1)
+ file_error (filename);
+
+ fd = open (filename, O_RDONLY, 0666);
+
+ if (fd == -1)
+ file_error (filename);
+
+ file_size = (size_t)finfo.st_size;
+ buffer = xmalloc (1 + file_size);
+
+ if ((nr = read (fd, buffer, file_size)) < 0)
+ file_error (filename);
+
+ /* This is needed on WIN32, and does not hurt on Unix. */
+ if (nr < file_size)
+ file_size = nr;
+
+ close (fd);
+
+ if (nr == 0)
+ {
+ fprintf (stderr, "mkbuiltins: %s: skipping zero-length file\n", filename);
+ free (buffer);
+ return;
+ }
+
+ /* Create and fill in the initial structure describing this file. */
+ defs = (DEF_FILE *)xmalloc (sizeof (DEF_FILE));
+ defs->filename = filename;
+ defs->lines = array_create (sizeof (char *));
+ defs->line_number = 0;
+ defs->production = (char *)NULL;
+ defs->output = (FILE *)NULL;
+ defs->builtins = (ARRAY *)NULL;
+
+ /* Build the array of lines. */
+ i = 0;
+ while (i < file_size)
+ {
+ array_add (&buffer[i], defs->lines);
+
+ while (i < file_size && buffer[i] != '\n')
+ i++;
+ buffer[i++] = '\0';
+ }
+
+ /* Begin processing the input file. We don't write any output
+ until we have a file to write output to. */
+ output_cpp_line_info = 1;
+
+ /* Process each line in the array. */
+ for (i = 0; line = defs->lines->array[i]; i++)
+ {
+ defs->line_number = i;
+
+ if (*line == '$')
+ {
+ register int j;
+ char *directive;
+ HANDLER_ENTRY *handler;
+
+ /* Isolate the directive. */
+ for (j = 0; line[j] && !whitespace (line[j]); j++);
+
+ directive = xmalloc (j);
+ strncpy (directive, line + 1, j - 1);
+ directive[j -1] = '\0';
+
+ /* Get the function handler and call it. */
+ handler = find_directive (directive);
+
+ if (!handler)
+ {
+ line_error (defs, "Unknown directive `%s'", directive);
+ free (directive);
+ continue;
+ }
+ else
+ {
+ /* Advance to the first non-whitespace character. */
+ while (whitespace (line[j]))
+ j++;
+
+ /* Call the directive handler with the FILE, and ARGS. */
+ (*(handler->function)) (directive, defs, line + j);
+ }
+ free (directive);
+ }
+ else
+ {
+ if (building_builtin)
+ add_documentation (defs, line);
+ else if (defs->output)
+ {
+ if (output_cpp_line_info)
+ {
+ /* If we're handed an absolute pathname, don't prepend
+ the directory name. */
+ if (defs->filename[0] == '/')
+ fprintf (defs->output, "#line %d \"%s\"\n",
+ defs->line_number + 1, defs->filename);
+ else
+ fprintf (defs->output, "#line %d \"%s%s\"\n",
+ defs->line_number + 1,
+ error_directory ? error_directory : "./",
+ defs->filename);
+ output_cpp_line_info = 0;
+ }
+
+ fprintf (defs->output, "%s\n", line);
+ }
+ }
+ }
+
+ /* Close the production file. */
+ if (defs->output)
+ fclose (defs->output);
+
+ /* The file has been processed. Write the accumulated builtins to
+ the builtins.c file, and write the extern definitions to the
+ builtext.h file. */
+ write_builtins (defs, structfile, externfile);
+
+ free (buffer);
+ free_defs (defs);
+}
+
+#define free_safely(x) if (x) free (x)
+
+static void
+free_builtin (builtin)
+ BUILTIN_DESC *builtin;
+{
+ register int i;
+
+ free_safely (builtin->name);
+ free_safely (builtin->function);
+ free_safely (builtin->shortdoc);
+ free_safely (builtin->docname);
+
+ if (builtin->longdoc)
+ array_free (builtin->longdoc);
+
+ if (builtin->dependencies)
+ {
+ for (i = 0; builtin->dependencies->array[i]; i++)
+ free (builtin->dependencies->array[i]);
+ array_free (builtin->dependencies);
+ }
+}
+
+/* Free all of the memory allocated to a DEF_FILE. */
+void
+free_defs (defs)
+ DEF_FILE *defs;
+{
+ register int i;
+ register BUILTIN_DESC *builtin;
+
+ if (defs->production)
+ free (defs->production);
+
+ if (defs->lines)
+ array_free (defs->lines);
+
+ if (defs->builtins)
+ {
+ for (i = 0; builtin = (BUILTIN_DESC *)defs->builtins->array[i]; i++)
+ {
+ free_builtin (builtin);
+ free (builtin);
+ }
+ array_free (defs->builtins);
+ }
+ free (defs);
+}
+
+/* **************************************************************** */
+/* */
+/* The Handler Functions Themselves */
+/* */
+/* **************************************************************** */
+
+/* Strip surrounding whitespace from STRING, and
+ return a pointer to the start of it. */
+char *
+strip_whitespace (string)
+ char *string;
+{
+ while (whitespace (*string))
+ string++;
+
+ remove_trailing_whitespace (string);
+ return (string);
+}
+
+/* Remove only the trailing whitespace from STRING. */
+void
+remove_trailing_whitespace (string)
+ char *string;
+{
+ register int i;
+
+ i = strlen (string) - 1;
+
+ while (i > 0 && whitespace (string[i]))
+ i--;
+
+ string[++i] = '\0';
+}
+
+/* Ensure that there is a argument in STRING and return it.
+ FOR_WHOM is the name of the directive which needs the argument.
+ DEFS is the DEF_FILE in which the directive is found.
+ If there is no argument, produce an error. */
+char *
+get_arg (for_whom, defs, string)
+ char *for_whom, *string;
+ DEF_FILE *defs;
+{
+ char *new;
+
+ new = strip_whitespace (string);
+
+ if (!*new)
+ line_error (defs, "%s requires an argument", for_whom);
+
+ return (savestring (new));
+}
+
+/* Error if not building a builtin. */
+void
+must_be_building (directive, defs)
+ char *directive;
+ DEF_FILE *defs;
+{
+ if (!building_builtin)
+ line_error (defs, "%s must be inside of a $BUILTIN block", directive);
+}
+
+/* Return the current builtin. */
+BUILTIN_DESC *
+current_builtin (directive, defs)
+ char *directive;
+ DEF_FILE *defs;
+{
+ must_be_building (directive, defs);
+ if (defs->builtins)
+ return ((BUILTIN_DESC *)defs->builtins->array[defs->builtins->sindex - 1]);
+ else
+ return ((BUILTIN_DESC *)NULL);
+}
+
+/* Add LINE to the long documentation for the current builtin.
+ Ignore blank lines until the first non-blank line has been seen. */
+void
+add_documentation (defs, line)
+ DEF_FILE *defs;
+ char *line;
+{
+ register BUILTIN_DESC *builtin;
+
+ builtin = current_builtin ("(implied LONGDOC)", defs);
+
+ remove_trailing_whitespace (line);
+
+ if (!*line && !builtin->longdoc)
+ return;
+
+ if (!builtin->longdoc)
+ builtin->longdoc = array_create (sizeof (char *));
+
+ array_add (line, builtin->longdoc);
+}
+
+/* How to handle the $BUILTIN directive. */
+int
+builtin_handler (self, defs, arg)
+ char *self;
+ DEF_FILE *defs;
+ char *arg;
+{
+ BUILTIN_DESC *new;
+ char *name;
+
+ /* If we are already building a builtin, we cannot start a new one. */
+ if (building_builtin)
+ {
+ line_error (defs, "%s found before $END", self);
+ return (-1);
+ }
+
+ output_cpp_line_info++;
+
+ /* Get the name of this builtin, and stick it in the array. */
+ name = get_arg (self, defs, arg);
+
+ /* If this is the first builtin, create the array to hold them. */
+ if (!defs->builtins)
+ defs->builtins = array_create (sizeof (BUILTIN_DESC *));
+
+ new = (BUILTIN_DESC *)xmalloc (sizeof (BUILTIN_DESC));
+ new->name = name;
+ new->function = (char *)NULL;
+ new->shortdoc = (char *)NULL;
+ new->docname = (char *)NULL;
+ new->longdoc = (ARRAY *)NULL;
+ new->dependencies = (ARRAY *)NULL;
+ new->flags = 0;
+
+ if (is_special_builtin (name))
+ new->flags |= BUILTIN_FLAG_SPECIAL;
+ if (is_assignment_builtin (name))
+ new->flags |= BUILTIN_FLAG_ASSIGNMENT;
+ if (is_localvar_builtin (name))
+ new->flags |= BUILTIN_FLAG_LOCALVAR;
+ if (is_posix_builtin (name))
+ new->flags |= BUILTIN_FLAG_POSIX_BUILTIN;
+ if (is_arrayvar_builtin (name))
+ new->flags |= BUILTIN_FLAG_ARRAYREF_ARG;
+
+ array_add ((char *)new, defs->builtins);
+ building_builtin = 1;
+
+ return (0);
+}
+
+/* How to handle the $FUNCTION directive. */
+int
+function_handler (self, defs, arg)
+ char *self;
+ DEF_FILE *defs;
+ char *arg;
+{
+ register BUILTIN_DESC *builtin;
+
+ builtin = current_builtin (self, defs);
+
+ if (builtin == 0)
+ {
+ line_error (defs, "syntax error: no current builtin for $FUNCTION directive");
+ exit (1);
+ }
+ if (builtin->function)
+ line_error (defs, "%s already has a function (%s)",
+ builtin->name, builtin->function);
+ else
+ builtin->function = get_arg (self, defs, arg);
+
+ return (0);
+}
+
+/* How to handle the $DOCNAME directive. */
+int
+docname_handler (self, defs, arg)
+ char *self;
+ DEF_FILE *defs;
+ char *arg;
+{
+ register BUILTIN_DESC *builtin;
+
+ builtin = current_builtin (self, defs);
+
+ if (builtin->docname)
+ line_error (defs, "%s already had a docname (%s)",
+ builtin->name, builtin->docname);
+ else
+ builtin->docname = get_arg (self, defs, arg);
+
+ return (0);
+}
+
+/* How to handle the $SHORT_DOC directive. */
+int
+short_doc_handler (self, defs, arg)
+ char *self;
+ DEF_FILE *defs;
+ char *arg;
+{
+ register BUILTIN_DESC *builtin;
+
+ builtin = current_builtin (self, defs);
+
+ if (builtin->shortdoc)
+ line_error (defs, "%s already has short documentation (%s)",
+ builtin->name, builtin->shortdoc);
+ else
+ builtin->shortdoc = get_arg (self, defs, arg);
+
+ return (0);
+}
+
+/* How to handle the $COMMENT directive. */
+int
+comment_handler (self, defs, arg)
+ char *self;
+ DEF_FILE *defs;
+ char *arg;
+{
+ return (0);
+}
+
+/* How to handle the $DEPENDS_ON directive. */
+int
+depends_on_handler (self, defs, arg)
+ char *self;
+ DEF_FILE *defs;
+ char *arg;
+{
+ register BUILTIN_DESC *builtin;
+ char *dependent;
+
+ builtin = current_builtin (self, defs);
+ dependent = get_arg (self, defs, arg);
+
+ if (!builtin->dependencies)
+ builtin->dependencies = array_create (sizeof (char *));
+
+ array_add (dependent, builtin->dependencies);
+
+ return (0);
+}
+
+/* How to handle the $PRODUCES directive. */
+int
+produces_handler (self, defs, arg)
+ char *self;
+ DEF_FILE *defs;
+ char *arg;
+{
+ /* If just hacking documentation, don't change any of the production
+ files. */
+ if (only_documentation)
+ return (0);
+
+ output_cpp_line_info++;
+
+ if (defs->production)
+ line_error (defs, "%s already has a %s definition", defs->filename, self);
+ else
+ {
+ defs->production = get_arg (self, defs, arg);
+
+ if (inhibit_production)
+ return (0);
+
+ defs->output = fopen (defs->production, "w");
+
+ if (!defs->output)
+ file_error (defs->production);
+
+ fprintf (defs->output, "/* %s, created from %s. */\n",
+ defs->production, defs->filename);
+ }
+ return (0);
+}
+
+/* How to handle the $END directive. */
+int
+end_handler (self, defs, arg)
+ char *self;
+ DEF_FILE *defs;
+ char *arg;
+{
+ must_be_building (self, defs);
+ building_builtin = 0;
+ return (0);
+}
+
+/* **************************************************************** */
+/* */
+/* Error Handling Functions */
+/* */
+/* **************************************************************** */
+
+/* Produce an error for DEFS with FORMAT and ARGS. */
+void
+line_error (defs, format, arg1, arg2)
+ DEF_FILE *defs;
+ char *format, *arg1, *arg2;
+{
+ if (defs->filename[0] != '/')
+ fprintf (stderr, "%s", error_directory ? error_directory : "./");
+ fprintf (stderr, "%s:%d:", defs->filename, defs->line_number + 1);
+ fprintf (stderr, format, arg1, arg2);
+ fprintf (stderr, "\n");
+ fflush (stderr);
+}
+
+/* Print error message for FILENAME. */
+void
+file_error (filename)
+ char *filename;
+{
+ perror (filename);
+ exit (2);
+}
+
+/* **************************************************************** */
+/* */
+/* xmalloc and xrealloc () */
+/* */
+/* **************************************************************** */
+
+static void memory_error_and_abort ();
+
+static char *
+xmalloc (bytes)
+ int bytes;
+{
+ char *temp = (char *)malloc (bytes);
+
+ if (!temp)
+ memory_error_and_abort ();
+ return (temp);
+}
+
+static char *
+xrealloc (pointer, bytes)
+ char *pointer;
+ int bytes;
+{
+ char *temp;
+
+ if (!pointer)
+ temp = (char *)malloc (bytes);
+ else
+ temp = (char *)realloc (pointer, bytes);
+
+ if (!temp)
+ memory_error_and_abort ();
+
+ return (temp);
+}
+
+static void
+memory_error_and_abort ()
+{
+ fprintf (stderr, "mkbuiltins: out of virtual memory\n");
+ abort ();
+}
+
+/* **************************************************************** */
+/* */
+/* Creating the Struct and Extern Files */
+/* */
+/* **************************************************************** */
+
+/* Return a pointer to a newly allocated builtin which is
+ an exact copy of BUILTIN. */
+BUILTIN_DESC *
+copy_builtin (builtin)
+ BUILTIN_DESC *builtin;
+{
+ BUILTIN_DESC *new;
+
+ new = (BUILTIN_DESC *)xmalloc (sizeof (BUILTIN_DESC));
+
+ new->name = savestring (builtin->name);
+ new->shortdoc = savestring (builtin->shortdoc);
+ new->longdoc = copy_string_array (builtin->longdoc);
+ new->dependencies = copy_string_array (builtin->dependencies);
+
+ new->function =
+ builtin->function ? savestring (builtin->function) : (char *)NULL;
+ new->docname =
+ builtin->docname ? savestring (builtin->docname) : (char *)NULL;
+
+ return (new);
+}
+
+/* How to save away a builtin. */
+void
+save_builtin (builtin)
+ BUILTIN_DESC *builtin;
+{
+ BUILTIN_DESC *newbuiltin;
+
+ newbuiltin = copy_builtin (builtin);
+
+ /* If this is the first builtin to be saved, create the array
+ to hold it. */
+ if (!saved_builtins)
+ saved_builtins = array_create (sizeof (BUILTIN_DESC *));
+
+ array_add ((char *)newbuiltin, saved_builtins);
+}
+
+/* Flags that mean something to write_documentation (). */
+#define STRING_ARRAY 0x01
+#define TEXINFO 0x02
+#define PLAINTEXT 0x04
+#define HELPFILE 0x08
+
+char *structfile_header[] = {
+ "/* 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 <http://www.gnu.org/licenses/>.",
+ "*/",
+ "",
+ "/* 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\"",
+ (char *)NULL
+ };
+
+char *structfile_footer[] = {
+ " { (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 =",
+ "\tsizeof (static_shell_builtins) / sizeof (struct builtin) - 1;",
+ (char *)NULL
+};
+
+/* Write out any necessary opening information for
+ STRUCTFILE and EXTERNFILE. */
+void
+write_file_headers (structfile, externfile)
+ FILE *structfile, *externfile;
+{
+ register int i;
+
+ if (structfile)
+ {
+ for (i = 0; structfile_header[i]; i++)
+ fprintf (structfile, "%s\n", structfile_header[i]);
+
+ fprintf (structfile, "#include \"%s\"\n",
+ include_filename ? include_filename : "builtext.h");
+
+ fprintf (structfile, "#include \"bashintl.h\"\n");
+
+ fprintf (structfile, "\nstruct builtin static_shell_builtins[] = {\n");
+ }
+
+ if (externfile)
+ fprintf (externfile,
+ "/* %s - The list of builtins found in libbuiltins.a. */\n",
+ include_filename ? include_filename : "builtext.h");
+}
+
+/* Write out any necessary closing information for
+ STRUCTFILE and EXTERNFILE. */
+void
+write_file_footers (structfile, externfile)
+ FILE *structfile, *externfile;
+{
+ register int i;
+
+ /* Write out the footers. */
+ if (structfile)
+ {
+ for (i = 0; structfile_footer[i]; i++)
+ fprintf (structfile, "%s\n", structfile_footer[i]);
+ }
+}
+
+/* Write out the information accumulated in DEFS to
+ STRUCTFILE and EXTERNFILE. */
+void
+write_builtins (defs, structfile, externfile)
+ DEF_FILE *defs;
+ FILE *structfile, *externfile;
+{
+ register int i;
+
+ /* Write out the information. */
+ if (defs->builtins)
+ {
+ register BUILTIN_DESC *builtin;
+
+ for (i = 0; i < defs->builtins->sindex; i++)
+ {
+ builtin = (BUILTIN_DESC *)defs->builtins->array[i];
+
+ /* Write out any #ifdefs that may be there. */
+ if (!only_documentation)
+ {
+ if (builtin->dependencies)
+ {
+ write_ifdefs (externfile, builtin->dependencies->array);
+ write_ifdefs (structfile, builtin->dependencies->array);
+ }
+
+ /* Write the extern definition. */
+ if (externfile)
+ {
+ if (builtin->function)
+ fprintf (externfile, "extern int %s PARAMS((WORD_LIST *));\n",
+ builtin->function);
+
+ fprintf (externfile, "extern char * const %s_doc[];\n",
+ document_name (builtin));
+ }
+
+ /* Write the structure definition. */
+ if (structfile)
+ {
+ fprintf (structfile, " { \"%s\", ", builtin->name);
+
+ if (builtin->function && inhibit_functions == 0)
+ fprintf (structfile, "%s, ", builtin->function);
+ else
+ fprintf (structfile, "(sh_builtin_func_t *)0x0, ");
+
+ fprintf (structfile, "%s%s%s%s%s%s, %s_doc,\n",
+ "BUILTIN_ENABLED | STATIC_BUILTIN",
+ (builtin->flags & BUILTIN_FLAG_SPECIAL) ? " | SPECIAL_BUILTIN" : "",
+ (builtin->flags & BUILTIN_FLAG_ASSIGNMENT) ? " | ASSIGNMENT_BUILTIN" : "",
+ (builtin->flags & BUILTIN_FLAG_LOCALVAR) ? " | LOCALVAR_BUILTIN" : "",
+ (builtin->flags & BUILTIN_FLAG_POSIX_BUILTIN) ? " | POSIX_BUILTIN" : "",
+ (builtin->flags & BUILTIN_FLAG_ARRAYREF_ARG) ? " | ARRAYREF_BUILTIN" : "",
+ document_name (builtin));
+
+ /* Don't translate short document summaries that are identical
+ to command names */
+ if (builtin->shortdoc && strcmp (builtin->name, builtin->shortdoc) == 0)
+ {
+ if (inhibit_functions)
+ fprintf (structfile, " \"%s\", \"%s\" },\n",
+ builtin->shortdoc ? builtin->shortdoc : builtin->name,
+ document_name (builtin));
+ else
+ fprintf (structfile, " \"%s\", (char *)NULL },\n",
+ builtin->shortdoc ? builtin->shortdoc : builtin->name);
+ }
+ else
+ {
+ if (inhibit_functions)
+ fprintf (structfile, " N_(\"%s\"), \"%s\" },\n",
+ builtin->shortdoc ? builtin->shortdoc : builtin->name,
+ document_name (builtin));
+ else
+ fprintf (structfile, " N_(\"%s\"), (char *)NULL },\n",
+ builtin->shortdoc ? builtin->shortdoc : builtin->name);
+ }
+ }
+
+ if (structfile || separate_helpfiles)
+ /* Save away this builtin for later writing of the
+ long documentation strings. */
+ save_builtin (builtin);
+
+ /* Write out the matching #endif, if necessary. */
+ if (builtin->dependencies)
+ {
+ if (externfile)
+ write_endifs (externfile, builtin->dependencies->array);
+
+ if (structfile)
+ write_endifs (structfile, builtin->dependencies->array);
+ }
+ }
+
+ if (documentation_file)
+ {
+ fprintf (documentation_file, "@item %s\n", builtin->name);
+ write_documentation
+ (documentation_file, builtin->longdoc->array, 0, TEXINFO);
+ }
+ }
+ }
+}
+
+/* Write out the long documentation strings in BUILTINS to STREAM. */
+void
+write_longdocs (stream, builtins)
+ FILE *stream;
+ ARRAY *builtins;
+{
+ register int i;
+ register BUILTIN_DESC *builtin;
+ char *dname;
+ char *sarray[2];
+
+ for (i = 0; i < builtins->sindex; i++)
+ {
+ builtin = (BUILTIN_DESC *)builtins->array[i];
+
+ if (builtin->dependencies)
+ write_ifdefs (stream, builtin->dependencies->array);
+
+ /* Write the long documentation strings. */
+ dname = document_name (builtin);
+ fprintf (stream, "char * const %s_doc[] =", dname);
+
+ if (separate_helpfiles)
+ {
+ int l = strlen (helpfile_directory) + strlen (dname) + 1;
+ sarray[0] = (char *)xmalloc (l + 1);
+ sprintf (sarray[0], "%s/%s", helpfile_directory, dname);
+ sarray[1] = (char *)NULL;
+ write_documentation (stream, sarray, 0, STRING_ARRAY|HELPFILE);
+ free (sarray[0]);
+ }
+ else
+ write_documentation (stream, builtin->longdoc->array, 0, STRING_ARRAY);
+
+ if (builtin->dependencies)
+ write_endifs (stream, builtin->dependencies->array);
+
+ }
+}
+
+void
+write_dummy_declarations (stream, builtins)
+ FILE *stream;
+ ARRAY *builtins;
+{
+ register int i;
+ BUILTIN_DESC *builtin;
+
+ for (i = 0; structfile_header[i]; i++)
+ fprintf (stream, "%s\n", structfile_header[i]);
+
+ for (i = 0; i < builtins->sindex; i++)
+ {
+ builtin = (BUILTIN_DESC *)builtins->array[i];
+
+ /* How to guarantee that no builtin is written more than once? */
+ fprintf (stream, "int %s () { return (0); }\n", builtin->function);
+ }
+}
+
+/* Write an #ifdef string saying what needs to be defined (or not defined)
+ in order to allow compilation of the code that will follow.
+ STREAM is the stream to write the information to,
+ DEFINES is a null terminated array of define names.
+ If a define is preceded by an `!', then the sense of the test is
+ reversed. */
+void
+write_ifdefs (stream, defines)
+ FILE *stream;
+ char **defines;
+{
+ register int i;
+
+ if (!stream)
+ return;
+
+ fprintf (stream, "#if ");
+
+ for (i = 0; defines[i]; i++)
+ {
+ char *def = defines[i];
+
+ if (*def == '!')
+ fprintf (stream, "!defined (%s)", def + 1);
+ else
+ fprintf (stream, "defined (%s)", def);
+
+ if (defines[i + 1])
+ fprintf (stream, " && ");
+ }
+ fprintf (stream, "\n");
+}
+
+/* Write an #endif string saying what defines controlled the compilation
+ of the immediately preceding code.
+ STREAM is the stream to write the information to.
+ DEFINES is a null terminated array of define names. */
+void
+write_endifs (stream, defines)
+ FILE *stream;
+ char **defines;
+{
+ register int i;
+
+ if (!stream)
+ return;
+
+ fprintf (stream, "#endif /* ");
+
+ for (i = 0; defines[i]; i++)
+ {
+ fprintf (stream, "%s", defines[i]);
+
+ if (defines[i + 1])
+ fprintf (stream, " && ");
+ }
+
+ fprintf (stream, " */\n");
+}
+
+/* Write DOCUMENTATION to STREAM, perhaps surrounding it with double-quotes
+ and quoting special characters in the string. Handle special things for
+ internationalization (gettext) and the single-string vs. multiple-strings
+ issues. */
+void
+write_documentation (stream, documentation, indentation, flags)
+ FILE *stream;
+ char **documentation;
+ int indentation, flags;
+{
+ register int i, j;
+ register char *line;
+ int string_array, texinfo, base_indent, filename_p;
+
+ if (stream == 0)
+ return;
+
+ string_array = flags & STRING_ARRAY;
+ filename_p = flags & HELPFILE;
+
+ if (string_array)
+ {
+ fprintf (stream, " {\n#if defined (HELP_BUILTIN)\n"); /* } */
+ if (single_longdoc_strings)
+ {
+ if (filename_p == 0)
+ {
+ if (documentation && documentation[0] && documentation[0][0])
+ fprintf (stream, "N_(\"");
+ else
+ fprintf (stream, "N_(\" "); /* the empty string translates specially. */
+ }
+ else
+ fprintf (stream, "\"");
+ }
+ }
+
+ base_indent = (string_array && single_longdoc_strings && filename_p == 0) ? BASE_INDENT : 0;
+
+ for (i = 0, texinfo = (flags & TEXINFO); documentation && (line = documentation[i]); i++)
+ {
+ /* Allow #ifdef's to be written out verbatim, but don't put them into
+ separate help files. */
+ if (*line == '#')
+ {
+ if (string_array && filename_p == 0 && single_longdoc_strings == 0)
+ fprintf (stream, "%s\n", line);
+ continue;
+ }
+
+ /* prefix with N_( for gettext */
+ if (string_array && single_longdoc_strings == 0)
+ {
+ if (filename_p == 0)
+ {
+ if (line[0])
+ fprintf (stream, " N_(\"");
+ else
+ fprintf (stream, " N_(\" "); /* the empty string translates specially. */
+ }
+ else
+ fprintf (stream, " \"");
+ }
+
+ if (indentation)
+ for (j = 0; j < indentation; j++)
+ fprintf (stream, " ");
+
+ /* Don't indent the first line, because of how the help builtin works. */
+ if (i == 0)
+ indentation += base_indent;
+
+ if (string_array)
+ {
+ for (j = 0; line[j]; j++)
+ {
+ switch (line[j])
+ {
+ case '\\':
+ case '"':
+ fprintf (stream, "\\%c", line[j]);
+ break;
+
+ default:
+ fprintf (stream, "%c", line[j]);
+ }
+ }
+
+ /* closing right paren for gettext */
+ if (single_longdoc_strings == 0)
+ {
+ if (filename_p == 0)
+ fprintf (stream, "\"),\n");
+ else
+ fprintf (stream, "\",\n");
+ }
+ else if (documentation[i+1])
+ /* don't add extra newline after last line */
+ fprintf (stream, "\\n\\\n");
+ }
+ else if (texinfo)
+ {
+ for (j = 0; line[j]; j++)
+ {
+ switch (line[j])
+ {
+ case '@':
+ case '{':
+ case '}':
+ fprintf (stream, "@%c", line[j]);
+ break;
+
+ default:
+ fprintf (stream, "%c", line[j]);
+ }
+ }
+ fprintf (stream, "\n");
+ }
+ else
+ fprintf (stream, "%s\n", line);
+ }
+
+ /* closing right paren for gettext */
+ if (string_array && single_longdoc_strings)
+ {
+ if (filename_p == 0)
+ fprintf (stream, "\"),\n");
+ else
+ fprintf (stream, "\",\n");
+ }
+
+ if (string_array)
+ fprintf (stream, "#endif /* HELP_BUILTIN */\n (char *)NULL\n};\n");
+}
+
+int
+write_helpfiles (builtins)
+ ARRAY *builtins;
+{
+ char *helpfile, *bname;
+ FILE *helpfp;
+ int i, hdlen;
+ BUILTIN_DESC *builtin;
+
+ i = mkdir ("helpfiles", 0777);
+ if (i < 0 && errno != EEXIST)
+ {
+ fprintf (stderr, "write_helpfiles: helpfiles: cannot create directory\n");
+ return -1;
+ }
+
+ hdlen = strlen ("helpfiles/");
+ for (i = 0; i < builtins->sindex; i++)
+ {
+ builtin = (BUILTIN_DESC *)builtins->array[i];
+
+ bname = document_name (builtin);
+ helpfile = (char *)xmalloc (hdlen + strlen (bname) + 1);
+ sprintf (helpfile, "helpfiles/%s", bname);
+
+ helpfp = fopen (helpfile, "w");
+ if (helpfp == 0)
+ {
+ fprintf (stderr, "write_helpfiles: cannot open %s\n", helpfile);
+ free (helpfile);
+ continue;
+ }
+
+ write_documentation (helpfp, builtin->longdoc->array, 4, PLAINTEXT);
+
+ fflush (helpfp);
+ fclose (helpfp);
+ free (helpfile);
+ }
+ return 0;
+}
+
+static int
+_find_in_table (name, name_table)
+ char *name, *name_table[];
+{
+ register int i;
+
+ for (i = 0; name_table[i]; i++)
+ if (strcmp (name, name_table[i]) == 0)
+ return 1;
+ return 0;
+}
+
+static int
+is_special_builtin (name)
+ char *name;
+{
+ return (_find_in_table (name, special_builtins));
+}
+
+static int
+is_assignment_builtin (name)
+ char *name;
+{
+ return (_find_in_table (name, assignment_builtins));
+}
+
+static int
+is_localvar_builtin (name)
+ char *name;
+{
+ return (_find_in_table (name, localvar_builtins));
+}
+
+static int
+is_posix_builtin (name)
+ char *name;
+{
+ return (_find_in_table (name, posix_builtins));
+}
+
+static int
+is_arrayvar_builtin (name)
+ char *name;
+{
+ return (_find_in_table (name, arrayvar_builtins));
+}
+
+#if !defined (HAVE_RENAME)
+static int
+rename (from, to)
+ char *from, *to;
+{
+ unlink (to);
+ if (link (from, to) < 0)
+ return (-1);
+ unlink (from);
+ return (0);
+}
+#endif /* !HAVE_RENAME */
diff --git a/builtins/printf.def b/builtins/printf.def
new file mode 100644
index 0000000..84658c3
--- /dev/null
+++ b/builtins/printf.def
@@ -0,0 +1,1355 @@
+This file is printf.def, from which is created printf.c.
+It implements the builtin "printf" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES printf.c
+
+$BUILTIN printf
+$FUNCTION printf_builtin
+$SHORT_DOC printf [-v var] format [arguments]
+Formats and prints ARGUMENTS under control of the FORMAT.
+
+Options:
+ -v var assign the output to shell variable VAR rather than
+ display it on the standard output
+
+FORMAT is a character string which contains three types of objects: plain
+characters, which are simply copied to standard output; character escape
+sequences, which are converted and copied to the standard output; and
+format specifications, each of which causes printing of the next successive
+argument.
+
+In addition to the standard format specifications described in printf(1),
+printf interprets:
+
+ %b expand backslash escape sequences in the corresponding argument
+ %q quote the argument in a way that can be reused as shell input
+ %Q like %q, but apply any precision to the unquoted argument before
+ quoting
+ %(fmt)T output the date-time string resulting from using FMT as a format
+ string for strftime(3)
+
+The format is re-used as necessary to consume all of the arguments. If
+there are fewer arguments than the format requires, extra format
+specifications behave as if a zero value or null string, as appropriate,
+had been supplied.
+
+Exit Status:
+Returns success unless an invalid option is given or a write or assignment
+error occurs.
+$END
+
+#include <config.h>
+
+#include "../bashtypes.h"
+
+#include <errno.h>
+#if defined (HAVE_LIMITS_H)
+# include <limits.h>
+#else
+ /* Assume 32-bit ints. */
+# define INT_MAX 2147483647
+# define INT_MIN (-2147483647-1)
+#endif
+
+#if defined (PREFER_STDARG)
+# include <stdarg.h>
+#else
+# include <varargs.h>
+#endif
+
+#include <stdio.h>
+#include <chartypes.h>
+
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#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/builtins/psize.c b/builtins/psize.c
new file mode 100644
index 0000000..30881fb
--- /dev/null
+++ b/builtins/psize.c
@@ -0,0 +1,79 @@
+/* psize.c - Find pipe size. */
+
+/* Copyright (C) 1987, 1991 Free Software Foundation, Inc.
+
+ This file is part of GNU Bash, the Bourne Again SHell.
+
+ Bash is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Bash is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Bash. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+/* Write output in 128-byte chunks until we get a sigpipe or write gets an
+ EPIPE. Then report how many bytes we wrote. We assume that this is the
+ pipe size. */
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#ifndef _MINIX
+#include "../bashtypes.h"
+#endif
+#include <signal.h>
+#include <errno.h>
+
+#include "../command.h"
+#include "../general.h"
+#include "../sig.h"
+
+#ifndef errno
+extern int errno;
+#endif
+
+int nw;
+
+sighandler
+sigpipe (sig)
+ int sig;
+{
+ fprintf (stderr, "%d\n", nw);
+ exit (0);
+}
+
+int
+main (argc, argv)
+ int argc;
+ char **argv;
+{
+ char buf[128];
+ register int i;
+
+ for (i = 0; i < 128; i++)
+ buf[i] = ' ';
+
+ signal (SIGPIPE, sigpipe);
+
+ nw = 0;
+ for (;;)
+ {
+ int n;
+ n = write (1, buf, 128);
+ nw += n;
+ }
+ return (0);
+}
diff --git a/builtins/psize.sh b/builtins/psize.sh
new file mode 100644
index 0000000..29bc115
--- /dev/null
+++ b/builtins/psize.sh
@@ -0,0 +1,45 @@
+#! /bin/sh
+#
+# psize.sh -- determine this system's pipe size, and write a define to
+# pipesize.h so ulimit.c can use it.
+
+: ${TMPDIR:=/tmp}
+# try to use mktemp(1) if the system supports it
+{ TMPFILE="`mktemp $TMPDIR/pipsize.XXXXXX 2>/dev/null`"; } 2>/dev/null
+used_mktemp=true
+
+if [ -z "$TMPFILE" ]; then
+ TMPNAME=pipsize.$$
+ TMPFILE=$TMPDIR/$TMPNAME
+ used_mktemp=false
+fi
+
+trap 'rm -f "$TMPFILE" ; exit 1' 1 2 3 6 15
+trap 'rm -f "$TMPFILE"' 0
+
+echo "/*"
+echo " * pipesize.h"
+echo " *"
+echo " * This file is automatically generated by psize.sh"
+echo " * Do not edit!"
+echo " */"
+echo ""
+
+#
+# Try to avoid tempfile races. We can't really check for the file's
+# existence before we run psize.aux, because `test -e' is not portable,
+# `test -h' (test for symlinks) is not portable, and `test -f' only
+# checks for regular files. If we used mktemp(1), we're ahead of the
+# game.
+#
+$used_mktemp || rm -f "$TMPFILE"
+
+./psize.aux 2>"$TMPFILE" | sleep 3
+
+if [ -s "$TMPFILE" ]; then
+ echo "#define PIPESIZE `cat "$TMPFILE"`"
+else
+ echo "#define PIPESIZE 512"
+fi
+
+exit 0
diff --git a/builtins/pushd.def b/builtins/pushd.def
new file mode 100644
index 0000000..829f827
--- /dev/null
+++ b/builtins/pushd.def
@@ -0,0 +1,796 @@
+This file is pushd.def, from which is created pushd.c. It implements the
+builtins "pushd", "popd", and "dirs" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES pushd.c
+
+$BUILTIN pushd
+$FUNCTION pushd_builtin
+$DEPENDS_ON PUSHD_AND_POPD
+$SHORT_DOC pushd [-n] [+N | -N | dir]
+Add directories to stack.
+
+Adds a directory to the top of the directory stack, or rotates
+the stack, making the new top of the stack the current working
+directory. With no arguments, exchanges the top two directories.
+
+Options:
+ -n Suppresses the normal change of directory when adding
+ directories to the stack, so only the stack is manipulated.
+
+Arguments:
+ +N Rotates the stack so that the Nth directory (counting
+ from the left of the list shown by `dirs', starting with
+ zero) is at the top.
+
+ -N Rotates the stack so that the Nth directory (counting
+ from the right of the list shown by `dirs', starting with
+ zero) is at the top.
+
+ dir Adds DIR to the directory stack at the top, making it the
+ new current working directory.
+
+The `dirs' builtin displays the directory stack.
+
+Exit Status:
+Returns success unless an invalid argument is supplied or the directory
+change fails.
+$END
+
+$BUILTIN popd
+$FUNCTION popd_builtin
+$DEPENDS_ON PUSHD_AND_POPD
+$SHORT_DOC popd [-n] [+N | -N]
+Remove directories from stack.
+
+Removes entries from the directory stack. With no arguments, removes
+the top directory from the stack, and changes to the new top directory.
+
+Options:
+ -n Suppresses the normal change of directory when removing
+ directories from the stack, so only the stack is manipulated.
+
+Arguments:
+ +N Removes the Nth entry counting from the left of the list
+ shown by `dirs', starting with zero. For example: `popd +0'
+ removes the first directory, `popd +1' the second.
+
+ -N Removes the Nth entry counting from the right of the list
+ shown by `dirs', starting with zero. For example: `popd -0'
+ removes the last directory, `popd -1' the next to last.
+
+The `dirs' builtin displays the directory stack.
+
+Exit Status:
+Returns success unless an invalid argument is supplied or the directory
+change fails.
+$END
+
+$BUILTIN dirs
+$FUNCTION dirs_builtin
+$DEPENDS_ON PUSHD_AND_POPD
+$SHORT_DOC dirs [-clpv] [+N] [-N]
+Display directory stack.
+
+Display the list of currently remembered directories. Directories
+find their way onto the list with the `pushd' command; you can get
+back up through the list with the `popd' command.
+
+Options:
+ -c clear the directory stack by deleting all of the elements
+ -l do not print tilde-prefixed versions of directories relative
+ to your home directory
+ -p print the directory stack with one entry per line
+ -v print the directory stack with one entry per line prefixed
+ with its position in the stack
+
+Arguments:
+ +N Displays the Nth entry counting from the left of the list
+ shown by dirs when invoked without options, starting with
+ zero.
+
+ -N Displays the Nth entry counting from the right of the list
+ shown by dirs when invoked without options, starting with
+ zero.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
+$END
+
+#include <config.h>
+
+#if defined (PUSHD_AND_POPD)
+#include <stdio.h>
+#if defined (HAVE_SYS_PARAM_H)
+# include <sys/param.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include <errno.h>
+
+#include <tilde/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 (_("<no current directory>"));
+ 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/builtins/read.def b/builtins/read.def
new file mode 100644
index 0000000..ddd91d3
--- /dev/null
+++ b/builtins/read.def
@@ -0,0 +1,1273 @@
+This file is read.def, from which is created read.c.
+It implements the builtin "read" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES read.c
+
+$BUILTIN read
+$FUNCTION read_builtin
+$SHORT_DOC read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
+Read a line from the standard input and split it into fields.
+
+Reads a single line from the standard input, or from file descriptor FD
+if the -u option is supplied. The line is split into fields as with word
+splitting, and the first word is assigned to the first NAME, the second
+word to the second NAME, and so on, with any leftover words assigned to
+the last NAME. Only the characters found in $IFS are recognized as word
+delimiters. By default, the backslash character escapes delimiter characters
+and newline.
+
+If no NAMEs are supplied, the line read is stored in the REPLY variable.
+
+Options:
+ -a array assign the words read to sequential indices of the array
+ variable ARRAY, starting at zero
+ -d delim continue until the first character of DELIM is read, rather
+ than newline
+ -e use Readline to obtain the line
+ -i text use TEXT as the initial text for Readline
+ -n nchars return after reading NCHARS characters rather than waiting
+ for a newline, but honor a delimiter if fewer than
+ NCHARS characters are read before the delimiter
+ -N nchars return only after reading exactly NCHARS characters, unless
+ EOF is encountered or read times out, ignoring any
+ delimiter
+ -p prompt output the string PROMPT without a trailing newline before
+ attempting to read
+ -r do not allow backslashes to escape any characters
+ -s do not echo input coming from a terminal
+ -t timeout time out and return failure if a complete line of
+ input is not read within TIMEOUT seconds. The value of the
+ TMOUT variable is the default timeout. TIMEOUT may be a
+ fractional number. If TIMEOUT is 0, read returns
+ immediately, without trying to read any data, returning
+ success only if input is available on the specified
+ file descriptor. The exit status is greater than 128
+ if the timeout is exceeded
+ -u fd read from file descriptor FD instead of the standard input
+
+Exit Status:
+The return code is zero, unless end-of-file is encountered, read times out
+(in which case it's greater than 128), a variable assignment error occurs,
+or an invalid file descriptor is supplied as the argument to -u.
+$END
+
+#include <config.h>
+
+#include "bashtypes.h"
+#include "posixstat.h"
+
+#include <stdio.h>
+
+#include "bashansi.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <signal.h>
+#include <errno.h>
+
+#ifdef __CYGWIN__
+# include <fcntl.h>
+# include <io.h>
+#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 <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);
+#if defined (READLINE)
+ rl_clear_timeout ();
+#endif
+ 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/builtins/reserved.def b/builtins/reserved.def
new file mode 100644
index 0000000..420042d
--- /dev/null
+++ b/builtins/reserved.def
@@ -0,0 +1,288 @@
+This file is reserved.def, in which the shell reserved words are defined.
+It has no direct C file production, but defines builtins for the Bash
+builtin help command.
+
+Copyright (C) 1987-2019 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$BUILTIN for
+$SHORT_DOC for NAME [in WORDS ... ] ; do COMMANDS; done
+Execute commands for each member in a list.
+
+The `for' loop executes a sequence of commands for each member in a
+list of items. If `in WORDS ...;' is not present, then `in "$@"' is
+assumed. For each element in WORDS, NAME is set to that element, and
+the COMMANDS are executed.
+
+Exit Status:
+Returns the status of the last command executed.
+$END
+
+$BUILTIN for ((
+$DOCNAME arith_for
+$SHORT_DOC for (( exp1; exp2; exp3 )); do COMMANDS; done
+Arithmetic for loop.
+
+Equivalent to
+ (( EXP1 ))
+ while (( EXP2 )); do
+ COMMANDS
+ (( EXP3 ))
+ done
+EXP1, EXP2, and EXP3 are arithmetic expressions. If any expression is
+omitted, it behaves as if it evaluates to 1.
+
+Exit Status:
+Returns the status of the last command executed.
+$END
+
+$BUILTIN select
+$SHORT_DOC select NAME [in WORDS ... ;] do COMMANDS; done
+Select words from a list and execute commands.
+
+The WORDS are expanded, generating a list of words. The
+set of expanded words is printed on the standard error, each
+preceded by a number. If `in WORDS' is not present, `in "$@"'
+is assumed. The PS3 prompt is then displayed and a line read
+from the standard input. If the line consists of the number
+corresponding to one of the displayed words, then NAME is set
+to that word. If the line is empty, WORDS and the prompt are
+redisplayed. If EOF is read, the command completes. Any other
+value read causes NAME to be set to null. The line read is saved
+in the variable REPLY. COMMANDS are executed after each selection
+until a break command is executed.
+
+Exit Status:
+Returns the status of the last command executed.
+$END
+
+$BUILTIN time
+$SHORT_DOC time [-p] pipeline
+Report time consumed by pipeline's execution.
+
+Execute PIPELINE and print a summary of the real time, user CPU time,
+and system CPU time spent executing PIPELINE when it terminates.
+
+Options:
+ -p print the timing summary in the portable Posix format
+
+The value of the TIMEFORMAT variable is used as the output format.
+
+Exit Status:
+The return status is the return status of PIPELINE.
+$END
+
+$BUILTIN case
+$SHORT_DOC case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
+Execute commands based on pattern matching.
+
+Selectively execute COMMANDS based upon WORD matching PATTERN. The
+`|' is used to separate multiple patterns.
+
+Exit Status:
+Returns the status of the last command executed.
+$END
+
+$BUILTIN if
+$SHORT_DOC if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
+Execute commands based on conditional.
+
+The `if COMMANDS' list is executed. If its exit status is zero, then the
+`then COMMANDS' list is executed. Otherwise, each `elif COMMANDS' list is
+executed in turn, and if its exit status is zero, the corresponding
+`then COMMANDS' list is executed and the if command completes. Otherwise,
+the `else COMMANDS' list is executed, if present. The exit status of the
+entire construct is the exit status of the last command executed, or zero
+if no condition tested true.
+
+Exit Status:
+Returns the status of the last command executed.
+$END
+
+$BUILTIN while
+$SHORT_DOC while COMMANDS; do COMMANDS-2; done
+Execute commands as long as a test succeeds.
+
+Expand and execute COMMANDS-2 as long as the final command in COMMANDS has
+an exit status of zero.
+
+Exit Status:
+Returns the status of the last command executed.
+$END
+
+$BUILTIN until
+$SHORT_DOC until COMMANDS; do COMMANDS-2; done
+Execute commands as long as a test does not succeed.
+
+Expand and execute COMMANDS-2 as long as the final command in COMMANDS has
+an exit status which is not zero.
+
+Exit Status:
+Returns the status of the last command executed.
+$END
+
+$BUILTIN coproc
+$SHORT_DOC coproc [NAME] command [redirections]
+Create a coprocess named NAME.
+
+Execute COMMAND asynchronously, with the standard output and standard
+input of the command connected via a pipe to file descriptors assigned
+to indices 0 and 1 of an array variable NAME in the executing shell.
+The default NAME is "COPROC".
+
+Exit Status:
+The coproc command returns an exit status of 0.
+$END
+
+$BUILTIN function
+$SHORT_DOC function name { COMMANDS ; } or name () { COMMANDS ; }
+Define shell function.
+
+Create a shell function named NAME. When invoked as a simple command,
+NAME runs COMMANDs in the calling shell's context. When NAME is invoked,
+the arguments are passed to the function as $1...$n, and the function's
+name is in $FUNCNAME.
+
+Exit Status:
+Returns success unless NAME is readonly.
+$END
+
+$BUILTIN { ... }
+$DOCNAME grouping_braces
+$SHORT_DOC { COMMANDS ; }
+Group commands as a unit.
+
+Run a set of commands in a group. This is one way to redirect an
+entire set of commands.
+
+Exit Status:
+Returns the status of the last command executed.
+$END
+
+$BUILTIN %
+$DOCNAME fg_percent
+$SHORT_DOC job_spec [&]
+Resume job in foreground.
+
+Equivalent to the JOB_SPEC argument to the `fg' command. Resume a
+stopped or background job. JOB_SPEC can specify either a job name
+or a job number. Following JOB_SPEC with a `&' places the job in
+the background, as if the job specification had been supplied as an
+argument to `bg'.
+
+Exit Status:
+Returns the status of the resumed job.
+$END
+
+$BUILTIN (( ... ))
+$DOCNAME arith
+$SHORT_DOC (( expression ))
+Evaluate arithmetic expression.
+
+The EXPRESSION is evaluated according to the rules for arithmetic
+evaluation. Equivalent to `let "EXPRESSION"'.
+
+Exit Status:
+Returns 1 if EXPRESSION evaluates to 0; returns 0 otherwise.
+$END
+
+$BUILTIN [[ ... ]]
+$DOCNAME conditional
+$SHORT_DOC [[ expression ]]
+Execute conditional command.
+
+Returns a status of 0 or 1 depending on the evaluation of the conditional
+expression EXPRESSION. Expressions are composed of the same primaries used
+by the `test' builtin, and may be combined using the following operators:
+
+ ( EXPRESSION ) Returns the value of EXPRESSION
+ ! EXPRESSION True if EXPRESSION is false; else false
+ EXPR1 && EXPR2 True if both EXPR1 and EXPR2 are true; else false
+ EXPR1 || EXPR2 True if either EXPR1 or EXPR2 is true; else false
+
+When the `==' and `!=' operators are used, the string to the right of
+the operator is used as a pattern and pattern matching is performed.
+When the `=~' operator is used, the string to the right of the operator
+is matched as a regular expression.
+
+The && and || operators do not evaluate EXPR2 if EXPR1 is sufficient to
+determine the expression's value.
+
+Exit Status:
+0 or 1 depending on value of EXPRESSION.
+$END
+
+$BUILTIN variables
+$DOCNAME variable_help
+$SHORT_DOC variables - Names and meanings of some shell variables
+Common shell variable names and usage.
+
+BASH_VERSION Version information for this Bash.
+CDPATH A colon-separated list of directories to search
+ for directories given as arguments to `cd'.
+GLOBIGNORE A colon-separated list of patterns describing filenames to
+ be ignored by pathname expansion.
+#if defined (HISTORY)
+HISTFILE The name of the file where your command history is stored.
+HISTFILESIZE The maximum number of lines this file can contain.
+HISTSIZE The maximum number of history lines that a running
+ shell can access.
+#endif /* HISTORY */
+HOME The complete pathname to your login directory.
+HOSTNAME The name of the current host.
+HOSTTYPE The type of CPU this version of Bash is running under.
+IGNOREEOF Controls the action of the shell on receipt of an EOF
+ character as the sole input. If set, then the value
+ of it is the number of EOF characters that can be seen
+ in a row on an empty line before the shell will exit
+ (default 10). When unset, EOF signifies the end of input.
+MACHTYPE A string describing the current system Bash is running on.
+MAILCHECK How often, in seconds, Bash checks for new mail.
+MAILPATH A colon-separated list of filenames which Bash checks
+ for new mail.
+OSTYPE The version of Unix this version of Bash is running on.
+PATH A colon-separated list of directories to search when
+ looking for commands.
+PROMPT_COMMAND A command to be executed before the printing of each
+ primary prompt.
+PS1 The primary prompt string.
+PS2 The secondary prompt string.
+PWD The full pathname of the current directory.
+SHELLOPTS A colon-separated list of enabled shell options.
+TERM The name of the current terminal type.
+TIMEFORMAT The output format for timing statistics displayed by the
+ `time' reserved word.
+auto_resume Non-null means a command word appearing on a line by
+ itself is first looked for in the list of currently
+ stopped jobs. If found there, that job is foregrounded.
+ A value of `exact' means that the command word must
+ exactly match a command in the list of stopped jobs. A
+ value of `substring' means that the command word must
+ match a substring of the job. Any other value means that
+ the command must be a prefix of a stopped job.
+#if defined (HISTORY)
+# if defined (BANG_HISTORY)
+histchars Characters controlling history expansion and quick
+ substitution. The first character is the history
+ substitution character, usually `!'. The second is
+ the `quick substitution' character, usually `^'. The
+ third is the `history comment' character, usually `#'.
+# endif /* BANG_HISTORY */
+HISTIGNORE A colon-separated list of patterns used to decide which
+ commands should be saved on the history list.
+#endif /* HISTORY */
+$END
diff --git a/builtins/return.def b/builtins/return.def
new file mode 100644
index 0000000..03c98eb
--- /dev/null
+++ b/builtins/return.def
@@ -0,0 +1,71 @@
+This file is return.def, from which is created return.c.
+It implements the builtin "return" in Bash.
+
+Copyright (C) 1987-2015 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES return.c
+
+$BUILTIN return
+
+$FUNCTION return_builtin
+$SHORT_DOC return [n]
+Return from a shell function.
+
+Causes a function or sourced script to exit with the return value
+specified by N. If N is omitted, the return status is that of the
+last command executed within the function or script.
+
+Exit Status:
+Returns N, or failure if the shell is not executing a function or script.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#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/builtins/set.def b/builtins/set.def
new file mode 100644
index 0000000..44f1769
--- /dev/null
+++ b/builtins/set.def
@@ -0,0 +1,1028 @@
+This file is set.def, from which is created set.c.
+It implements the "set" and "unset" builtins in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES set.c
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+#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 <readline/readline.h>
+#endif
+
+#if defined (HISTORY)
+# include "../bashhist.h"
+#endif
+
+$BUILTIN set
+$FUNCTION set_builtin
+$SHORT_DOC set [-abefhkmnptuvxBCEHPT] [-o option-name] [--] [-] [arg ...]
+Set or unset values of shell options and positional parameters.
+
+Change the value of shell attributes and positional parameters, or
+display the names and values of shell variables.
+
+Options:
+ -a Mark variables which are modified or created for export.
+ -b Notify of job termination immediately.
+ -e Exit immediately if a command exits with a non-zero status.
+ -f Disable file name generation (globbing).
+ -h Remember the location of commands as they are looked up.
+ -k All assignment arguments are placed in the environment for a
+ command, not just those that precede the command name.
+ -m Job control is enabled.
+ -n Read commands but do not execute them.
+ -o option-name
+ Set the variable corresponding to option-name:
+ allexport same as -a
+ braceexpand same as -B
+#if defined (READLINE)
+ emacs use an emacs-style line editing interface
+#endif /* READLINE */
+ errexit same as -e
+ errtrace same as -E
+ functrace same as -T
+ hashall same as -h
+#if defined (BANG_HISTORY)
+ histexpand same as -H
+#endif /* BANG_HISTORY */
+#if defined (HISTORY)
+ history enable command history
+#endif
+ ignoreeof the shell will not exit upon reading EOF
+ interactive-comments
+ allow comments to appear in interactive commands
+ keyword same as -k
+#if defined (JOB_CONTROL)
+ monitor same as -m
+#endif
+ noclobber same as -C
+ noexec same as -n
+ noglob same as -f
+ nolog currently accepted but ignored
+#if defined (JOB_CONTROL)
+ notify same as -b
+#endif
+ nounset same as -u
+ onecmd same as -t
+ physical same as -P
+ pipefail the return value of a pipeline is the status of
+ the last command to exit with a non-zero status,
+ or zero if no command exited with a non-zero status
+ posix change the behavior of bash where the default
+ operation differs from the Posix standard to
+ match the standard
+ privileged same as -p
+ verbose same as -v
+#if defined (READLINE)
+ vi use a vi-style line editing interface
+#endif /* READLINE */
+ xtrace same as -x
+ -p Turned on whenever the real and effective user ids do not match.
+ Disables processing of the $ENV file and importing of shell
+ functions. Turning this option off causes the effective uid and
+ gid to be set to the real uid and gid.
+ -t Exit after reading and executing one command.
+ -u Treat unset variables as an error when substituting.
+ -v Print shell input lines as they are read.
+ -x Print commands and their arguments as they are executed.
+#if defined (BRACE_EXPANSION)
+ -B the shell will perform brace expansion
+#endif /* BRACE_EXPANSION */
+ -C If set, disallow existing regular files to be overwritten
+ by redirection of output.
+ -E If set, the ERR trap is inherited by shell functions.
+#if defined (BANG_HISTORY)
+ -H Enable ! style history substitution. This flag is on
+ by default when the shell is interactive.
+#endif /* BANG_HISTORY */
+ -P If set, do not resolve symbolic links when executing commands
+ such as cd which change the current directory.
+ -T If set, the DEBUG and RETURN traps are inherited by shell functions.
+ -- Assign any remaining arguments to the positional parameters.
+ If there are no remaining arguments, the positional parameters
+ are unset.
+ - Assign any remaining arguments to the positional parameters.
+ The -x and -v options are turned off.
+
+Using + rather than - causes these flags to be turned off. The
+flags can also be used upon invocation of the shell. The current
+set of flags may be found in $-. The remaining n ARGs are positional
+parameters and are assigned, in order, to $1, $2, .. $n. If no
+ARGs are given, all shell variables are printed.
+
+Exit Status:
+Returns success unless an invalid option is given.
+$END
+
+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);
+}
+
+$BUILTIN unset
+$FUNCTION unset_builtin
+$SHORT_DOC unset [-f] [-v] [-n] [name ...]
+Unset values and attributes of shell variables and functions.
+
+For each NAME, remove the corresponding variable or function.
+
+Options:
+ -f treat each NAME as a shell function
+ -v treat each NAME as a shell variable
+ -n treat each NAME as a name reference and unset the variable itself
+ rather than the variable it references
+
+Without options, unset first tries to unset a variable, and if that fails,
+tries to unset a function.
+
+Some variables cannot be unset; also see `readonly'.
+
+Exit Status:
+Returns success unless an invalid option is given or a NAME is read-only.
+$END
+
+#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/builtins/setattr.def b/builtins/setattr.def
new file mode 100644
index 0000000..50c8edf
--- /dev/null
+++ b/builtins/setattr.def
@@ -0,0 +1,672 @@
+This file is setattr.def, from which is created setattr.c.
+It implements the builtins "export" and "readonly", in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES setattr.c
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#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)
+
+$BUILTIN export
+$FUNCTION export_builtin
+$SHORT_DOC export [-fn] [name[=value] ...] or export -p
+Set export attribute for shell variables.
+
+Marks each NAME for automatic export to the environment of subsequently
+executed commands. If VALUE is supplied, assign VALUE before exporting.
+
+Options:
+ -f refer to shell functions
+ -n remove the export property from each NAME
+ -p display a list of all exported variables and functions
+
+An argument of `--' disables further option processing.
+
+Exit Status:
+Returns success unless an invalid option is given or NAME is invalid.
+$END
+
+/* 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));
+}
+
+$BUILTIN readonly
+$FUNCTION readonly_builtin
+$SHORT_DOC readonly [-aAf] [name[=value] ...] or readonly -p
+Mark shell variables as unchangeable.
+
+Mark each NAME as read-only; the values of these NAMEs may not be
+changed by subsequent assignment. If VALUE is supplied, assign VALUE
+before marking as read-only.
+
+Options:
+ -a refer to indexed array variables
+ -A refer to associative array variables
+ -f refer to shell functions
+ -p display a list of all readonly variables or functions,
+ depending on whether or not the -f option is given
+
+An argument of `--' disables further option processing.
+
+Exit Status:
+Returns success unless an invalid option is given or NAME is invalid.
+$END
+
+/* 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/builtins/shift.def b/builtins/shift.def
new file mode 100644
index 0000000..bb9af01
--- /dev/null
+++ b/builtins/shift.def
@@ -0,0 +1,90 @@
+This file is shift.def, from which is created shift.c.
+It implements the builtin "shift" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES shift.c
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include "../bashansi.h"
+#include "../bashintl.h"
+
+#include "../shell.h"
+#include "common.h"
+
+$BUILTIN shift
+$FUNCTION shift_builtin
+$SHORT_DOC shift [n]
+Shift positional parameters.
+
+Rename the positional parameters $N+1,$N+2 ... to $1,$2 ... If N is
+not given, it is assumed to be 1.
+
+Exit Status:
+Returns success unless N is negative or greater than $#.
+$END
+
+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, &times) == 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/builtins/shopt.def b/builtins/shopt.def
new file mode 100644
index 0000000..ba97e70
--- /dev/null
+++ b/builtins/shopt.def
@@ -0,0 +1,966 @@
+This file is shopt.def, from which is created shopt.c.
+It implements the Bash `shopt' builtin.
+
+Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES shopt.c
+
+$BUILTIN shopt
+$FUNCTION shopt_builtin
+$SHORT_DOC shopt [-pqsu] [-o] [optname ...]
+Set and unset shell options.
+
+Change the setting of each shell option OPTNAME. Without any option
+arguments, list each supplied OPTNAME, or all shell options if no
+OPTNAMEs are given, with an indication of whether or not each is set.
+
+Options:
+ -o restrict OPTNAMEs to those defined for use with `set -o'
+ -p print each shell option with an indication of its status
+ -q suppress output
+ -s enable (set) each OPTNAME
+ -u disable (unset) each OPTNAME
+
+Exit Status:
+Returns success if OPTNAME is enabled; fails if an invalid option is
+given or OPTNAME is disabled.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+
+#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
+
+#if defined (EXTENDED_GLOB)
+int extglob_flag = EXTGLOB_DEFAULT;
+static int shopt_set_extglob PARAMS((char *, int));
+#endif
+
+int expaliases_flag = 0;
+static int shopt_set_expaliases PARAMS((char *, int));
+
+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", &expaliases_flag, shopt_set_expaliases },
+#if defined (DEBUGGER)
+ { "extdebug", &debugging_mode, shopt_set_debug_mode },
+#endif
+#if defined (EXTENDED_GLOB)
+ { "extglob", &extglob_flag, shopt_set_extglob },
+#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 = expaliases_flag = 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_flag = 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);
+}
+
+static int
+shopt_set_expaliases (option_name, mode)
+ char *option_name;
+ int mode;
+{
+ expand_aliases = expaliases_flag;
+ return 0;
+}
+
+#if defined (EXTENDED_GLOB)
+static int
+shopt_set_extglob (option_name, mode)
+ char *option_name;
+ int mode;
+{
+ extended_glob = extglob_flag;
+ return 0;
+}
+#endif
+
+#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/builtins/source.def b/builtins/source.def
new file mode 100644
index 0000000..5b2f994
--- /dev/null
+++ b/builtins/source.def
@@ -0,0 +1,200 @@
+This file is source.def, from which is created source.c.
+It implements the builtins "." and "source" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES source.c
+
+$BUILTIN source
+$FUNCTION source_builtin
+$SHORT_DOC source filename [arguments]
+Execute commands from a file in the current shell.
+
+Read and execute commands from FILENAME in the current shell. The
+entries in $PATH are used to find the directory containing FILENAME.
+If any ARGUMENTS are supplied, they become the positional parameters
+when FILENAME is executed.
+
+Exit Status:
+Returns the status of the last command executed in FILENAME; fails if
+FILENAME cannot be read.
+$END
+
+$BUILTIN .
+$DOCNAME dot
+$FUNCTION source_builtin
+$SHORT_DOC . filename [arguments]
+Execute commands from a file in the current shell.
+
+Read and execute commands from FILENAME in the current shell. The
+entries in $PATH are used to find the directory containing FILENAME.
+If any ARGUMENTS are supplied, they become the positional parameters
+when FILENAME is executed.
+
+Exit Status:
+Returns the status of the last command executed in FILENAME; fails if
+FILENAME cannot be read.
+$END
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#include "posixstat.h"
+#include "filecntl.h"
+#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H)
+# include <sys/file.h>
+#endif
+#include <errno.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#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/builtins/suspend.def b/builtins/suspend.def
new file mode 100644
index 0000000..4f3a56c
--- /dev/null
+++ b/builtins/suspend.def
@@ -0,0 +1,129 @@
+This file is suspend.def, from which is created suspend.c.
+It implements the builtin "suspend" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES suspend.c
+
+$BUILTIN suspend
+$DEPENDS_ON JOB_CONTROL
+$FUNCTION suspend_builtin
+$SHORT_DOC suspend [-f]
+Suspend shell execution.
+
+Suspend the execution of this shell until it receives a SIGCONT signal.
+Unless forced, login shells and shells without job control cannot be
+suspended.
+
+Options:
+ -f force the suspend, even if the shell is a login shell or job
+ control is not enabled.
+
+Exit Status:
+Returns success unless job control is not enabled or an error occurs.
+$END
+
+#include <config.h>
+
+#if defined (JOB_CONTROL)
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include "../bashtypes.h"
+#include <signal.h>
+#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/builtins/test.def b/builtins/test.def
new file mode 100644
index 0000000..bd9a203
--- /dev/null
+++ b/builtins/test.def
@@ -0,0 +1,159 @@
+This file is test.def, from which is created test.c.
+It implements the builtin "test" in Bash.
+
+Copyright (C) 1987-2015 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES test.c
+
+$BUILTIN test
+$FUNCTION test_builtin
+$SHORT_DOC test [expr]
+Evaluate conditional expression.
+
+Exits with a status of 0 (true) or 1 (false) depending on
+the evaluation of EXPR. Expressions may be unary or binary. Unary
+expressions are often used to examine the status of a file. There
+are string operators and numeric comparison operators as well.
+
+The behavior of test depends on the number of arguments. Read the
+bash manual page for the complete specification.
+
+File operators:
+
+ -a FILE True if file exists.
+ -b FILE True if file is block special.
+ -c FILE True if file is character special.
+ -d FILE True if file is a directory.
+ -e FILE True if file exists.
+ -f FILE True if file exists and is a regular file.
+ -g FILE True if file is set-group-id.
+ -h FILE True if file is a symbolic link.
+ -L FILE True if file is a symbolic link.
+ -k FILE True if file has its `sticky' bit set.
+ -p FILE True if file is a named pipe.
+ -r FILE True if file is readable by you.
+ -s FILE True if file exists and is not empty.
+ -S FILE True if file is a socket.
+ -t FD True if FD is opened on a terminal.
+ -u FILE True if the file is set-user-id.
+ -w FILE True if the file is writable by you.
+ -x FILE True if the file is executable by you.
+ -O FILE True if the file is effectively owned by you.
+ -G FILE True if the file is effectively owned by your group.
+ -N FILE True if the file has been modified since it was last read.
+
+ FILE1 -nt FILE2 True if file1 is newer than file2 (according to
+ modification date).
+
+ FILE1 -ot FILE2 True if file1 is older than file2.
+
+ FILE1 -ef FILE2 True if file1 is a hard link to file2.
+
+String operators:
+
+ -z STRING True if string is empty.
+
+ -n STRING
+ STRING True if string is not empty.
+
+ STRING1 = STRING2
+ True if the strings are equal.
+ STRING1 != STRING2
+ True if the strings are not equal.
+ STRING1 < STRING2
+ True if STRING1 sorts before STRING2 lexicographically.
+ STRING1 > STRING2
+ True if STRING1 sorts after STRING2 lexicographically.
+
+Other operators:
+
+ -o OPTION True if the shell option OPTION is enabled.
+ -v VAR True if the shell variable VAR is set.
+ -R VAR True if the shell variable VAR is set and is a name
+ reference.
+ ! EXPR True if expr is false.
+ EXPR1 -a EXPR2 True if both expr1 AND expr2 are true.
+ EXPR1 -o EXPR2 True if either expr1 OR expr2 is true.
+
+ arg1 OP arg2 Arithmetic tests. OP is one of -eq, -ne,
+ -lt, -le, -gt, or -ge.
+
+Arithmetic binary operators return true if ARG1 is equal, not-equal,
+less-than, less-than-or-equal, greater-than, or greater-than-or-equal
+than ARG2.
+
+Exit Status:
+Returns success if EXPR evaluates to true; fails if EXPR evaluates to
+false or an invalid argument is given.
+$END
+
+$BUILTIN [
+$DOCNAME test_bracket
+$FUNCTION test_builtin
+$SHORT_DOC [ arg... ]
+Evaluate conditional expression.
+
+This is a synonym for the "test" builtin, but the last argument must
+be a literal `]', to match the opening `['.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#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/builtins/times.def b/builtins/times.def
new file mode 100644
index 0000000..f31f433
--- /dev/null
+++ b/builtins/times.def
@@ -0,0 +1,119 @@
+This file is times.def, from which is created times.c.
+It implements the builtin "times" in Bash.
+
+Copyright (C) 1987-2009 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Bash is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Bash. If not, see <http://www.gnu.org/licenses/>.
+
+$PRODUCES times.c
+
+$BUILTIN times
+$FUNCTION times_builtin
+$SHORT_DOC times
+Display process times.
+
+Prints the accumulated user and system times for the shell and all of its
+child processes.
+
+Exit Status:
+Always succeeds.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "../bashtypes.h"
+#include "../shell.h"
+
+#include <posixtime.h>
+
+#if defined (HAVE_SYS_TIMES_H)
+# include <sys/times.h>
+#endif /* HAVE_SYS_TIMES_H */
+
+#if defined (HAVE_SYS_RESOURCE_H) && !defined (RLIMTYPE)
+# include <sys/resource.h>
+#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/builtins/trap.def b/builtins/trap.def
new file mode 100644
index 0000000..9422943
--- /dev/null
+++ b/builtins/trap.def
@@ -0,0 +1,316 @@
+This file is trap.def, from which is created trap.c.
+It implements the builtin "trap" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES trap.c
+
+$BUILTIN trap
+$FUNCTION trap_builtin
+$SHORT_DOC trap [-lp] [[arg] signal_spec ...]
+Trap signals and other events.
+
+Defines and activates handlers to be run when the shell receives signals
+or other conditions.
+
+ARG is a command to be read and executed when the shell receives the
+signal(s) SIGNAL_SPEC. If ARG is absent (and a single SIGNAL_SPEC
+is supplied) or `-', each specified signal is reset to its original
+value. If ARG is the null string each SIGNAL_SPEC is ignored by the
+shell and by the commands it invokes.
+
+If a SIGNAL_SPEC is EXIT (0) ARG is executed on exit from the shell. If
+a SIGNAL_SPEC is DEBUG, ARG is executed before every simple command. If
+a SIGNAL_SPEC is RETURN, ARG is executed each time a shell function or a
+script run by the . or source builtins finishes executing. A SIGNAL_SPEC
+of ERR means to execute ARG each time a command's failure would cause the
+shell to exit when the -e option is enabled.
+
+If no arguments are supplied, trap prints the list of commands associated
+with each signal.
+
+Options:
+ -l print a list of signal names and their corresponding numbers
+ -p display the trap commands associated with each SIGNAL_SPEC
+
+Each SIGNAL_SPEC is either a signal name in <signal.h> or a signal number.
+Signal names are case insensitive and the SIG prefix is optional. A
+signal may be sent to the shell with "kill -signal $$".
+
+Exit Status:
+Returns success unless a SIGSPEC is invalid or an invalid option is given.
+$END
+
+#include <config.h>
+
+#if defined (HAVE_UNISTD_H)
+# ifdef _MINIX
+# include <sys/types.h>
+# endif
+# include <unistd.h>
+#endif
+
+#include "../bashtypes.h"
+#include <signal.h>
+#include <stdio.h>
+#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 <arg> <signal ...>
+ trap <signal ...>
+ 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/builtins/type.def b/builtins/type.def
new file mode 100644
index 0000000..a8e47c0
--- /dev/null
+++ b/builtins/type.def
@@ -0,0 +1,420 @@
+This file is type.def, from which is created type.c.
+It implements the builtin "type" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES type.c
+
+$BUILTIN type
+$FUNCTION type_builtin
+$SHORT_DOC type [-afptP] name [name ...]
+Display information about command type.
+
+For each NAME, indicate how it would be interpreted if used as a
+command name.
+
+Options:
+ -a display all locations containing an executable named NAME;
+ includes aliases, builtins, and functions, if and only if
+ the `-p' option is not also used
+ -f suppress shell function lookup
+ -P force a PATH search for each NAME, even if it is an alias,
+ builtin, or function, and returns the name of the disk file
+ that would be executed
+ -p returns either the name of the disk file that would be executed,
+ or nothing if `type -t NAME' would not return `file'
+ -t output a single word which is one of `alias', `keyword',
+ `function', `builtin', `file' or `', if NAME is an alias,
+ shell reserved word, shell function, shell builtin, disk file,
+ or not found, respectively
+
+Arguments:
+ NAME Command name to be interpreted.
+
+Exit Status:
+Returns success if all of the NAMEs are found; fails if any are not found.
+$END
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#include "posixstat.h"
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#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/builtins/ulimit.def b/builtins/ulimit.def
new file mode 100644
index 0000000..fd9f7a3
--- /dev/null
+++ b/builtins/ulimit.def
@@ -0,0 +1,809 @@
+This file is ulimit.def, from which is created ulimit.c.
+It implements the builtin "ulimit" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES ulimit.c
+
+$BUILTIN ulimit
+$FUNCTION ulimit_builtin
+$DEPENDS_ON !_MINIX
+$SHORT_DOC ulimit [-SHabcdefiklmnpqrstuvxPRT] [limit]
+Modify shell resource limits.
+
+Provides control over the resources available to the shell and processes
+it creates, on systems that allow such control.
+
+Options:
+ -S use the `soft' resource limit
+ -H use the `hard' resource limit
+ -a all current limits are reported
+ -b the socket buffer size
+ -c the maximum size of core files created
+ -d the maximum size of a process's data segment
+ -e the maximum scheduling priority (`nice')
+ -f the maximum size of files written by the shell and its children
+ -i the maximum number of pending signals
+ -k the maximum number of kqueues allocated for this process
+ -l the maximum size a process may lock into memory
+ -m the maximum resident set size
+ -n the maximum number of open file descriptors
+ -p the pipe buffer size
+ -q the maximum number of bytes in POSIX message queues
+ -r the maximum real-time scheduling priority
+ -s the maximum stack size
+ -t the maximum amount of cpu time in seconds
+ -u the maximum number of user processes
+ -v the size of virtual memory
+ -x the maximum number of file locks
+ -P the maximum number of pseudoterminals
+ -R the maximum time a real-time process can run before blocking
+ -T the maximum number of threads
+
+Not all options are available on all platforms.
+
+If LIMIT is given, it is the new value of the specified resource; the
+special LIMIT values `soft', `hard', and `unlimited' stand for the
+current soft limit, the current hard limit, and no limit, respectively.
+Otherwise, the current value of the specified resource is printed. If
+no option is given, then -f is assumed.
+
+Values are in 1024-byte increments, except for -t, which is in seconds,
+-p, which is in increments of 512 bytes, and -u, which is an unscaled
+number of processes.
+
+Exit Status:
+Returns success unless an invalid option is supplied or an error occurs.
+$END
+
+#if !defined (_MINIX)
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#if defined (HAVE_SYS_PARAM_H)
+# include <sys/param.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <errno.h>
+
+#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 <sys/resource.h>
+ and #undef it afterward. */
+#if defined (HAVE_RESOURCE)
+# include <sys/time.h>
+# if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL)
+# define _KERNEL
+# endif
+# include <sys/resource.h>
+# if defined (HPUX) && defined (RLIMIT_NEEDS_KERNEL)
+# undef _KERNEL
+# endif
+#elif defined (HAVE_SYS_TIMES_H)
+# include <sys/times.h>
+#endif
+
+#if defined (HAVE_LIMITS_H)
+# include <limits.h>
+#endif
+
+/* Check for the most basic symbols. If they aren't present, this
+ system's <sys/resource.h> 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 <ulimit.h>
+#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/builtins/umask.def b/builtins/umask.def
new file mode 100644
index 0000000..8041d56
--- /dev/null
+++ b/builtins/umask.def
@@ -0,0 +1,317 @@
+This file is umask.def, from which is created umask.c.
+It implements the builtin "umask" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$PRODUCES umask.c
+
+$BUILTIN umask
+$FUNCTION umask_builtin
+$SHORT_DOC umask [-p] [-S] [mode]
+Display or set file mode mask.
+
+Sets the user file-creation mask to MODE. If MODE is omitted, prints
+the current value of the mask.
+
+If MODE begins with a digit, it is interpreted as an octal number;
+otherwise it is a symbolic mode string like that accepted by chmod(1).
+
+Options:
+ -p if MODE is omitted, output in a form that may be reused as input
+ -S makes the output symbolic; otherwise an octal number is output
+
+Exit Status:
+Returns success unless MODE is invalid or an invalid option is given.
+$END
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#include "filecntl.h"
+#if ! defined(_MINIX) && defined (HAVE_SYS_FILE_H)
+# include <sys/file.h>
+#endif
+
+#if defined (HAVE_UNISTD_H)
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+#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/builtins/wait.def b/builtins/wait.def
new file mode 100644
index 0000000..b066d78
--- /dev/null
+++ b/builtins/wait.def
@@ -0,0 +1,381 @@
+'This file is wait.def, from which is created wait.c.
+It implements the builtin "wait" in Bash.
+
+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 <http://www.gnu.org/licenses/>.
+
+$BUILTIN wait
+$FUNCTION wait_builtin
+$DEPENDS_ON JOB_CONTROL
+$PRODUCES wait.c
+$SHORT_DOC wait [-fn] [-p var] [id ...]
+Wait for job completion and return exit status.
+
+Waits for each process identified by an ID, which may be a process ID or a
+job specification, and reports its termination status. If ID is not
+given, waits for all currently active child processes, and the return
+status is zero. If ID is a job specification, waits for all processes
+in that job's pipeline.
+
+If the -n option is supplied, waits for a single job from the list of IDs,
+or, if no IDs are supplied, for the next job to complete and returns its
+exit status.
+
+If the -p option is supplied, the process or job identifier of the job
+for which the exit status is returned is assigned to the variable VAR
+named by the option argument. The variable will be unset initially, before
+any assignment. This is useful only when the -n option is supplied.
+
+If the -f option is supplied, and job control is enabled, waits for the
+specified ID to terminate, instead of waiting for it to change status.
+
+Exit Status:
+Returns the status of the last ID; fails if ID is invalid or an invalid
+option is given, or if -n is supplied and the shell has no unwaited-for
+children.
+$END
+
+$BUILTIN wait
+$FUNCTION wait_builtin
+$DEPENDS_ON !JOB_CONTROL
+$SHORT_DOC wait [pid ...]
+Wait for process completion and return exit status.
+
+Waits for each process specified by a PID and reports its termination status.
+If PID is not given, waits for all currently active child processes,
+and the return status is zero. PID must be a process ID.
+
+Exit Status:
+Returns the status of the last PID; fails if PID is invalid or an invalid
+option is given.
+$END
+
+#include <config.h>
+
+#include "../bashtypes.h"
+#include <signal.h>
+
+#if defined (HAVE_UNISTD_H)
+# include <unistd.h>
+#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> [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