From 1d7ecd770cee8bfeb5e9c168472c89d7625ec579 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Sat, 3 Dec 2011 12:56:32 -0500 Subject: [PATCH] commit bash-20040408 snapshot --- CWRU/CWRU.chlog | 50 + CWRU/CWRU.chlog~ | 67 + builtins/alias.def | 16 +- builtins/alias.def~ | 229 +++ builtins/cd.def | 4 +- execute_cmd.c~ | 4023 +++++++++++++++++++++++++++++++++++++++++++++ externs.h | 1 + externs.h~ | 375 +++++ general.c | 16 + general.c~ | 958 +++++++++++ general.h | 1 + general.h~ | 311 ++++ include/shmbutil.h | 44 +- include/shmbutil.h.save1 | 470 ++++++ include/shmbutil.h~ | 1 + jobs.c | 6 + jobs.c~ | 3535 +++++++++++++++++++++++++++++++++++++++ print_cmd.c | 31 + print_cmd.c~ | 1282 +++++++++++++++ subst.c | 12 +- subst.c~ | 83 +- syntax.h | 2 + syntax.h~ | 100 ++ tests/RUN-ONE-TEST | 2 +- tests/errors.right | 2 +- tests/errors.right~ | 100 ++ variables.c | 9 +- variables.c~ | 4092 ++++++++++++++++++++++++++++++++++++++++++++++ 28 files changed, 15744 insertions(+), 78 deletions(-) create mode 100644 builtins/alias.def~ create mode 100644 execute_cmd.c~ create mode 100644 externs.h~ create mode 100644 general.c~ create mode 100644 general.h~ create mode 100644 include/shmbutil.h.save1 create mode 100644 jobs.c~ create mode 100644 print_cmd.c~ create mode 100644 syntax.h~ create mode 100644 tests/errors.right~ create mode 100644 variables.c~ diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 9130bf0..7b16815 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -9317,3 +9317,53 @@ configure.in include/posixdir.h - use new and renamed HAVE_STRUCT_DIRENT_D_xxx defines + + 4/7 + --- +builtins/cd.def + - ensure that we print out a non-null pathname after getting a + directory from CDPATH and canonicalizing it (e.g., if the result + exceeds PATH_MAX in length and the_current_working_directory is + set to NULL) + + 4/12 + ---- +print_cmd.c + - new functionto print out assignment statements when `set -x' has + been enabled: xtrace_print_assignment + +externs.h + - extern declaration for xtrace_print_assignment + + 4/13 + ---- +{subst,variables}.c + - call xtrace_print_assignment instead of using inline code + +jobs.c + - if turning on job control when it was previously off, set + pipeline_pgrp to 0 in set_job_control so make_child puts + subsequent children in their own process group + + 4/14 + ---- +general.c + - new function, legal_alias_name, called to decide whether an + argument to add_alias is a valid alias name -- essentially any + character except one which must be quoted to the shell parser + and `/' + +general.h + - new extern declaration for legal_alias_name + +builtins/alias.def + - `unalias' now returns failure status if no NAME arguments are + supplied and -a is not given + - call legal_alias_name to make sure alias name is valid before + calling add_alias from alias_builtin + + 4/19 + ---- +include/shmbutil.h + - include for definition of HANDLE_MULTIBYTE rather than + duplicating logic diff --git a/CWRU/CWRU.chlog~ b/CWRU/CWRU.chlog~ index a35dff8..126cd0c 100644 --- a/CWRU/CWRU.chlog~ +++ b/CWRU/CWRU.chlog~ @@ -9264,6 +9264,13 @@ expr.c pre-decrement work when separated from their accompanying identifier by whitespace + 3/18 + ---- +lib/readline/misc.c + - in rl_maybe_unsave_line, don't force rl_replace_line to clear + the undo_list, since it might point directly at an undo list + from a history entry (to which we have no handle) + 3/19 ---- lib/readline/display.c @@ -9295,3 +9302,63 @@ bashhist.c that returned `print only' to the history list, since history_expand no longer does it (and, when using readline, do it only when rl_dispatching is zero) + + 3/22 + ---- +config.h.in,aclocal.m4 + - change bash-specific functions that look in struct dirent to define + HAVE_STRUCT_DIRENT_xxx, like AC_CHECK_MEMBERS does (though the + functions are otherwise the same) + - new function, BASH_STRUCT_DIRENT_D_NAMLEN, define + HAVE_STRUCT_DIRENT_D_NAMLEN if struct dirent has a `d_namlen' member + +configure.in + - call BASH_STRUCT_DIRENT_D_NAMLEN + +include/posixdir.h + - use new and renamed HAVE_STRUCT_DIRENT_D_xxx defines + + 4/7 + --- +builtins/cd.def + - ensure that we print out a non-null pathname after getting a + directory from CDPATH and canonicalizing it (e.g., if the result + exceeds PATH_MAX in length and the_current_working_directory is + set to NULL) + + 4/12 + ---- +print_cmd.c + - new functionto print out assignment statements when `set -x' has + been enabled: xtrace_print_assignment + +externs.h + - extern declaration for xtrace_print_assignment + + 4/13 + ---- +{subst,variables}.c + - call xtrace_print_assignment instead of using inline code + +jobs.c + - if turning on job control when it was previously off, set + pipeline_pgrp to 0 in set_job_control so make_child puts + subsequent children in their own process group + + 4/14 + ---- +general.c + - new function, legal_alias_name, called to decide whether an + argument to add_alias is a valid alias name -- essentially any + character except one which must be quoted to the shell parser + and `/' + +general.h + - new extern declaration for legal_alias_name + +builtins/alias.def + - `unalias' now returns failure status if no NAME arguments are + supplied and -a is not given + - call legal_alias_name to make sure alias name is valid before + calling add_alias from alias_builtin + diff --git a/builtins/alias.def b/builtins/alias.def index 5c7ed3d..ec83d0b 100644 --- a/builtins/alias.def +++ b/builtins/alias.def @@ -118,7 +118,13 @@ alias_builtin (list) name[offset] = '\0'; value = name + offset + 1; - add_alias (name, value); + if (legal_alias_name (name, 0) == 0) + { + builtin_error ("%s: invalid alias name", name); + any_failed++; + } + else + add_alias (name, value); } else { @@ -141,7 +147,7 @@ alias_builtin (list) $BUILTIN unalias $FUNCTION unalias_builtin $DEPENDS_ON ALIAS -$SHORT_DOC unalias [-a] [name ...] +$SHORT_DOC unalias [-a] name [name ...] Remove NAMEs from the list of defined aliases. If the -a option is given, then remove all alias definitions. $END @@ -178,6 +184,12 @@ unalias_builtin (list) return (EXECUTION_SUCCESS); } + if (list == 0) + { + builtin_usage (); + return (EX_USAGE); + } + aflag = 0; while (list) { diff --git a/builtins/alias.def~ b/builtins/alias.def~ new file mode 100644 index 0000000..2ffae28 --- /dev/null +++ b/builtins/alias.def~ @@ -0,0 +1,229 @@ +This file is alias.def, from which is created alias.c +It implements the builtins "alias" and "unalias" in Bash. + +Copyright (C) 1987-2002 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2, or (at your option) any later +version. + +Bash is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with Bash; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +$BUILTIN alias +$FUNCTION alias_builtin +$DEPENDS_ON ALIAS +$PRODUCES alias.c +$SHORT_DOC alias [-p] [name[=value] ... ] +`alias' with no arguments or with the -p option prints the list +of aliases in the 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. Alias returns +true unless a NAME is given for which no alias has been defined. +$END + +#include + +#if defined (ALIAS) + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +# include "../bashansi.h" + +# include +# include "../shell.h" +# include "../alias.h" +# include "common.h" +# include "bashgetopt.h" + +/* Flags for print_alias */ +#define AL_REUSABLE 0x01 + +static void print_alias __P((alias_t *, int)); + +extern int posixly_correct; + +/* 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; + 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 (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++; + continue; + } + + 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 NAMEs from the list of defined aliases. If the -a option is given, +then remove all alias definitions. +$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; + 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 "); + printf ("%s=%s\n", alias->name, value); + free (value); + + fflush (stdout); +} +#endif /* ALIAS */ diff --git a/builtins/cd.def b/builtins/cd.def index 10cbe98..a902476 100644 --- a/builtins/cd.def +++ b/builtins/cd.def @@ -228,8 +228,8 @@ cd_builtin (list) 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) - printf ("%s\n", no_symlinks ? temp : the_current_working_directory); + if (opt && (path = no_symlinks ? temp : the_current_working_directory)) + printf ("%s\n", path); free (temp); /* Posix.2 says that after using CDPATH, the resultant diff --git a/execute_cmd.c~ b/execute_cmd.c~ new file mode 100644 index 0000000..3aefd2f --- /dev/null +++ b/execute_cmd.c~ @@ -0,0 +1,4023 @@ +/* execute_command.c -- Execute a COMMAND structure. */ + +/* Copyright (C) 1987-2003 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Bash is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with Bash; see the file COPYING. If not, write to the Free + Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ +#include "config.h" + +#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) + #pragma alloca +#endif /* _AIX && RISC6000 && !__GNUC__ */ + +#include +#include "chartypes.h" +#include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "filecntl.h" +#include "posixstat.h" +#include +#ifndef _MINIX +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "posixtime.h" + +#if defined (HAVE_SYS_RESOURCE_H) && !defined (RLIMTYPE) +# include +#endif + +#if defined (HAVE_SYS_TIMES_H) && defined (HAVE_TIMES) +# include +#endif + +#include + +#if !defined (errno) +extern int errno; +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "memalloc.h" +#include "shell.h" +#include /* use <...> so we pick it up from the build directory */ +#include "flags.h" +#include "builtins.h" +#include "hashlib.h" +#include "jobs.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "redir.h" +#include "trap.h" +#include "pathexp.h" +#include "hashcmd.h" + +#if defined (COND_COMMAND) +# include "test.h" +#endif + +#include "builtins/common.h" +#include "builtins/builtext.h" /* list of builtins */ + +#include +#include + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +#if defined (ALIAS) +# include "alias.h" +#endif + +#if defined (HISTORY) +# include "bashhist.h" +#endif + +extern int posixly_correct; +extern int breaking, continuing, loop_level; +extern int expand_aliases; +extern int parse_and_execute_level, running_trap; +extern int command_string_index, line_number; +extern int dot_found_in_search; +extern int already_making_children; +extern char *the_printed_command, *shell_name; +extern pid_t last_command_subst_pid; +extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin; +extern char **subshell_argv, **subshell_envp; +extern int subshell_argc; +#if 0 +extern char *glob_argv_flags; +#endif + +extern int close __P((int)); + +/* Static functions defined and used in this file. */ +static void close_pipes __P((int, int)); +static void do_piping __P((int, int)); +static void bind_lastarg __P((char *)); +static int shell_control_structure __P((enum command_type)); +static void cleanup_redirects __P((REDIRECT *)); + +#if defined (JOB_CONTROL) +static int restore_signal_mask __P((sigset_t *)); +#endif + +static void async_redirect_stdin __P((void)); + +static int builtin_status __P((int)); + +static int execute_for_command __P((FOR_COM *)); +#if defined (SELECT_COMMAND) +static int print_index_and_element __P((int, int, WORD_LIST *)); +static void indent __P((int, int)); +static void print_select_list __P((WORD_LIST *, int, int, int)); +static char *select_query __P((WORD_LIST *, int, char *, int)); +static int execute_select_command __P((SELECT_COM *)); +#endif +#if defined (DPAREN_ARITHMETIC) +static int execute_arith_command __P((ARITH_COM *)); +#endif +#if defined (COND_COMMAND) +static int execute_cond_node __P((COND_COM *)); +static int execute_cond_command __P((COND_COM *)); +#endif +#if defined (COMMAND_TIMING) +static int mkfmt __P((char *, int, int, time_t, int)); +static void print_formatted_time __P((FILE *, char *, + time_t, int, time_t, int, + time_t, int, int)); +static int time_command __P((COMMAND *, int, int, int, struct fd_bitmap *)); +#endif +#if defined (ARITH_FOR_COMMAND) +static intmax_t eval_arith_for_expr __P((WORD_LIST *, int *)); +static int execute_arith_for_command __P((ARITH_FOR_COM *)); +#endif +static int execute_case_command __P((CASE_COM *)); +static int execute_while_command __P((WHILE_COM *)); +static int execute_until_command __P((WHILE_COM *)); +static int execute_while_or_until __P((WHILE_COM *, int)); +static int execute_if_command __P((IF_COM *)); +static int execute_null_command __P((REDIRECT *, int, int, int, pid_t)); +static void fix_assignment_words __P((WORD_LIST *)); +static int execute_simple_command __P((SIMPLE_COM *, int, int, int, struct fd_bitmap *)); +static int execute_builtin __P((sh_builtin_func_t *, WORD_LIST *, int, int)); +static int execute_function __P((SHELL_VAR *, WORD_LIST *, int, struct fd_bitmap *, int, int)); +static int execute_builtin_or_function __P((WORD_LIST *, sh_builtin_func_t *, + SHELL_VAR *, + REDIRECT *, struct fd_bitmap *, int)); +static void execute_subshell_builtin_or_function __P((WORD_LIST *, REDIRECT *, + sh_builtin_func_t *, + SHELL_VAR *, + int, int, int, + struct fd_bitmap *, + int)); +static void execute_disk_command __P((WORD_LIST *, REDIRECT *, char *, + int, int, int, struct fd_bitmap *, int)); + +static char *getinterp __P((char *, int, int *)); +static void initialize_subshell __P((void)); +static int execute_in_subshell __P((COMMAND *, int, int, int, struct fd_bitmap *)); + +static int execute_pipeline __P((COMMAND *, int, int, int, struct fd_bitmap *)); + +static int execute_connection __P((COMMAND *, int, int, int, struct fd_bitmap *)); + +static int execute_intern_function __P((WORD_DESC *, COMMAND *)); + +/* The line number that the currently executing function starts on. */ +static int function_line_number; + +/* Set to 1 if fd 0 was the subject of redirection to a subshell. Global + so that reader_loop can set it to zero before executing a command. */ +int stdin_redir; + +/* The name of the command that is currently being executed. + `test' needs this, for example. */ +char *this_command_name; + +/* The printed representation of the currently-executing command (same as + the_printed_command), except when a trap is being executed. Useful for + a debugger to know where exactly the program is currently executing. */ +char *the_printed_command_except_trap; + +static COMMAND *currently_executing_command; + +struct stat SB; /* used for debugging */ + +static int special_builtin_failed; + +/* XXX - set to 1 if we're running the DEBUG trap and we want to show the line + number containing the function name. Used by executing_line_number to + report the correct line number. Kind of a hack. */ +static int showing_function_line; + +/* For catching RETURN in a function. */ +int return_catch_flag; +int return_catch_value; +procenv_t return_catch; + +/* The value returned by the last synchronous command. */ +int last_command_exit_value; + +/* Whether or not the last command (corresponding to last_command_exit_value) + was terminated by a signal, and, if so, which one. */ +int last_command_exit_signal; + +/* The list of redirections to perform which will undo the redirections + that I made in the shell. */ +REDIRECT *redirection_undo_list = (REDIRECT *)NULL; + +/* The list of redirections to perform which will undo the internal + redirections performed by the `exec' builtin. These are redirections + that must be undone even when exec discards redirection_undo_list. */ +REDIRECT *exec_redirection_undo_list = (REDIRECT *)NULL; + +/* Non-zero if we have just forked and are currently running in a subshell + environment. */ +int subshell_environment; + +/* Count of nested subshells, like SHLVL. Available via $BASH_SUBSHELL */ +int subshell_level = 0; + +/* Currently-executing shell function. */ +SHELL_VAR *this_shell_function; + +struct fd_bitmap *current_fds_to_close = (struct fd_bitmap *)NULL; + +#define FD_BITMAP_DEFAULT_SIZE 32 + +/* Functions to allocate and deallocate the structures used to pass + information from the shell to its children about file descriptors + to close. */ +struct fd_bitmap * +new_fd_bitmap (size) + int size; +{ + struct fd_bitmap *ret; + + ret = (struct fd_bitmap *)xmalloc (sizeof (struct fd_bitmap)); + + ret->size = size; + + if (size) + { + ret->bitmap = (char *)xmalloc (size); + memset (ret->bitmap, '\0', size); + } + else + ret->bitmap = (char *)NULL; + return (ret); +} + +void +dispose_fd_bitmap (fdbp) + struct fd_bitmap *fdbp; +{ + FREE (fdbp->bitmap); + free (fdbp); +} + +void +close_fd_bitmap (fdbp) + struct fd_bitmap *fdbp; +{ + register int i; + + if (fdbp) + { + for (i = 0; i < fdbp->size; i++) + if (fdbp->bitmap[i]) + { + close (i); + fdbp->bitmap[i] = 0; + } + } +} + +/* Return the line number of the currently executing command. */ +int +executing_line_number () +{ + if (executing && showing_function_line == 0 && + (variable_context == 0 || interactive_shell == 0) && + currently_executing_command) + { +#if defined (COND_COMMAND) + if (currently_executing_command->type == cm_cond) + return currently_executing_command->value.Cond->line; +#endif +#if defined (DPAREN_ARITHMETIC) + else if (currently_executing_command->type == cm_arith) + return currently_executing_command->value.Arith->line; +#endif +#if defined (ARITH_FOR_COMMAND) + else if (currently_executing_command->type == cm_arith_for) + return currently_executing_command->value.ArithFor->line; +#endif + + return line_number; + } + else + return line_number; +} + +/* Execute the command passed in COMMAND. COMMAND is exactly what + read_command () places into GLOBAL_COMMAND. See "command.h" for the + details of the command structure. + + EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible + return values. Executing a command with nothing in it returns + EXECUTION_SUCCESS. */ +int +execute_command (command) + COMMAND *command; +{ + struct fd_bitmap *bitmap; + int result; + + current_fds_to_close = (struct fd_bitmap *)NULL; + bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE); + begin_unwind_frame ("execute-command"); + add_unwind_protect (dispose_fd_bitmap, (char *)bitmap); + + /* Just do the command, but not asynchronously. */ + result = execute_command_internal (command, 0, NO_PIPE, NO_PIPE, bitmap); + + dispose_fd_bitmap (bitmap); + discard_unwind_frame ("execute-command"); + +#if defined (PROCESS_SUBSTITUTION) + /* don't unlink fifos if we're in a shell function; wait until the function + returns. */ + if (variable_context == 0) + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + + return (result); +} + +/* Return 1 if TYPE is a shell control structure type. */ +static int +shell_control_structure (type) + enum command_type type; +{ + switch (type) + { + case cm_for: +#if defined (ARITH_FOR_COMMAND) + case cm_arith_for: +#endif +#if defined (SELECT_COMMAND) + case cm_select: +#endif +#if defined (DPAREN_ARITHMETIC) + case cm_arith: +#endif +#if defined (COND_COMMAND) + case cm_cond: +#endif + case cm_case: + case cm_while: + case cm_until: + case cm_if: + case cm_group: + return (1); + + default: + return (0); + } +} + +/* A function to use to unwind_protect the redirection undo list + for loops. */ +static void +cleanup_redirects (list) + REDIRECT *list; +{ + do_redirections (list, RX_ACTIVE); + dispose_redirects (list); +} + +#if 0 +/* Function to unwind_protect the redirections for functions and builtins. */ +static void +cleanup_func_redirects (list) + REDIRECT *list; +{ + do_redirections (list, RX_ACTIVE); +} +#endif + +void +dispose_exec_redirects () +{ + if (exec_redirection_undo_list) + { + dispose_redirects (exec_redirection_undo_list); + exec_redirection_undo_list = (REDIRECT *)NULL; + } +} + +#if defined (JOB_CONTROL) +/* A function to restore the signal mask to its proper value when the shell + is interrupted or errors occur while creating a pipeline. */ +static int +restore_signal_mask (set) + sigset_t *set; +{ + return (sigprocmask (SIG_SETMASK, set, (sigset_t *)NULL)); +} +#endif /* JOB_CONTROL */ + +#ifdef DEBUG +/* A debugging function that can be called from gdb, for instance. */ +void +open_files () +{ + register int i; + int f, fd_table_size; + + fd_table_size = getdtablesize (); + + fprintf (stderr, "pid %ld open files:", (long)getpid ()); + for (i = 3; i < fd_table_size; i++) + { + if ((f = fcntl (i, F_GETFD, 0)) != -1) + fprintf (stderr, " %d (%s)", i, f ? "close" : "open"); + } + fprintf (stderr, "\n"); +} +#endif + +static void +async_redirect_stdin () +{ + int fd; + + fd = open ("/dev/null", O_RDONLY); + if (fd > 0) + { + dup2 (fd, 0); + close (fd); + } + else if (fd < 0) + internal_error (_("cannot redirect standard input from /dev/null: %s"), strerror (errno)); +} + +#define DESCRIBE_PID(pid) do { if (interactive) describe_pid (pid); } while (0) + +/* Execute the command passed in COMMAND, perhaps doing it asynchrounously. + COMMAND is exactly what read_command () places into GLOBAL_COMMAND. + ASYNCHROUNOUS, if non-zero, says to do this command in the background. + PIPE_IN and PIPE_OUT are file descriptors saying where input comes + from and where it goes. They can have the value of NO_PIPE, which means + I/O is stdin/stdout. + FDS_TO_CLOSE is a list of file descriptors to close once the child has + been forked. This list often contains the unusable sides of pipes, etc. + + EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible + return values. Executing a command with nothing in it returns + EXECUTION_SUCCESS. */ +int +execute_command_internal (command, asynchronous, pipe_in, pipe_out, + fds_to_close) + COMMAND *command; + int asynchronous; + int pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + int exec_result, invert, ignore_return, was_error_trap; + REDIRECT *my_undo_list, *exec_undo_list; + volatile pid_t last_pid; + volatile int save_line_number; + + if (command == 0 || breaking || continuing || read_but_dont_execute) + return (EXECUTION_SUCCESS); + + run_pending_traps (); + +#if 0 + if (running_trap == 0) +#endif + currently_executing_command = command; + + invert = (command->flags & CMD_INVERT_RETURN) != 0; + + /* If we're inverting the return value and `set -e' has been executed, + we don't want a failing command to inadvertently cause the shell + to exit. */ + if (exit_immediately_on_error && invert) /* XXX */ + command->flags |= CMD_IGNORE_RETURN; /* XXX */ + + exec_result = EXECUTION_SUCCESS; + + /* If a command was being explicitly run in a subshell, or if it is + a shell control-structure, and it has a pipe, then we do the command + in a subshell. */ + if (command->type == cm_subshell && (command->flags & CMD_NO_FORK)) + return (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)); + + if (command->type == cm_subshell || + (command->flags & (CMD_WANT_SUBSHELL|CMD_FORCE_SUBSHELL)) || + (shell_control_structure (command->type) && + (pipe_out != NO_PIPE || pipe_in != NO_PIPE || asynchronous))) + { + pid_t paren_pid; + +if (asynchronous) +itrace("execute_command_internal: making child: asynchronous = 1 job_control = %d", job_control); + /* Fork a subshell, turn off the subshell bit, turn off job + control and call execute_command () on the command again. */ + paren_pid = make_child (savestring (make_command_string (command)), + asynchronous); + if (paren_pid == 0) + exit (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)); + /* NOTREACHED */ + else + { + close_pipes (pipe_in, pipe_out); + +#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) + unlink_fifo_list (); +#endif + /* If we are part of a pipeline, and not the end of the pipeline, + then we should simply return and let the last command in the + pipe be waited for. If we are not in a pipeline, or are the + last command in the pipeline, then we wait for the subshell + and return its exit status as usual. */ + if (pipe_out != NO_PIPE) + return (EXECUTION_SUCCESS); + + stop_pipeline (asynchronous, (COMMAND *)NULL); + + if (asynchronous == 0) + { + last_command_exit_value = wait_for (paren_pid); + + /* If we have to, invert the return value. */ + if (invert) + exec_result = ((last_command_exit_value == EXECUTION_SUCCESS) + ? EXECUTION_FAILURE + : EXECUTION_SUCCESS); + else + exec_result = last_command_exit_value; + + return (last_command_exit_value = exec_result); + } + else + { + DESCRIBE_PID (paren_pid); + + run_pending_traps (); + + return (EXECUTION_SUCCESS); + } + } + } + +#if defined (COMMAND_TIMING) + if (command->flags & CMD_TIME_PIPELINE) + { + if (asynchronous) + { + command->flags |= CMD_FORCE_SUBSHELL; + exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); + } + else + { + exec_result = time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close); +#if 0 + if (running_trap == 0) +#endif + currently_executing_command = (COMMAND *)NULL; + } + return (exec_result); + } +#endif /* COMMAND_TIMING */ + + if (shell_control_structure (command->type) && command->redirects) + stdin_redir = stdin_redirects (command->redirects); + + /* Handle WHILE FOR CASE etc. with redirections. (Also '&' input + redirection.) */ + if (do_redirections (command->redirects, RX_ACTIVE|RX_UNDOABLE) != 0) + { + cleanup_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + dispose_exec_redirects (); + return (EXECUTION_FAILURE); + } + + if (redirection_undo_list) + { + my_undo_list = (REDIRECT *)copy_redirects (redirection_undo_list); + dispose_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + } + else + my_undo_list = (REDIRECT *)NULL; + + if (exec_redirection_undo_list) + { + exec_undo_list = (REDIRECT *)copy_redirects (exec_redirection_undo_list); + dispose_redirects (exec_redirection_undo_list); + exec_redirection_undo_list = (REDIRECT *)NULL; + } + else + exec_undo_list = (REDIRECT *)NULL; + + if (my_undo_list || exec_undo_list) + begin_unwind_frame ("loop_redirections"); + + if (my_undo_list) + add_unwind_protect ((Function *)cleanup_redirects, my_undo_list); + + if (exec_undo_list) + add_unwind_protect ((Function *)dispose_redirects, exec_undo_list); + + ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; + + QUIT; + + switch (command->type) + { + case cm_simple: + { + save_line_number = line_number; + /* We can't rely on variables retaining their values across a + call to execute_simple_command if a longjmp occurs as the + result of a `return' builtin. This is true for sure with gcc. */ + last_pid = last_made_pid; + was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0; + + if (ignore_return && command->value.Simple) + command->value.Simple->flags |= CMD_IGNORE_RETURN; + if (command->flags & CMD_STDIN_REDIR) + command->value.Simple->flags |= CMD_STDIN_REDIR; + + line_number = command->value.Simple->line; + exec_result = + execute_simple_command (command->value.Simple, pipe_in, pipe_out, + asynchronous, fds_to_close); + line_number = save_line_number; + + /* The temporary environment should be used for only the simple + command immediately following its definition. */ + dispose_used_env_vars (); + +#if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA) + /* Reclaim memory allocated with alloca () on machines which + may be using the alloca emulation code. */ + (void) alloca (0); +#endif /* (ultrix && mips) || C_ALLOCA */ + + /* If we forked to do the command, then we must wait_for () + the child. */ + + /* XXX - this is something to watch out for if there are problems + when the shell is compiled without job control. */ + if (already_making_children && pipe_out == NO_PIPE && + last_pid != last_made_pid) + { + stop_pipeline (asynchronous, (COMMAND *)NULL); + + if (asynchronous) + { + DESCRIBE_PID (last_made_pid); + } + else +#if !defined (JOB_CONTROL) + /* Do not wait for asynchronous processes started from + startup files. */ + if (last_made_pid != last_asynchronous_pid) +#endif + /* When executing a shell function that executes other + commands, this causes the last simple command in + the function to be waited for twice. This also causes + subshells forked to execute builtin commands (e.g., in + pipelines) to be waited for twice. */ + exec_result = wait_for (last_made_pid); +#if defined (RECYCLES_PIDS) + /* LynxOS, for one, recycles pids very quickly -- so quickly + that a new process may have the same pid as the last one + created. This has been reported to fix the problem on that + OS, and a similar problem on Cygwin. */ + if (exec_result == 0) + last_made_pid = NO_PID; +#endif + } + } + + if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS) + { + last_command_exit_value = exec_result; + run_error_trap (); + } + + if (ignore_return == 0 && invert == 0 && + ((posixly_correct && interactive == 0 && special_builtin_failed) || + (exit_immediately_on_error && (exec_result != EXECUTION_SUCCESS)))) + { + last_command_exit_value = exec_result; + run_pending_traps (); + jump_to_top_level (ERREXIT); + } + + break; + + case cm_for: + if (ignore_return) + command->value.For->flags |= CMD_IGNORE_RETURN; + exec_result = execute_for_command (command->value.For); + break; + +#if defined (ARITH_FOR_COMMAND) + case cm_arith_for: + if (ignore_return) + command->value.ArithFor->flags |= CMD_IGNORE_RETURN; + exec_result = execute_arith_for_command (command->value.ArithFor); + break; +#endif + +#if defined (SELECT_COMMAND) + case cm_select: + if (ignore_return) + command->value.Select->flags |= CMD_IGNORE_RETURN; + exec_result = execute_select_command (command->value.Select); + break; +#endif + + case cm_case: + if (ignore_return) + command->value.Case->flags |= CMD_IGNORE_RETURN; + exec_result = execute_case_command (command->value.Case); + break; + + case cm_while: + if (ignore_return) + command->value.While->flags |= CMD_IGNORE_RETURN; + exec_result = execute_while_command (command->value.While); + break; + + case cm_until: + if (ignore_return) + command->value.While->flags |= CMD_IGNORE_RETURN; + exec_result = execute_until_command (command->value.While); + break; + + case cm_if: + if (ignore_return) + command->value.If->flags |= CMD_IGNORE_RETURN; + exec_result = execute_if_command (command->value.If); + break; + + case cm_group: + + /* This code can be executed from either of two paths: an explicit + '{}' command, or via a function call. If we are executed via a + function call, we have already taken care of the function being + executed in the background (down there in execute_simple_command ()), + and this command should *not* be marked as asynchronous. If we + are executing a regular '{}' group command, and asynchronous == 1, + we must want to execute the whole command in the background, so we + need a subshell, and we want the stuff executed in that subshell + (this group command) to be executed in the foreground of that + subshell (i.e. there will not be *another* subshell forked). + + What we do is to force a subshell if asynchronous, and then call + execute_command_internal again with asynchronous still set to 1, + but with the original group command, so the printed command will + look right. + + The code above that handles forking off subshells will note that + both subshell and async are on, and turn off async in the child + after forking the subshell (but leave async set in the parent, so + the normal call to describe_pid is made). This turning off + async is *crucial*; if it is not done, this will fall into an + infinite loop of executions through this spot in subshell after + subshell until the process limit is exhausted. */ + + if (asynchronous) + { + command->flags |= CMD_FORCE_SUBSHELL; + exec_result = + execute_command_internal (command, 1, pipe_in, pipe_out, + fds_to_close); + } + else + { + if (ignore_return && command->value.Group->command) + command->value.Group->command->flags |= CMD_IGNORE_RETURN; + exec_result = + execute_command_internal (command->value.Group->command, + asynchronous, pipe_in, pipe_out, + fds_to_close); + } + break; + + case cm_connection: + exec_result = execute_connection (command, asynchronous, + pipe_in, pipe_out, fds_to_close); + break; + +#if defined (DPAREN_ARITHMETIC) + case cm_arith: + if (ignore_return) + command->value.Arith->flags |= CMD_IGNORE_RETURN; + exec_result = execute_arith_command (command->value.Arith); + break; +#endif + +#if defined (COND_COMMAND) + case cm_cond: + if (ignore_return) + command->value.Cond->flags |= CMD_IGNORE_RETURN; + save_line_number = line_number; + exec_result = execute_cond_command (command->value.Cond); + line_number = save_line_number; + break; +#endif + + case cm_function_def: + exec_result = execute_intern_function (command->value.Function_def->name, + command->value.Function_def->command); + break; + + default: + command_error ("execute_command", CMDERR_BADTYPE, command->type, 0); + } + + if (my_undo_list) + { + do_redirections (my_undo_list, RX_ACTIVE); + dispose_redirects (my_undo_list); + } + + if (exec_undo_list) + dispose_redirects (exec_undo_list); + + if (my_undo_list || exec_undo_list) + discard_unwind_frame ("loop_redirections"); + + /* Invert the return value if we have to */ + if (invert) + exec_result = (exec_result == EXECUTION_SUCCESS) + ? EXECUTION_FAILURE + : EXECUTION_SUCCESS; + + last_command_exit_value = exec_result; + run_pending_traps (); +#if 0 + if (running_trap == 0) +#endif + currently_executing_command = (COMMAND *)NULL; + return (last_command_exit_value); +} + +#if defined (COMMAND_TIMING) + +#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) +extern struct timeval *difftimeval __P((struct timeval *, struct timeval *, struct timeval *)); +extern struct timeval *addtimeval __P((struct timeval *, struct timeval *, struct timeval *)); +extern int timeval_to_cpu __P((struct timeval *, struct timeval *, struct timeval *)); +#endif + +#define POSIX_TIMEFORMAT "real %2R\nuser %2U\nsys %2S" +#define BASH_TIMEFORMAT "\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS" + +static int precs[] = { 0, 100, 10, 1 }; + +/* Expand one `%'-prefixed escape sequence from a time format string. */ +static int +mkfmt (buf, prec, lng, sec, sec_fraction) + char *buf; + int prec, lng; + time_t sec; + int sec_fraction; +{ + time_t min; + char abuf[INT_STRLEN_BOUND(time_t) + 1]; + int ind, aind; + + ind = 0; + abuf[sizeof(abuf) - 1] = '\0'; + + /* If LNG is non-zero, we want to decompose SEC into minutes and seconds. */ + if (lng) + { + min = sec / 60; + sec %= 60; + aind = sizeof(abuf) - 2; + do + abuf[aind--] = (min % 10) + '0'; + while (min /= 10); + aind++; + while (abuf[aind]) + buf[ind++] = abuf[aind++]; + buf[ind++] = 'm'; + } + + /* Now add the seconds. */ + aind = sizeof (abuf) - 2; + do + abuf[aind--] = (sec % 10) + '0'; + while (sec /= 10); + aind++; + while (abuf[aind]) + buf[ind++] = abuf[aind++]; + + /* We want to add a decimal point and PREC places after it if PREC is + nonzero. PREC is not greater than 3. SEC_FRACTION is between 0 + and 999. */ + if (prec != 0) + { + buf[ind++] = '.'; + for (aind = 1; aind <= prec; aind++) + { + buf[ind++] = (sec_fraction / precs[aind]) + '0'; + sec_fraction %= precs[aind]; + } + } + + if (lng) + buf[ind++] = 's'; + buf[ind] = '\0'; + + return (ind); +} + +/* Interpret the format string FORMAT, interpolating the following escape + sequences: + %[prec][l][RUS] + + where the optional `prec' is a precision, meaning the number of + characters after the decimal point, the optional `l' means to format + using minutes and seconds (MMmNN[.FF]s), like the `times' builtin', + and the last character is one of + + R number of seconds of `real' time + U number of seconds of `user' time + S number of seconds of `system' time + + An occurrence of `%%' in the format string is translated to a `%'. The + result is printed to FP, a pointer to a FILE. The other variables are + the seconds and thousandths of a second of real, user, and system time, + resectively. */ +static void +print_formatted_time (fp, format, rs, rsf, us, usf, ss, ssf, cpu) + FILE *fp; + char *format; + time_t rs; + int rsf; + time_t us; + int usf; + time_t ss; + int ssf, cpu; +{ + int prec, lng, len; + char *str, *s, ts[INT_STRLEN_BOUND (time_t) + sizeof ("mSS.FFFF")]; + time_t sum; + int sum_frac; + int sindex, ssize; + + len = strlen (format); + ssize = (len + 64) - (len % 64); + str = (char *)xmalloc (ssize); + sindex = 0; + + for (s = format; *s; s++) + { + if (*s != '%' || s[1] == '\0') + { + RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64); + str[sindex++] = *s; + } + else if (s[1] == '%') + { + s++; + RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64); + str[sindex++] = *s; + } + else if (s[1] == 'P') + { + s++; + if (cpu > 10000) + cpu = 10000; + sum = cpu / 100; + sum_frac = (cpu % 100) * 10; + len = mkfmt (ts, 2, 0, sum, sum_frac); + RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64); + strcpy (str + sindex, ts); + sindex += len; + } + else + { + prec = 3; /* default is three places past the decimal point. */ + lng = 0; /* default is to not use minutes or append `s' */ + s++; + if (DIGIT (*s)) /* `precision' */ + { + prec = *s++ - '0'; + if (prec > 3) prec = 3; + } + if (*s == 'l') /* `length extender' */ + { + lng = 1; + s++; + } + if (*s == 'R' || *s == 'E') + len = mkfmt (ts, prec, lng, rs, rsf); + else if (*s == 'U') + len = mkfmt (ts, prec, lng, us, usf); + else if (*s == 'S') + len = mkfmt (ts, prec, lng, ss, ssf); + else + { + internal_error (_("TIMEFORMAT: `%c': invalid format character"), *s); + free (str); + return; + } + RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64); + strcpy (str + sindex, ts); + sindex += len; + } + } + + str[sindex] = '\0'; + fprintf (fp, "%s\n", str); + fflush (fp); + + free (str); +} + +static int +time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int asynchronous, pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + int rv, posix_time, old_flags; + time_t rs, us, ss; + int rsf, usf, ssf; + int cpu; + char *time_format; + +#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) + struct timeval real, user, sys; + struct timeval before, after; +# if defined (HAVE_STRUCT_TIMEZONE) + struct timezone dtz; /* posix doesn't define this */ +# endif + struct rusage selfb, selfa, kidsb, kidsa; /* a = after, b = before */ +#else +# if defined (HAVE_TIMES) + clock_t tbefore, tafter, real, user, sys; + struct tms before, after; +# endif +#endif + +#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) +# if defined (HAVE_STRUCT_TIMEZONE) + gettimeofday (&before, &dtz); +# else + gettimeofday (&before, (void *)NULL); +# endif /* !HAVE_STRUCT_TIMEZONE */ + getrusage (RUSAGE_SELF, &selfb); + getrusage (RUSAGE_CHILDREN, &kidsb); +#else +# if defined (HAVE_TIMES) + tbefore = times (&before); +# endif +#endif + + posix_time = (command->flags & CMD_TIME_POSIX); + + old_flags = command->flags; + command->flags &= ~(CMD_TIME_PIPELINE|CMD_TIME_POSIX); + rv = execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close); + command->flags = old_flags; + + rs = us = ss = 0; + rsf = usf = ssf = cpu = 0; + +#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) +# if defined (HAVE_STRUCT_TIMEZONE) + gettimeofday (&after, &dtz); +# else + gettimeofday (&after, (void *)NULL); +# endif /* !HAVE_STRUCT_TIMEZONE */ + getrusage (RUSAGE_SELF, &selfa); + getrusage (RUSAGE_CHILDREN, &kidsa); + + difftimeval (&real, &before, &after); + timeval_to_secs (&real, &rs, &rsf); + + addtimeval (&user, difftimeval(&after, &selfb.ru_utime, &selfa.ru_utime), + difftimeval(&before, &kidsb.ru_utime, &kidsa.ru_utime)); + timeval_to_secs (&user, &us, &usf); + + addtimeval (&sys, difftimeval(&after, &selfb.ru_stime, &selfa.ru_stime), + difftimeval(&before, &kidsb.ru_stime, &kidsa.ru_stime)); + timeval_to_secs (&sys, &ss, &ssf); + + cpu = timeval_to_cpu (&real, &user, &sys); +#else +# if defined (HAVE_TIMES) + tafter = times (&after); + + real = tafter - tbefore; + clock_t_to_secs (real, &rs, &rsf); + + user = (after.tms_utime - before.tms_utime) + (after.tms_cutime - before.tms_cutime); + clock_t_to_secs (user, &us, &usf); + + sys = (after.tms_stime - before.tms_stime) + (after.tms_cstime - before.tms_cstime); + clock_t_to_secs (sys, &ss, &ssf); + + cpu = (real == 0) ? 0 : ((user + sys) * 10000) / real; + +# else + rs = us = ss = 0; + rsf = usf = ssf = cpu = 0; +# endif +#endif + + if (posix_time) + time_format = POSIX_TIMEFORMAT; + else if ((time_format = get_string_value ("TIMEFORMAT")) == 0) + time_format = BASH_TIMEFORMAT; + + if (time_format && *time_format) + print_formatted_time (stderr, time_format, rs, rsf, us, usf, ss, ssf, cpu); + + return rv; +} +#endif /* COMMAND_TIMING */ + +/* Execute a command that's supposed to be in a subshell. This must be + called after make_child and we must be running in the child process. + The caller will return or exit() immediately with the value this returns. */ +static int +execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int asynchronous; + int pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + int user_subshell, return_code, function_value, should_redir_stdin, invert; + int ois; + COMMAND *tcom; + + USE_VAR(user_subshell); + USE_VAR(invert); + USE_VAR(tcom); + USE_VAR(asynchronous); + + subshell_level++; + should_redir_stdin = (asynchronous && (command->flags & CMD_STDIN_REDIR) && + pipe_in == NO_PIPE && + stdin_redirects (command->redirects) == 0); + + invert = (command->flags & CMD_INVERT_RETURN) != 0; + user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0); + + command->flags &= ~(CMD_FORCE_SUBSHELL | CMD_WANT_SUBSHELL | CMD_INVERT_RETURN); + +itrace("execute_in_subshell: job_control = %d user_subshell = %d", job_control, user_subshell); + + /* If a command is asynchronous in a subshell (like ( foo ) & or + the special case of an asynchronous GROUP command where the + the subshell bit is turned on down in case cm_group: below), + turn off `asynchronous', so that two subshells aren't spawned. + + This seems semantically correct to me. For example, + ( foo ) & seems to say ``do the command `foo' in a subshell + environment, but don't wait for that subshell to finish'', + and "{ foo ; bar ; } &" seems to me to be like functions or + builtins in the background, which executed in a subshell + environment. I just don't see the need to fork two subshells. */ + + /* Don't fork again, we are already in a subshell. A `doubly + async' shell is not interactive, however. */ + if (asynchronous) + { +#if defined (JOB_CONTROL) + /* If a construct like ( exec xxx yyy ) & is given while job + control is active, we want to prevent exec from putting the + subshell back into the original process group, carefully + undoing all the work we just did in make_child. */ + original_pgrp = -1; +#endif /* JOB_CONTROL */ + ois = interactive_shell; + interactive_shell = 0; + /* This test is to prevent alias expansion by interactive shells that + run `(command) &' but to allow scripts that have enabled alias + expansion with `shopt -s expand_alias' to continue to expand + aliases. */ + if (ois != interactive_shell) + expand_aliases = 0; + asynchronous = 0; + } + + /* Subshells are neither login nor interactive. */ + login_shell = interactive = 0; + + subshell_environment = user_subshell ? SUBSHELL_PAREN : SUBSHELL_ASYNC; + + reset_terminating_signals (); /* in sig.c */ + /* Cancel traps, in trap.c. */ + restore_original_signals (); + if (asynchronous) + setup_async_signals (); + +#if defined (JOB_CONTROL) + set_sigchld_handler (); +#endif /* JOB_CONTROL */ + + set_sigint_handler (); + +#if defined (JOB_CONTROL) + /* Delete all traces that there were any jobs running. This is + only for subshells. */ + without_job_control (); +#endif /* JOB_CONTROL */ + + if (fds_to_close) + close_fd_bitmap (fds_to_close); + + do_piping (pipe_in, pipe_out); + + /* If this is a user subshell, set a flag if stdin was redirected. + This is used later to decide whether to redirect fd 0 to + /dev/null for async commands in the subshell. This adds more + sh compatibility, but I'm not sure it's the right thing to do. */ + if (user_subshell) + { + stdin_redir = stdin_redirects (command->redirects); + restore_default_signal (0); + } + + /* If this is an asynchronous command (command &), we want to + redirect the standard input from /dev/null in the absence of + any specific redirection involving stdin. */ + if (should_redir_stdin && stdin_redir == 0) + async_redirect_stdin (); + + /* Do redirections, then dispose of them before recursive call. */ + if (command->redirects) + { + if (do_redirections (command->redirects, RX_ACTIVE) != 0) + exit (invert ? EXECUTION_SUCCESS : EXECUTION_FAILURE); + + dispose_redirects (command->redirects); + command->redirects = (REDIRECT *)NULL; + } + + tcom = (command->type == cm_subshell) ? command->value.Subshell->command : command; + + /* Make sure the subshell inherits any CMD_IGNORE_RETURN flag. */ + if ((command->flags & CMD_IGNORE_RETURN) && tcom != command) + tcom->flags |= CMD_IGNORE_RETURN; + + /* If this is a simple command, tell execute_disk_command that it + might be able to get away without forking and simply exec. + This means things like ( sleep 10 ) will only cause one fork. + If we're timing the command or inverting its return value, however, + we cannot do this optimization. */ + if (user_subshell && (tcom->type == cm_simple || tcom->type == cm_subshell) && + ((tcom->flags & CMD_TIME_PIPELINE) == 0) && + ((tcom->flags & CMD_INVERT_RETURN) == 0)) + { + tcom->flags |= CMD_NO_FORK; + if (tcom->type == cm_simple) + tcom->value.Simple->flags |= CMD_NO_FORK; + } + + invert = (tcom->flags & CMD_INVERT_RETURN) != 0; + tcom->flags &= ~CMD_INVERT_RETURN; + + /* If we're inside a function while executing this subshell, we + need to handle a possible `return'. */ + function_value = 0; + if (return_catch_flag) + function_value = setjmp (return_catch); + + if (function_value) + return_code = return_catch_value; + else + return_code = execute_command_internal + (tcom, asynchronous, NO_PIPE, NO_PIPE, fds_to_close); + + /* If we are asked to, invert the return value. */ + if (invert) + return_code = (return_code == EXECUTION_SUCCESS) ? EXECUTION_FAILURE + : EXECUTION_SUCCESS; + + /* If we were explicitly placed in a subshell with (), we need + to do the `shell cleanup' things, such as running traps[0]. */ + if (user_subshell && signal_is_trapped (0)) + { + last_command_exit_value = return_code; + return_code = run_exit_trap (); + } + + subshell_level--; + return (return_code); + /* NOTREACHED */ +} + +static int +execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int asynchronous, pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + int prev, fildes[2], new_bitmap_size, dummyfd, ignore_return, exec_result; + COMMAND *cmd; + struct fd_bitmap *fd_bitmap; + +#if defined (JOB_CONTROL) + sigset_t set, oset; + BLOCK_CHILD (set, oset); +#endif /* JOB_CONTROL */ + + ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; + + prev = pipe_in; + cmd = command; + + while (cmd && cmd->type == cm_connection && + cmd->value.Connection && cmd->value.Connection->connector == '|') + { + /* Make a pipeline between the two commands. */ + if (pipe (fildes) < 0) + { + sys_error ("pipe error"); +#if defined (JOB_CONTROL) + terminate_current_pipeline (); + kill_current_pipeline (); +#endif /* JOB_CONTROL */ + last_command_exit_value = EXECUTION_FAILURE; + /* The unwind-protects installed below will take care + of closing all of the open file descriptors. */ + throw_to_top_level (); + return (EXECUTION_FAILURE); /* XXX */ + } + + /* Here is a problem: with the new file close-on-exec + code, the read end of the pipe (fildes[0]) stays open + in the first process, so that process will never get a + SIGPIPE. There is no way to signal the first process + that it should close fildes[0] after forking, so it + remains open. No SIGPIPE is ever sent because there + is still a file descriptor open for reading connected + to the pipe. We take care of that here. This passes + around a bitmap of file descriptors that must be + closed after making a child process in execute_simple_command. */ + + /* We need fd_bitmap to be at least as big as fildes[0]. + If fildes[0] is less than fds_to_close->size, then + use fds_to_close->size. */ + new_bitmap_size = (fildes[0] < fds_to_close->size) + ? fds_to_close->size + : fildes[0] + 8; + + fd_bitmap = new_fd_bitmap (new_bitmap_size); + + /* Now copy the old information into the new bitmap. */ + xbcopy ((char *)fds_to_close->bitmap, (char *)fd_bitmap->bitmap, fds_to_close->size); + + /* And mark the pipe file descriptors to be closed. */ + fd_bitmap->bitmap[fildes[0]] = 1; + + /* In case there are pipe or out-of-processes errors, we + want all these file descriptors to be closed when + unwind-protects are run, and the storage used for the + bitmaps freed up. */ + begin_unwind_frame ("pipe-file-descriptors"); + add_unwind_protect (dispose_fd_bitmap, fd_bitmap); + add_unwind_protect (close_fd_bitmap, fd_bitmap); + if (prev >= 0) + add_unwind_protect (close, prev); + dummyfd = fildes[1]; + add_unwind_protect (close, dummyfd); + +#if defined (JOB_CONTROL) + add_unwind_protect (restore_signal_mask, &oset); +#endif /* JOB_CONTROL */ + + if (ignore_return && cmd->value.Connection->first) + cmd->value.Connection->first->flags |= CMD_IGNORE_RETURN; + execute_command_internal (cmd->value.Connection->first, asynchronous, + prev, fildes[1], fd_bitmap); + + if (prev >= 0) + close (prev); + + prev = fildes[0]; + close (fildes[1]); + + dispose_fd_bitmap (fd_bitmap); + discard_unwind_frame ("pipe-file-descriptors"); + + cmd = cmd->value.Connection->second; + } + + /* Now execute the rightmost command in the pipeline. */ + if (ignore_return && cmd) + cmd->flags |= CMD_IGNORE_RETURN; + exec_result = execute_command_internal (cmd, asynchronous, prev, pipe_out, fds_to_close); + + if (prev >= 0) + close (prev); + +#if defined (JOB_CONTROL) + UNBLOCK_CHILD (oset); +#endif + + return (exec_result); +} + +static int +execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int asynchronous, pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + REDIRECT *rp; + COMMAND *tc, *second; + int ignore_return, exec_result; + + ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; + + switch (command->value.Connection->connector) + { + /* Do the first command asynchronously. */ + case '&': + tc = command->value.Connection->first; + if (tc == 0) + return (EXECUTION_SUCCESS); + + rp = tc->redirects; + + if (ignore_return) + tc->flags |= CMD_IGNORE_RETURN; + tc->flags |= CMD_AMPERSAND; + + /* If this shell was compiled without job control support, + if we are currently in a subshell via `( xxx )', or if job + control is not active then the standard input for an + asynchronous command is forced to /dev/null. */ +#if defined (JOB_CONTROL) + if ((subshell_environment || !job_control) && !stdin_redir) +#else + if (!stdin_redir) +#endif /* JOB_CONTROL */ + tc->flags |= CMD_STDIN_REDIR; + + exec_result = execute_command_internal (tc, 1, pipe_in, pipe_out, fds_to_close); + + if (tc->flags & CMD_STDIN_REDIR) + tc->flags &= ~CMD_STDIN_REDIR; + + second = command->value.Connection->second; + if (second) + { + if (ignore_return) + second->flags |= CMD_IGNORE_RETURN; + + exec_result = execute_command_internal (second, asynchronous, pipe_in, pipe_out, fds_to_close); + } + + break; + + /* Just call execute command on both sides. */ + case ';': + if (ignore_return) + { + if (command->value.Connection->first) + command->value.Connection->first->flags |= CMD_IGNORE_RETURN; + if (command->value.Connection->second) + command->value.Connection->second->flags |= CMD_IGNORE_RETURN; + } + QUIT; + execute_command (command->value.Connection->first); + QUIT; + exec_result = execute_command_internal (command->value.Connection->second, + asynchronous, pipe_in, pipe_out, + fds_to_close); + break; + + case '|': + exec_result = execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close); + break; + + case AND_AND: + case OR_OR: + if (asynchronous) + { + /* If we have something like `a && b &' or `a || b &', run the + && or || stuff in a subshell. Force a subshell and just call + execute_command_internal again. Leave asynchronous on + so that we get a report from the parent shell about the + background job. */ + command->flags |= CMD_FORCE_SUBSHELL; + exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); + break; + } + + /* Execute the first command. If the result of that is successful + and the connector is AND_AND, or the result is not successful + and the connector is OR_OR, then execute the second command, + otherwise return. */ + + if (command->value.Connection->first) + command->value.Connection->first->flags |= CMD_IGNORE_RETURN; + + exec_result = execute_command (command->value.Connection->first); + QUIT; + if (((command->value.Connection->connector == AND_AND) && + (exec_result == EXECUTION_SUCCESS)) || + ((command->value.Connection->connector == OR_OR) && + (exec_result != EXECUTION_SUCCESS))) + { + if (ignore_return && command->value.Connection->second) + command->value.Connection->second->flags |= CMD_IGNORE_RETURN; + + exec_result = execute_command (command->value.Connection->second); + } + break; + + default: + command_error ("execute_connection", CMDERR_BADCONN, command->value.Connection->connector, 0); + jump_to_top_level (DISCARD); + exec_result = EXECUTION_FAILURE; + } + + return exec_result; +} + +#define REAP() \ + do \ + { \ + if (!interactive_shell) \ + reap_dead_jobs (); \ + } \ + while (0) + +/* Execute a FOR command. The syntax is: FOR word_desc IN word_list; + DO command; DONE */ +static int +execute_for_command (for_command) + FOR_COM *for_command; +{ + register WORD_LIST *releaser, *list; + SHELL_VAR *v; + char *identifier; + int retval, save_line_number; +#if 0 + SHELL_VAR *old_value = (SHELL_VAR *)NULL; /* Remember the old value of x. */ +#endif + + save_line_number = line_number; + if (check_identifier (for_command->name, 1) == 0) + { + if (posixly_correct && interactive_shell == 0) + { + last_command_exit_value = EX_USAGE; + jump_to_top_level (ERREXIT); + } + return (EXECUTION_FAILURE); + } + + loop_level++; + identifier = for_command->name->word; + + list = releaser = expand_words_no_vars (for_command->map_list); + + begin_unwind_frame ("for"); + add_unwind_protect (dispose_words, releaser); + +#if 0 + if (lexical_scoping) + { + old_value = copy_variable (find_variable (identifier)); + if (old_value) + add_unwind_protect (dispose_variable, old_value); + } +#endif + + if (for_command->flags & CMD_IGNORE_RETURN) + for_command->action->flags |= CMD_IGNORE_RETURN; + + for (retval = EXECUTION_SUCCESS; list; list = list->next) + { + QUIT; + + line_number = for_command->line; + + /* Remember what this command looks like, for debugger. */ + command_string_index = 0; + print_for_command_head (for_command); + + if (echo_command_at_execute) + xtrace_print_for_command_head (for_command); + + /* Save this command unless it's a trap command. */ + if (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)) + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + retval = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + continue; +#endif + + this_command_name = (char *)NULL; + v = bind_variable (identifier, list->word->word); + if (readonly_p (v) || noassign_p (v)) + { + line_number = save_line_number; + if (readonly_p (v) && interactive_shell == 0 && posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; + jump_to_top_level (FORCE_EOF); + } + else + { + dispose_words (releaser); + discard_unwind_frame ("for"); + loop_level--; + return (EXECUTION_FAILURE); + } + } + retval = execute_command (for_command->action); + REAP (); + QUIT; + + if (breaking) + { + breaking--; + break; + } + + if (continuing) + { + continuing--; + if (continuing) + break; + } + } + + loop_level--; + line_number = save_line_number; + +#if 0 + if (lexical_scoping) + { + if (!old_value) + unbind_variable (identifier); + else + { + SHELL_VAR *new_value; + + new_value = bind_variable (identifier, value_cell(old_value)); + new_value->attributes = old_value->attributes; + dispose_variable (old_value); + } + } +#endif + + dispose_words (releaser); + discard_unwind_frame ("for"); + return (retval); +} + +#if defined (ARITH_FOR_COMMAND) +/* Execute an arithmetic for command. The syntax is + + for (( init ; step ; test )) + do + body + done + + The execution should be exactly equivalent to + + eval \(\( init \)\) + while eval \(\( test \)\) ; do + body; + eval \(\( step \)\) + done +*/ +static intmax_t +eval_arith_for_expr (l, okp) + WORD_LIST *l; + int *okp; +{ + WORD_LIST *new; + intmax_t expresult; + int r; + + new = expand_words_no_vars (l); + if (new) + { + if (echo_command_at_execute) + xtrace_print_arith_cmd (new); + this_command_name = "(("; /* )) for expression error messages */ + + command_string_index = 0; + print_arith_command (new); + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + + r = run_debug_trap (); + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ +#if defined (DEBUGGER) + if (debugging_mode == 0 || r == EXECUTION_SUCCESS) + expresult = evalexp (new->word->word, okp); + else + { + expresult = 0; + if (okp) + *okp = 1; + } +#else + expresult = evalexp (new->word->word, okp); +#endif + dispose_words (new); + } + else + { + expresult = 0; + if (okp) + *okp = 1; + } + return (expresult); +} + +static int +execute_arith_for_command (arith_for_command) + ARITH_FOR_COM *arith_for_command; +{ + intmax_t expresult; + int expok, body_status, arith_lineno, save_lineno; + + body_status = EXECUTION_SUCCESS; + loop_level++; + save_lineno = line_number; + + if (arith_for_command->flags & CMD_IGNORE_RETURN) + arith_for_command->action->flags |= CMD_IGNORE_RETURN; + + this_command_name = "(("; /* )) for expression error messages */ + + /* save the starting line number of the command so we can reset + line_number before executing each expression -- for $LINENO + and the DEBUG trap. */ + line_number = arith_lineno = arith_for_command->line; + if (variable_context && interactive_shell) + line_number -= function_line_number; + + /* Evaluate the initialization expression. */ + expresult = eval_arith_for_expr (arith_for_command->init, &expok); + if (expok == 0) + { + line_number = save_lineno; + return (EXECUTION_FAILURE); + } + + while (1) + { + /* Evaluate the test expression. */ + line_number = arith_lineno; + expresult = eval_arith_for_expr (arith_for_command->test, &expok); + line_number = save_lineno; + + if (expok == 0) + { + body_status = EXECUTION_FAILURE; + break; + } + REAP (); + if (expresult == 0) + break; + + /* Execute the body of the arithmetic for command. */ + QUIT; + body_status = execute_command (arith_for_command->action); + QUIT; + + /* Handle any `break' or `continue' commands executed by the body. */ + if (breaking) + { + breaking--; + break; + } + + if (continuing) + { + continuing--; + if (continuing) + break; + } + + /* Evaluate the step expression. */ + line_number = arith_lineno; + expresult = eval_arith_for_expr (arith_for_command->step, &expok); + line_number = save_lineno; + + if (expok == 0) + { + body_status = EXECUTION_FAILURE; + break; + } + } + + loop_level--; + line_number = save_lineno; + + return (body_status); +} +#endif + +#if defined (SELECT_COMMAND) +static int LINES, COLS, tabsize; + +#define RP_SPACE ") " +#define RP_SPACE_LEN 2 + +/* XXX - does not handle numbers > 1000000 at all. */ +#define NUMBER_LEN(s) \ +((s < 10) ? 1 \ + : ((s < 100) ? 2 \ + : ((s < 1000) ? 3 \ + : ((s < 10000) ? 4 \ + : ((s < 100000) ? 5 \ + : 6))))) + +static int +print_index_and_element (len, ind, list) + int len, ind; + WORD_LIST *list; +{ + register WORD_LIST *l; + register int i; + + if (list == 0) + return (0); + for (i = ind, l = list; l && --i; l = l->next) + ; + fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word); + return (STRLEN (l->word->word)); +} + +static void +indent (from, to) + int from, to; +{ + while (from < to) + { + if ((to / tabsize) > (from / tabsize)) + { + putc ('\t', stderr); + from += tabsize - from % tabsize; + } + else + { + putc (' ', stderr); + from++; + } + } +} + +static void +print_select_list (list, list_len, max_elem_len, indices_len) + WORD_LIST *list; + int list_len, max_elem_len, indices_len; +{ + int ind, row, elem_len, pos, cols, rows; + int first_column_indices_len, other_indices_len; + + if (list == 0) + { + putc ('\n', stderr); + return; + } + + cols = max_elem_len ? COLS / max_elem_len : 1; + if (cols == 0) + cols = 1; + rows = list_len ? list_len / cols + (list_len % cols != 0) : 1; + cols = list_len ? list_len / rows + (list_len % rows != 0) : 1; + + if (rows == 1) + { + rows = cols; + cols = 1; + } + + first_column_indices_len = NUMBER_LEN (rows); + other_indices_len = indices_len; + + for (row = 0; row < rows; row++) + { + ind = row; + pos = 0; + while (1) + { + indices_len = (pos == 0) ? first_column_indices_len : other_indices_len; + elem_len = print_index_and_element (indices_len, ind + 1, list); + elem_len += indices_len + RP_SPACE_LEN; + ind += rows; + if (ind >= list_len) + break; + indent (pos + elem_len, pos + max_elem_len); + pos += max_elem_len; + } + putc ('\n', stderr); + } +} + +/* Print the elements of LIST, one per line, preceded by an index from 1 to + LIST_LEN. Then display PROMPT and wait for the user to enter a number. + If the number is between 1 and LIST_LEN, return that selection. If EOF + is read, return a null string. If a blank line is entered, or an invalid + number is entered, the loop is executed again. */ +static char * +select_query (list, list_len, prompt, print_menu) + WORD_LIST *list; + int list_len; + char *prompt; + int print_menu; +{ + int max_elem_len, indices_len, len; + intmax_t reply; + WORD_LIST *l; + char *repl_string, *t; + + t = get_string_value ("LINES"); + LINES = (t && *t) ? atoi (t) : 24; + t = get_string_value ("COLUMNS"); + COLS = (t && *t) ? atoi (t) : 80; + +#if 0 + t = get_string_value ("TABSIZE"); + tabsize = (t && *t) ? atoi (t) : 8; + if (tabsize <= 0) + tabsize = 8; +#else + tabsize = 8; +#endif + + max_elem_len = 0; + for (l = list; l; l = l->next) + { + len = STRLEN (l->word->word); + if (len > max_elem_len) + max_elem_len = len; + } + indices_len = NUMBER_LEN (list_len); + max_elem_len += indices_len + RP_SPACE_LEN + 2; + + while (1) + { + if (print_menu) + print_select_list (list, list_len, max_elem_len, indices_len); + fprintf (stderr, "%s", prompt); + fflush (stderr); + QUIT; + + if (read_builtin ((WORD_LIST *)NULL) == EXECUTION_FAILURE) + { + putchar ('\n'); + return ((char *)NULL); + } + repl_string = get_string_value ("REPLY"); + if (*repl_string == 0) + { + print_menu = 1; + continue; + } + if (legal_number (repl_string, &reply) == 0) + return ""; + if (reply < 1 || reply > list_len) + return ""; + + for (l = list; l && --reply; l = l->next) + ; + return (l->word->word); + } +} + +/* Execute a SELECT command. The syntax is: + SELECT word IN list DO command_list DONE + Only `break' or `return' in command_list will terminate + the command. */ +static int +execute_select_command (select_command) + SELECT_COM *select_command; +{ + WORD_LIST *releaser, *list; + SHELL_VAR *v; + char *identifier, *ps3_prompt, *selection; + int retval, list_len, show_menu, save_line_number; + + if (check_identifier (select_command->name, 1) == 0) + return (EXECUTION_FAILURE); + + save_line_number = line_number; + line_number = select_command->line; + + command_string_index = 0; + print_select_command_head (select_command); + + if (echo_command_at_execute) + xtrace_print_select_command_head (select_command); + + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + + retval = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + return (EXECUTION_SUCCESS); +#endif + + loop_level++; + identifier = select_command->name->word; + + /* command and arithmetic substitution, parameter and variable expansion, + word splitting, pathname expansion, and quote removal. */ + list = releaser = expand_words_no_vars (select_command->map_list); + list_len = list_length (list); + if (list == 0 || list_len == 0) + { + if (list) + dispose_words (list); + line_number = save_line_number; + return (EXECUTION_SUCCESS); + } + + begin_unwind_frame ("select"); + add_unwind_protect (dispose_words, releaser); + + if (select_command->flags & CMD_IGNORE_RETURN) + select_command->action->flags |= CMD_IGNORE_RETURN; + + retval = EXECUTION_SUCCESS; + show_menu = 1; + + while (1) + { + line_number = select_command->line; + ps3_prompt = get_string_value ("PS3"); + if (ps3_prompt == 0) + ps3_prompt = "#? "; + + QUIT; + selection = select_query (list, list_len, ps3_prompt, show_menu); + QUIT; + if (selection == 0) + { + /* select_query returns EXECUTION_FAILURE if the read builtin + fails, so we want to return failure in this case. */ + retval = EXECUTION_FAILURE; + break; + } + + v = bind_variable (identifier, selection); + if (readonly_p (v) || noassign_p (v)) + { + if (readonly_p (v) && interactive_shell == 0 && posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; + jump_to_top_level (FORCE_EOF); + } + else + { + dispose_words (releaser); + discard_unwind_frame ("select"); + loop_level--; + line_number = save_line_number; + return (EXECUTION_FAILURE); + } + } + + retval = execute_command (select_command->action); + + REAP (); + QUIT; + + if (breaking) + { + breaking--; + break; + } + + if (continuing) + { + continuing--; + if (continuing) + break; + } + +#if defined (KSH_COMPATIBLE_SELECT) + show_menu = 0; + selection = get_string_value ("REPLY"); + if (selection && *selection == '\0') + show_menu = 1; +#endif + } + + loop_level--; + line_number = save_line_number; + + dispose_words (releaser); + discard_unwind_frame ("select"); + return (retval); +} +#endif /* SELECT_COMMAND */ + +/* Execute a CASE command. The syntax is: CASE word_desc IN pattern_list ESAC. + The pattern_list is a linked list of pattern clauses; each clause contains + some patterns to compare word_desc against, and an associated command to + execute. */ +static int +execute_case_command (case_command) + CASE_COM *case_command; +{ + register WORD_LIST *list; + WORD_LIST *wlist, *es; + PATTERN_LIST *clauses; + char *word, *pattern; + int retval, match, ignore_return, save_line_number; + + save_line_number = line_number; + line_number = case_command->line; + + command_string_index = 0; + print_case_command_head (case_command); + + if (echo_command_at_execute) + xtrace_print_case_command_head (case_command); + + if (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)) + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + retval = run_debug_trap(); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + { + line_number = save_line_number; + return (EXECUTION_SUCCESS); + } +#endif + + /* Posix.2 specifies that the WORD is tilde expanded. */ + if (member ('~', case_command->word->word)) + { + word = bash_tilde_expand (case_command->word->word, 0); + free (case_command->word->word); + case_command->word->word = word; + } + + wlist = expand_word_unsplit (case_command->word, 0); + word = wlist ? string_list (wlist) : savestring (""); + dispose_words (wlist); + + retval = EXECUTION_SUCCESS; + ignore_return = case_command->flags & CMD_IGNORE_RETURN; + + begin_unwind_frame ("case"); + add_unwind_protect ((Function *)xfree, word); + +#define EXIT_CASE() goto exit_case_command + + for (clauses = case_command->clauses; clauses; clauses = clauses->next) + { + QUIT; + for (list = clauses->patterns; list; list = list->next) + { + /* Posix.2 specifies to tilde expand each member of the pattern + list. */ + if (member ('~', list->word->word)) + { + pattern = bash_tilde_expand (list->word->word, 0); + free (list->word->word); + list->word->word = pattern; + } + + es = expand_word_leave_quoted (list->word, 0); + + if (es && es->word && es->word->word && *(es->word->word)) + pattern = quote_string_for_globbing (es->word->word, QGLOB_CVTNULL); + else + { + pattern = (char *)xmalloc (1); + pattern[0] = '\0'; + } + + /* Since the pattern does not undergo quote removal (as per + Posix.2, section 3.9.4.3), the strmatch () call must be able + to recognize backslashes as escape characters. */ + match = strmatch (pattern, word, FNMATCH_EXTFLAG) != FNM_NOMATCH; + free (pattern); + + dispose_words (es); + + if (match) + { + if (clauses->action && ignore_return) + clauses->action->flags |= CMD_IGNORE_RETURN; + retval = execute_command (clauses->action); + EXIT_CASE (); + } + + QUIT; + } + } + +exit_case_command: + free (word); + discard_unwind_frame ("case"); + line_number = save_line_number; + return (retval); +} + +#define CMD_WHILE 0 +#define CMD_UNTIL 1 + +/* The WHILE command. Syntax: WHILE test DO action; DONE. + Repeatedly execute action while executing test produces + EXECUTION_SUCCESS. */ +static int +execute_while_command (while_command) + WHILE_COM *while_command; +{ + return (execute_while_or_until (while_command, CMD_WHILE)); +} + +/* UNTIL is just like WHILE except that the test result is negated. */ +static int +execute_until_command (while_command) + WHILE_COM *while_command; +{ + return (execute_while_or_until (while_command, CMD_UNTIL)); +} + +/* The body for both while and until. The only difference between the + two is that the test value is treated differently. TYPE is + CMD_WHILE or CMD_UNTIL. The return value for both commands should + be EXECUTION_SUCCESS if no commands in the body are executed, and + the status of the last command executed in the body otherwise. */ +static int +execute_while_or_until (while_command, type) + WHILE_COM *while_command; + int type; +{ + int return_value, body_status; + + body_status = EXECUTION_SUCCESS; + loop_level++; + + while_command->test->flags |= CMD_IGNORE_RETURN; + if (while_command->flags & CMD_IGNORE_RETURN) + while_command->action->flags |= CMD_IGNORE_RETURN; + + while (1) + { + return_value = execute_command (while_command->test); + REAP (); + + /* Need to handle `break' in the test when we would break out of the + loop. The job control code will set `breaking' to loop_level + when a job in a loop is stopped with SIGTSTP. If the stopped job + is in the loop test, `breaking' will not be reset unless we do + this, and the shell will cease to execute commands. */ + if (type == CMD_WHILE && return_value != EXECUTION_SUCCESS) + { + if (breaking) + breaking--; + break; + } + if (type == CMD_UNTIL && return_value == EXECUTION_SUCCESS) + { + if (breaking) + breaking--; + break; + } + + QUIT; + body_status = execute_command (while_command->action); + QUIT; + + if (breaking) + { + breaking--; + break; + } + + if (continuing) + { + continuing--; + if (continuing) + break; + } + } + loop_level--; + + return (body_status); +} + +/* IF test THEN command [ELSE command]. + IF also allows ELIF in the place of ELSE IF, but + the parser makes *that* stupidity transparent. */ +static int +execute_if_command (if_command) + IF_COM *if_command; +{ + int return_value, save_line_number; + + save_line_number = line_number; + if_command->test->flags |= CMD_IGNORE_RETURN; + return_value = execute_command (if_command->test); + line_number = save_line_number; + + if (return_value == EXECUTION_SUCCESS) + { + QUIT; + + if (if_command->true_case && (if_command->flags & CMD_IGNORE_RETURN)) + if_command->true_case->flags |= CMD_IGNORE_RETURN; + + return (execute_command (if_command->true_case)); + } + else + { + QUIT; + + if (if_command->false_case && (if_command->flags & CMD_IGNORE_RETURN)) + if_command->false_case->flags |= CMD_IGNORE_RETURN; + + return (execute_command (if_command->false_case)); + } +} + +#if defined (DPAREN_ARITHMETIC) +static int +execute_arith_command (arith_command) + ARITH_COM *arith_command; +{ + int expok, save_line_number, retval; + intmax_t expresult; + WORD_LIST *new; + + expresult = 0; + + save_line_number = line_number; + this_command_name = "(("; /* )) */ + line_number = arith_command->line; + /* If we're in a function, update the line number information. */ + if (variable_context && interactive_shell) + line_number -= function_line_number; + + command_string_index = 0; + print_arith_command (arith_command->exp); + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + + /* Run the debug trap before each arithmetic command, but do it after we + update the line number information and before we expand the various + words in the expression. */ + retval = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + { + line_number = save_line_number; + return (EXECUTION_SUCCESS); + } +#endif + + new = expand_words_no_vars (arith_command->exp); + + /* If we're tracing, make a new word list with `((' at the front and `))' + at the back and print it. */ + if (echo_command_at_execute) + xtrace_print_arith_cmd (new); + + if (new) + { + expresult = evalexp (new->word->word, &expok); + line_number = save_line_number; + dispose_words (new); + } + else + { + expresult = 0; + expok = 1; + } + + if (expok == 0) + return (EXECUTION_FAILURE); + + return (expresult == 0 ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} +#endif /* DPAREN_ARITHMETIC */ + +#if defined (COND_COMMAND) + +static char *nullstr = ""; + +static int +execute_cond_node (cond) + COND_COM *cond; +{ + int result, invert, patmatch, rmatch, mflags; + char *arg1, *arg2; + + invert = (cond->flags & CMD_INVERT_RETURN); + + if (cond->type == COND_EXPR) + result = execute_cond_node (cond->left); + else if (cond->type == COND_OR) + { + result = execute_cond_node (cond->left); + if (result != EXECUTION_SUCCESS) + result = execute_cond_node (cond->right); + } + else if (cond->type == COND_AND) + { + result = execute_cond_node (cond->left); + if (result == EXECUTION_SUCCESS) + result = execute_cond_node (cond->right); + } + else if (cond->type == COND_UNARY) + { + arg1 = cond_expand_word (cond->left->op, 0); + if (arg1 == 0) + arg1 = nullstr; + if (echo_command_at_execute) + xtrace_print_cond_term (cond->type, invert, cond->op, arg1, (char *)NULL); + result = unary_test (cond->op->word, arg1) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; + if (arg1 != nullstr) + free (arg1); + } + else if (cond->type == COND_BINARY) + { + patmatch = ((cond->op->word[1] == '=') && (cond->op->word[2] == '\0') && + (cond->op->word[0] == '!' || cond->op->word[0] == '=') || + (cond->op->word[0] == '=' && cond->op->word[1] == '\0')); +#if defined (COND_REGEXP) + rmatch = (cond->op->word[0] == '=' && cond->op->word[1] == '~' && + cond->op->word[2] == '\0'); +#endif + + arg1 = cond_expand_word (cond->left->op, 0); + if (arg1 == 0) + arg1 = nullstr; + arg2 = cond_expand_word (cond->right->op, patmatch); + if (arg2 == 0) + arg2 = nullstr; + + if (echo_command_at_execute) + xtrace_print_cond_term (cond->type, invert, cond->op, arg1, arg2); + +#if defined (COND_REGEXP) + if (rmatch) + { + mflags = SHMAT_PWARN; +#if defined (ARRAY_VARS) + mflags |= SHMAT_SUBEXP; +#endif + + result = sh_regmatch (arg1, arg2, mflags); + } + else +#endif /* COND_REGEXP */ + result = binary_test (cond->op->word, arg1, arg2, TEST_PATMATCH|TEST_ARITHEXP) + ? EXECUTION_SUCCESS + : EXECUTION_FAILURE; + if (arg1 != nullstr) + free (arg1); + if (arg2 != nullstr) + free (arg2); + } + else + { + command_error ("execute_cond_node", CMDERR_BADTYPE, cond->type, 0); + jump_to_top_level (DISCARD); + result = EXECUTION_FAILURE; + } + + if (invert) + result = (result == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; + + return result; +} + +static int +execute_cond_command (cond_command) + COND_COM *cond_command; +{ + int retval, save_line_number; + + retval = EXECUTION_SUCCESS; + save_line_number = line_number; + + this_command_name = "[["; + line_number = cond_command->line; + /* If we're in a function, update the line number information. */ + if (variable_context && interactive_shell) + line_number -= function_line_number; + + command_string_index = 0; + print_cond_command (cond_command); + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + + /* Run the debug trap before each conditional command, but do it after we + update the line number information. */ + retval = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && retval != EXECUTION_SUCCESS) + { + line_number = save_line_number; + return (EXECUTION_SUCCESS); + } +#endif + +#if 0 + debug_print_cond_command (cond_command); +#endif + + last_command_exit_value = retval = execute_cond_node (cond_command); + line_number = save_line_number; + return (retval); +} +#endif /* COND_COMMAND */ + +static void +bind_lastarg (arg) + char *arg; +{ + SHELL_VAR *var; + + if (arg == 0) + arg = ""; + var = bind_variable ("_", arg); + VUNSETATTR (var, att_exported); +} + +/* Execute a null command. Fork a subshell if the command uses pipes or is + to be run asynchronously. This handles all the side effects that are + supposed to take place. */ +static int +execute_null_command (redirects, pipe_in, pipe_out, async, old_last_command_subst_pid) + REDIRECT *redirects; + int pipe_in, pipe_out, async; + pid_t old_last_command_subst_pid; +{ + int r; + + if (pipe_in != NO_PIPE || pipe_out != NO_PIPE || async) + { + /* We have a null command, but we really want a subshell to take + care of it. Just fork, do piping and redirections, and exit. */ + if (make_child ((char *)NULL, async) == 0) + { + /* Cancel traps, in trap.c. */ + restore_original_signals (); /* XXX */ + + do_piping (pipe_in, pipe_out); + + subshell_environment = SUBSHELL_ASYNC; + + if (do_redirections (redirects, RX_ACTIVE) == 0) + exit (EXECUTION_SUCCESS); + else + exit (EXECUTION_FAILURE); + } + else + { + close_pipes (pipe_in, pipe_out); +#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) + unlink_fifo_list (); +#endif + return (EXECUTION_SUCCESS); + } + } + else + { + /* Even if there aren't any command names, pretend to do the + redirections that are specified. The user expects the side + effects to take place. If the redirections fail, then return + failure. Otherwise, if a command substitution took place while + expanding the command or a redirection, return the value of that + substitution. Otherwise, return EXECUTION_SUCCESS. */ + + r = do_redirections (redirects, RX_ACTIVE|RX_UNDOABLE); + cleanup_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + + if (r != 0) + return (EXECUTION_FAILURE); + else if (old_last_command_subst_pid != last_command_subst_pid) + return (last_command_exit_value); + else + return (EXECUTION_SUCCESS); + } +} + +/* This is a hack to suppress word splitting for assignment statements + given as arguments to builtins with the ASSIGNMENT_BUILTIN flag set. */ +static void +fix_assignment_words (words) + WORD_LIST *words; +{ + WORD_LIST *w; + struct builtin *b; + + if (words == 0) + return; + + b = 0; + + for (w = words; w; w = w->next) + if (w->word->flags & W_ASSIGNMENT) + { + if (b == 0) + { + b = builtin_address_internal (words->word->word, 0); + if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0) + return; + } + w->word->flags |= (W_NOSPLIT|W_NOGLOB|W_TILDEEXP); + } +} + +/* The meaty part of all the executions. We have to start hacking the + real execution of commands here. Fork a process, set things up, + execute the command. */ +static int +execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close) + SIMPLE_COM *simple_command; + int pipe_in, pipe_out, async; + struct fd_bitmap *fds_to_close; +{ + WORD_LIST *words, *lastword; + char *command_line, *lastarg, *temp; + int first_word_quoted, result, builtin_is_special, already_forked, dofork; + pid_t old_last_command_subst_pid, old_last_async_pid; + sh_builtin_func_t *builtin; + SHELL_VAR *func; + + result = EXECUTION_SUCCESS; + special_builtin_failed = builtin_is_special = 0; + command_line = (char *)0; + + /* If we're in a function, update the line number information. */ + if (variable_context && interactive_shell) + line_number -= function_line_number; + + /* Remember what this command line looks like at invocation. */ + command_string_index = 0; + print_simple_command (simple_command); + + if (this_command_name == 0 || (STREQ (this_command_name, "trap") == 0)) + { + FREE (the_printed_command_except_trap); + the_printed_command_except_trap = savestring (the_printed_command); + } + + /* Run the debug trap before each simple command, but do it after we + update the line number information. */ + result = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode && result != EXECUTION_SUCCESS) + return (EXECUTION_SUCCESS); +#endif + + first_word_quoted = + simple_command->words ? (simple_command->words->word->flags & W_QUOTED): 0; + + old_last_command_subst_pid = last_command_subst_pid; + old_last_async_pid = last_asynchronous_pid; + + already_forked = dofork = 0; + + /* If we're in a pipeline or run in the background, set DOFORK so we + make the child early, before word expansion. This keeps assignment + statements from affecting the parent shell's environment when they + should not. */ + dofork = pipe_in != NO_PIPE || pipe_out != NO_PIPE || async; + + /* Something like `%2 &' should restart job 2 in the background, not cause + the shell to fork here. */ + if (dofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE && + simple_command->words && simple_command->words->word && + simple_command->words->word->word && + (simple_command->words->word->word[0] == '%')) + dofork = 0; + + if (dofork) + { +#if 0 + /* XXX memory leak if expand_words() error causes a jump_to_top_level */ + command_line = savestring (the_printed_command); +#endif + + /* Do this now, because execute_disk_command will do it anyway in the + vast majority of cases. */ + maybe_make_export_env (); + +#if 0 + if (make_child (command_line, async) == 0) +#else + if (make_child (savestring (the_printed_command), async) == 0) +#endif + { + already_forked = 1; + simple_command->flags |= CMD_NO_FORK; + + subshell_environment = (pipe_in != NO_PIPE || pipe_out != NO_PIPE) + ? (SUBSHELL_PIPE|SUBSHELL_FORK) + : (SUBSHELL_ASYNC|SUBSHELL_FORK); + + /* We need to do this before piping to handle some really + pathological cases where one of the pipe file descriptors + is < 2. */ + if (fds_to_close) + close_fd_bitmap (fds_to_close); + + do_piping (pipe_in, pipe_out); + pipe_in = pipe_out = NO_PIPE; + + last_asynchronous_pid = old_last_async_pid; + } + else + { + close_pipes (pipe_in, pipe_out); +#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) + unlink_fifo_list (); +#endif + command_line = (char *)NULL; /* don't free this. */ + bind_lastarg ((char *)NULL); + return (result); + } + } + + /* If we are re-running this as the result of executing the `command' + builtin, do not expand the command words a second time. */ + if ((simple_command->flags & CMD_INHIBIT_EXPANSION) == 0) + { + current_fds_to_close = fds_to_close; + fix_assignment_words (simple_command->words); + words = expand_words (simple_command->words); + current_fds_to_close = (struct fd_bitmap *)NULL; + } + else + words = copy_word_list (simple_command->words); + + /* It is possible for WORDS not to have anything left in it. + Perhaps all the words consisted of `$foo', and there was + no variable `$foo'. */ + if (words == 0) + { + this_command_name = 0; + result = execute_null_command (simple_command->redirects, + pipe_in, pipe_out, + already_forked ? 0 : async, + old_last_command_subst_pid); + if (already_forked) + exit (result); + else + { + bind_lastarg ((char *)NULL); + set_pipestatus_from_exit (result); + return (result); + } + } + + lastarg = (char *)NULL; + + begin_unwind_frame ("simple-command"); + + if (echo_command_at_execute) + xtrace_print_word_list (words, 1); + + builtin = (sh_builtin_func_t *)NULL; + func = (SHELL_VAR *)NULL; + if ((simple_command->flags & CMD_NO_FUNCTIONS) == 0) + { + /* Posix.2 says special builtins are found before functions. We + don't set builtin_is_special anywhere other than here, because + this path is followed only when the `command' builtin is *not* + being used, and we don't want to exit the shell if a special + builtin executed with `command builtin' fails. `command' is not + a special builtin. */ + if (posixly_correct) + { + builtin = find_special_builtin (words->word->word); + if (builtin) + builtin_is_special = 1; + } + if (builtin == 0) + func = find_function (words->word->word); + } + + add_unwind_protect (dispose_words, words); + QUIT; + + /* Bind the last word in this command to "$_" after execution. */ + for (lastword = words; lastword->next; lastword = lastword->next) + ; + lastarg = lastword->word->word; + +#if defined (JOB_CONTROL) + /* Is this command a job control related thing? */ + if (words->word->word[0] == '%' && already_forked == 0) + { + this_command_name = async ? "bg" : "fg"; + last_shell_builtin = this_shell_builtin; + this_shell_builtin = builtin_address (this_command_name); + result = (*this_shell_builtin) (words); + goto return_result; + } + + /* One other possiblilty. The user may want to resume an existing job. + If they do, find out whether this word is a candidate for a running + job. */ + if (job_control && already_forked == 0 && async == 0 && + !first_word_quoted && + !words->next && + words->word->word[0] && + !simple_command->redirects && + pipe_in == NO_PIPE && + pipe_out == NO_PIPE && + (temp = get_string_value ("auto_resume"))) + { + int job, jflags, started_status; + + jflags = JM_STOPPED|JM_FIRSTMATCH; + if (STREQ (temp, "exact")) + jflags |= JM_EXACT; + else if (STREQ (temp, "substring")) + jflags |= JM_SUBSTRING; + else + jflags |= JM_PREFIX; + job = get_job_by_name (words->word->word, jflags); + if (job != NO_JOB) + { + run_unwind_frame ("simple-command"); + this_command_name = "fg"; + last_shell_builtin = this_shell_builtin; + this_shell_builtin = builtin_address ("fg"); + + started_status = start_job (job, 1); + return ((started_status < 0) ? EXECUTION_FAILURE : started_status); + } + } +#endif /* JOB_CONTROL */ + + /* Remember the name of this command globally. */ + this_command_name = words->word->word; + + QUIT; + + /* This command could be a shell builtin or a user-defined function. + We have already found special builtins by this time, so we do not + set builtin_is_special. If this is a function or builtin, and we + have pipes, then fork a subshell in here. Otherwise, just execute + the command directly. */ + if (func == 0 && builtin == 0) + builtin = find_shell_builtin (this_command_name); + + last_shell_builtin = this_shell_builtin; + this_shell_builtin = builtin; + + if (builtin || func) + { + if (already_forked) + { + /* reset_terminating_signals (); */ /* XXX */ + /* Cancel traps, in trap.c. */ + restore_original_signals (); + + if (async) + { + if ((simple_command->flags & CMD_STDIN_REDIR) && + pipe_in == NO_PIPE && + (stdin_redirects (simple_command->redirects) == 0)) + async_redirect_stdin (); + setup_async_signals (); + } + + subshell_level++; + execute_subshell_builtin_or_function + (words, simple_command->redirects, builtin, func, + pipe_in, pipe_out, async, fds_to_close, + simple_command->flags); + subshell_level--; + } + else + { + result = execute_builtin_or_function + (words, builtin, func, simple_command->redirects, fds_to_close, + simple_command->flags); + if (builtin) + { + if (result > EX_SHERRBASE) + { + result = builtin_status (result); + if (builtin_is_special) + special_builtin_failed = 1; + } + /* In POSIX mode, if there are assignment statements preceding + a special builtin, they persist after the builtin + completes. */ + if (posixly_correct && builtin_is_special && temporary_env) + merge_temporary_env (); + } + else /* function */ + { + if (result == EX_USAGE) + result = EX_BADUSAGE; + else if (result > EX_SHERRBASE) + result = EXECUTION_FAILURE; + } + + set_pipestatus_from_exit (result); + + goto return_result; + } + } + + if (command_line == 0) + command_line = savestring (the_printed_command); + + execute_disk_command (words, simple_command->redirects, command_line, + pipe_in, pipe_out, async, fds_to_close, + simple_command->flags); + + return_result: + bind_lastarg (lastarg); + FREE (command_line); + dispose_words (words); + discard_unwind_frame ("simple-command"); + this_command_name = (char *)NULL; /* points to freed memory now */ + return (result); +} + +/* Translate the special builtin exit statuses. We don't really need a + function for this; it's a placeholder for future work. */ +static int +builtin_status (result) + int result; +{ + int r; + + switch (result) + { + case EX_USAGE: + r = EX_BADUSAGE; + break; + case EX_REDIRFAIL: + case EX_BADSYNTAX: + case EX_BADASSIGN: + case EX_EXPFAIL: + r = EXECUTION_FAILURE; + break; + default: + r = EXECUTION_SUCCESS; + break; + } + return (r); +} + +static int +execute_builtin (builtin, words, flags, subshell) + sh_builtin_func_t *builtin; + WORD_LIST *words; + int flags, subshell; +{ + int old_e_flag, result, eval_unwind; + int isbltinenv; + + old_e_flag = exit_immediately_on_error; + /* The eval builtin calls parse_and_execute, which does not know about + the setting of flags, and always calls the execution functions with + flags that will exit the shell on an error if -e is set. If the + eval builtin is being called, and we're supposed to ignore the exit + value of the command, we turn the -e flag off ourselves, then + restore it when the command completes. */ + if (subshell == 0 && builtin == eval_builtin && (flags & CMD_IGNORE_RETURN)) + { + begin_unwind_frame ("eval_builtin"); + unwind_protect_int (exit_immediately_on_error); + exit_immediately_on_error = 0; + eval_unwind = 1; + } + else + eval_unwind = 0; + + /* The temporary environment for a builtin is supposed to apply to + all commands executed by that builtin. Currently, this is a + problem only with the `source' and `eval' builtins. */ + isbltinenv = (builtin == source_builtin || builtin == eval_builtin); + if (isbltinenv) + { + if (subshell == 0) + begin_unwind_frame ("builtin_env"); + + if (temporary_env) + { + push_scope (VC_BLTNENV, temporary_env); + if (subshell == 0) + add_unwind_protect (pop_scope, "1"); + temporary_env = (HASH_TABLE *)NULL; + } + } + + /* `return' does a longjmp() back to a saved environment in execute_function. + If a variable assignment list preceded the command, and the shell is + running in POSIX mode, we need to merge that into the shell_variables + table, since `return' is a POSIX special builtin. */ + if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env) + { + begin_unwind_frame ("return_temp_env"); + add_unwind_protect (merge_temporary_env, (char *)NULL); + } + + result = ((*builtin) (words->next)); + + /* This shouldn't happen, but in case `return' comes back instead of + longjmp'ing, we need to unwind. */ + if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env) + discard_unwind_frame ("return_temp_env"); + + if (subshell == 0 && isbltinenv) + run_unwind_frame ("builtin_env"); + + if (eval_unwind) + { + exit_immediately_on_error += old_e_flag; + discard_unwind_frame ("eval_builtin"); + } + + return (result); +} + +static int +execute_function (var, words, flags, fds_to_close, async, subshell) + SHELL_VAR *var; + WORD_LIST *words; + int flags; + struct fd_bitmap *fds_to_close; + int async, subshell; +{ + int return_val, result; + COMMAND *tc, *fc, *save_current; + char *debug_trap, *error_trap, *return_trap; +#if defined (ARRAY_VARS) + SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v; + ARRAY *funcname_a, *bash_source_a, *bash_lineno_a; +#endif + FUNCTION_DEF *shell_fn; + char *sfile, *t; + static int funcnest = 0; + + USE_VAR(fc); + +#if defined (ARRAY_VARS) + GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a); + GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a); + GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a); +#endif + + tc = (COMMAND *)copy_command (function_cell (var)); + if (tc && (flags & CMD_IGNORE_RETURN)) + tc->flags |= CMD_IGNORE_RETURN; + + if (subshell == 0) + { + begin_unwind_frame ("function_calling"); + push_context (var->name, subshell, temporary_env); + add_unwind_protect (pop_context, (char *)NULL); + unwind_protect_int (line_number); + unwind_protect_int (return_catch_flag); + unwind_protect_jmp_buf (return_catch); + add_unwind_protect (dispose_command, (char *)tc); + unwind_protect_pointer (this_shell_function); + unwind_protect_int (loop_level); + } + else + push_context (var->name, subshell, temporary_env); /* don't unwind-protect for subshells */ + + temporary_env = (HASH_TABLE *)NULL; + + this_shell_function = var; + make_funcname_visible (1); + + debug_trap = TRAP_STRING(DEBUG_TRAP); + error_trap = TRAP_STRING(ERROR_TRAP); + return_trap = TRAP_STRING(RETURN_TRAP); + + /* The order of the unwind protects for debug_trap, error_trap and + return_trap is important here! unwind-protect commands are run + in reverse order of registration. If this causes problems, take + out the xfree unwind-protect calls and live with the small memory leak. */ + + /* function_trace_mode != 0 means that all functions inherit the DEBUG trap. + if the function has the trace attribute set, it inherits the DEBUG trap */ + if (debug_trap && ((trace_p (var) == 0) && function_trace_mode == 0)) + { + if (subshell == 0) + { + debug_trap = savestring (debug_trap); + add_unwind_protect (xfree, debug_trap); + add_unwind_protect (set_debug_trap, debug_trap); + } + restore_default_signal (DEBUG_TRAP); + } + + /* error_trace_mode != 0 means that functions inherit the ERR trap. */ + if (error_trap && error_trace_mode == 0) + { + if (subshell == 0) + { + error_trap = savestring (error_trap); + add_unwind_protect (xfree, error_trap); + add_unwind_protect (set_error_trap, error_trap); + } + restore_default_signal (ERROR_TRAP); + } + + if (return_trap && ((trace_p (var) == 0) && function_trace_mode == 0)) + { + if (subshell == 0) + { + return_trap = savestring (return_trap); + add_unwind_protect (xfree, return_trap); + add_unwind_protect (set_return_trap, return_trap); + } + restore_default_signal (RETURN_TRAP); + } + + funcnest++; +#if defined (ARRAY_VARS) + /* This is quite similar to the code in shell.c and elsewhere. */ + shell_fn = find_function_def (this_shell_function->name); + sfile = shell_fn ? shell_fn->source_file : ""; + array_push (funcname_a, this_shell_function->name); + + array_push (bash_source_a, sfile); + t = itos (executing_line_number ()); + array_push (bash_lineno_a, t); + free (t); +#endif + + /* The temporary environment for a function is supposed to apply to + all commands executed within the function body. */ + + remember_args (words->next, 1); + + /* Update BASH_ARGV and BASH_ARGC */ + if (debugging_mode) + push_args (words->next); + + /* Number of the line on which the function body starts. */ + line_number = function_line_number = tc->line; + + if (subshell) + { +#if defined (JOB_CONTROL) + stop_pipeline (async, (COMMAND *)NULL); +#endif + fc = (tc->type == cm_group) ? tc->value.Group->command : tc; + + if (fc && (flags & CMD_IGNORE_RETURN)) + fc->flags |= CMD_IGNORE_RETURN; + } + else + fc = tc; + + return_catch_flag++; + return_val = setjmp (return_catch); + + if (return_val) + result = return_catch_value; + else + { + /* Run the debug trap here so we can trap at the start of a function's + execution rather than the execution of the body's first command. */ + showing_function_line = 1; + save_current = currently_executing_command; + result = run_debug_trap (); +#if defined (DEBUGGER) + /* In debugging mode, if the DEBUG trap returns a non-zero status, we + skip the command. */ + if (debugging_mode == 0 || result == EXECUTION_SUCCESS) + { + showing_function_line = 0; + currently_executing_command = save_current; + result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close); + + /* Run the RETURN trap in the function's context */ + save_current = currently_executing_command; + run_return_trap (); + currently_executing_command = save_current; + } +#else + result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close); +#endif + showing_function_line = 0; + } + + /* Restore BASH_ARGC and BASH_ARGV */ + if (debugging_mode) + pop_args (); + + if (subshell == 0) + run_unwind_frame ("function_calling"); + + funcnest--; +#if defined (ARRAY_VARS) + array_pop (bash_source_a); + array_pop (funcname_a); + array_pop (bash_lineno_a); +#endif + + if (variable_context == 0 || this_shell_function == 0) + make_funcname_visible (0); + + return (result); +} + +/* A convenience routine for use by other parts of the shell to execute + a particular shell function. */ +int +execute_shell_function (var, words) + SHELL_VAR *var; + WORD_LIST *words; +{ + int ret; + struct fd_bitmap *bitmap; + + bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE); + begin_unwind_frame ("execute-shell-function"); + add_unwind_protect (dispose_fd_bitmap, (char *)bitmap); + + ret = execute_function (var, words, 0, bitmap, 0, 0); + + dispose_fd_bitmap (bitmap); + discard_unwind_frame ("execute-shell-function"); + + return ret; +} + +/* Execute a shell builtin or function in a subshell environment. This + routine does not return; it only calls exit(). If BUILTIN is non-null, + it points to a function to call to execute a shell builtin; otherwise + VAR points at the body of a function to execute. WORDS is the arguments + to the command, REDIRECTS specifies redirections to perform before the + command is executed. */ +static void +execute_subshell_builtin_or_function (words, redirects, builtin, var, + pipe_in, pipe_out, async, fds_to_close, + flags) + WORD_LIST *words; + REDIRECT *redirects; + sh_builtin_func_t *builtin; + SHELL_VAR *var; + int pipe_in, pipe_out, async; + struct fd_bitmap *fds_to_close; + int flags; +{ + int result, r; +#if defined (JOB_CONTROL) + int jobs_hack; + + jobs_hack = (builtin == jobs_builtin) && + ((subshell_environment & SUBSHELL_ASYNC) == 0 || pipe_out != NO_PIPE); +#endif + + /* A subshell is neither a login shell nor interactive. */ + login_shell = interactive = 0; + + subshell_environment = SUBSHELL_ASYNC; + + maybe_make_export_env (); /* XXX - is this needed? */ + +#if defined (JOB_CONTROL) + /* Eradicate all traces of job control after we fork the subshell, so + all jobs begun by this subshell are in the same process group as + the shell itself. */ + + /* Allow the output of `jobs' to be piped. */ + if (jobs_hack) + kill_current_pipeline (); + else + without_job_control (); + + set_sigchld_handler (); +#endif /* JOB_CONTROL */ + + set_sigint_handler (); + + if (fds_to_close) + close_fd_bitmap (fds_to_close); + + do_piping (pipe_in, pipe_out); + + if (do_redirections (redirects, RX_ACTIVE) != 0) + exit (EXECUTION_FAILURE); + + if (builtin) + { + /* Give builtins a place to jump back to on failure, + so we don't go back up to main(). */ + result = setjmp (top_level); + + if (result == EXITPROG) + exit (last_command_exit_value); + else if (result) + exit (EXECUTION_FAILURE); + else + { + r = execute_builtin (builtin, words, flags, 1); + if (r == EX_USAGE) + r = EX_BADUSAGE; + exit (r); + } + } + else + exit (execute_function (var, words, flags, fds_to_close, async, 1)); +} + +/* Execute a builtin or function in the current shell context. If BUILTIN + is non-null, it is the builtin command to execute, otherwise VAR points + to the body of a function. WORDS are the command's arguments, REDIRECTS + are the redirections to perform. FDS_TO_CLOSE is the usual bitmap of + file descriptors to close. + + If BUILTIN is exec_builtin, the redirections specified in REDIRECTS are + not undone before this function returns. */ +static int +execute_builtin_or_function (words, builtin, var, redirects, + fds_to_close, flags) + WORD_LIST *words; + sh_builtin_func_t *builtin; + SHELL_VAR *var; + REDIRECT *redirects; + struct fd_bitmap *fds_to_close; + int flags; +{ + int result; + REDIRECT *saved_undo_list; + sh_builtin_func_t *saved_this_shell_builtin; + + if (do_redirections (redirects, RX_ACTIVE|RX_UNDOABLE) != 0) + { + cleanup_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + dispose_exec_redirects (); + return (EX_REDIRFAIL); /* was EXECUTION_FAILURE */ + } + + saved_this_shell_builtin = this_shell_builtin; + saved_undo_list = redirection_undo_list; + + /* Calling the "exec" builtin changes redirections forever. */ + if (builtin == exec_builtin) + { + dispose_redirects (saved_undo_list); + saved_undo_list = exec_redirection_undo_list; + exec_redirection_undo_list = (REDIRECT *)NULL; + } + else + dispose_exec_redirects (); + + if (saved_undo_list) + { + begin_unwind_frame ("saved redirects"); + add_unwind_protect (cleanup_redirects, (char *)saved_undo_list); + } + + redirection_undo_list = (REDIRECT *)NULL; + + if (builtin) + result = execute_builtin (builtin, words, flags, 0); + else + result = execute_function (var, words, flags, fds_to_close, 0, 0); + + /* We do this before undoing the effects of any redirections. */ + if (ferror (stdout)) + clearerr (stdout); + + /* If we are executing the `command' builtin, but this_shell_builtin is + set to `exec_builtin', we know that we have something like + `command exec [redirection]', since otherwise `exec' would have + overwritten the shell and we wouldn't get here. In this case, we + want to behave as if the `command' builtin had not been specified + and preserve the redirections. */ + if (builtin == command_builtin && this_shell_builtin == exec_builtin) + { + if (saved_undo_list) + dispose_redirects (saved_undo_list); + redirection_undo_list = exec_redirection_undo_list; + saved_undo_list = exec_redirection_undo_list = (REDIRECT *)NULL; + discard_unwind_frame ("saved_redirects"); + } + + if (saved_undo_list) + { + redirection_undo_list = saved_undo_list; + discard_unwind_frame ("saved redirects"); + } + + if (redirection_undo_list) + { + cleanup_redirects (redirection_undo_list); + redirection_undo_list = (REDIRECT *)NULL; + } + + return (result); +} + +void +setup_async_signals () +{ +#if defined (__BEOS__) + set_signal_handler (SIGHUP, SIG_IGN); /* they want csh-like behavior */ +#endif + +#if defined (JOB_CONTROL) + if (job_control == 0) +#endif + { + set_signal_handler (SIGINT, SIG_IGN); + set_signal_ignored (SIGINT); + set_signal_handler (SIGQUIT, SIG_IGN); + set_signal_ignored (SIGQUIT); + } +} + +/* Execute a simple command that is hopefully defined in a disk file + somewhere. + + 1) fork () + 2) connect pipes + 3) look up the command + 4) do redirections + 5) execve () + 6) If the execve failed, see if the file has executable mode set. + If so, and it isn't a directory, then execute its contents as + a shell script. + + Note that the filename hashing stuff has to take place up here, + in the parent. This is probably why the Bourne style shells + don't handle it, since that would require them to go through + this gnarly hair, for no good reason. + + NOTE: callers expect this to fork or exit(). */ +static void +execute_disk_command (words, redirects, command_line, pipe_in, pipe_out, + async, fds_to_close, cmdflags) + WORD_LIST *words; + REDIRECT *redirects; + char *command_line; + int pipe_in, pipe_out, async; + struct fd_bitmap *fds_to_close; + int cmdflags; +{ + char *pathname, *command, **args; + int nofork; + pid_t pid; + + nofork = (cmdflags & CMD_NO_FORK); /* Don't fork, just exec, if no pipes */ + pathname = words->word->word; + +#if defined (RESTRICTED_SHELL) + command = (char *)NULL; + if (restricted && xstrchr (pathname, '/')) + { + internal_error (_("%s: restricted: cannot specify `/' in command names"), + pathname); + last_command_exit_value = EXECUTION_FAILURE; + + /* If we're not going to fork below, we must already be in a child + process or a context in which it's safe to call exit(2). */ + if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE) + exit (last_command_exit_value); + else + goto parent_return; + } +#endif /* RESTRICTED_SHELL */ + + command = search_for_command (pathname); + + if (command) + { + maybe_make_export_env (); + put_command_name_into_env (command); + } + + /* We have to make the child before we check for the non-existence + of COMMAND, since we want the error messages to be redirected. */ + /* If we can get away without forking and there are no pipes to deal with, + don't bother to fork, just directly exec the command. */ + if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE) + pid = 0; + else + pid = make_child (savestring (command_line), async); + + if (pid == 0) + { + int old_interactive; + +#if 0 + /* This has been disabled for the time being. */ +#if !defined (ARG_MAX) || ARG_MAX >= 10240 + if (posixly_correct == 0) + put_gnu_argv_flags_into_env ((long)getpid (), glob_argv_flags); +#endif +#endif + + /* Cancel traps, in trap.c. */ + restore_original_signals (); + + /* restore_original_signals may have undone the work done + by make_child to ensure that SIGINT and SIGQUIT are ignored + in asynchronous children. */ + if (async) + { + if ((cmdflags & CMD_STDIN_REDIR) && + pipe_in == NO_PIPE && + (stdin_redirects (redirects) == 0)) + async_redirect_stdin (); + setup_async_signals (); + } + + /* This functionality is now provided by close-on-exec of the + file descriptors manipulated by redirection and piping. + Some file descriptors still need to be closed in all children + because of the way bash does pipes; fds_to_close is a + bitmap of all such file descriptors. */ + if (fds_to_close) + close_fd_bitmap (fds_to_close); + + do_piping (pipe_in, pipe_out); + + old_interactive = interactive; + if (async) + interactive = 0; + + subshell_environment = SUBSHELL_FORK; + + if (redirects && (do_redirections (redirects, RX_ACTIVE) != 0)) + { +#if defined (PROCESS_SUBSTITUTION) + /* Try to remove named pipes that may have been created as the + result of redirections. */ + unlink_fifo_list (); +#endif /* PROCESS_SUBSTITUTION */ + exit (EXECUTION_FAILURE); + } + + if (async) + interactive = old_interactive; + + if (command == 0) + { + internal_error (_("%s: command not found"), pathname); + exit (EX_NOTFOUND); /* Posix.2 says the exit status is 127 */ + } + + /* Execve expects the command name to be in args[0]. So we + leave it there, in the same format that the user used to + type it in. */ + args = strvec_from_word_list (words, 0, 0, (int *)NULL); + exit (shell_execve (command, args, export_env)); + } + else + { +parent_return: + /* Make sure that the pipes are closed in the parent. */ + close_pipes (pipe_in, pipe_out); +#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) + unlink_fifo_list (); +#endif + FREE (command); + } +} + +/* CPP defines to decide whether a particular index into the #! line + corresponds to a valid interpreter name or argument character, or + whitespace. The MSDOS define is to allow \r to be treated the same + as \n. */ + +#if !defined (MSDOS) +# define STRINGCHAR(ind) \ + (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n') +# define WHITECHAR(ind) \ + (ind < sample_len && whitespace (sample[ind])) +#else /* MSDOS */ +# define STRINGCHAR(ind) \ + (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n' && sample[ind] != '\r') +# define WHITECHAR(ind) \ + (ind < sample_len && whitespace (sample[ind])) +#endif /* MSDOS */ + +static char * +getinterp (sample, sample_len, endp) + char *sample; + int sample_len, *endp; +{ + register int i; + char *execname; + int start; + + /* Find the name of the interpreter to exec. */ + for (i = 2; i < sample_len && whitespace (sample[i]); i++) + ; + + for (start = i; STRINGCHAR(i); i++) + ; + + execname = substring (sample, start, i); + + if (endp) + *endp = i; + return execname; +} + +#if !defined (HAVE_HASH_BANG_EXEC) +/* If the operating system on which we're running does not handle + the #! executable format, then help out. SAMPLE is the text read + from the file, SAMPLE_LEN characters. COMMAND is the name of + the script; it and ARGS, the arguments given by the user, will + become arguments to the specified interpreter. ENV is the environment + to pass to the interpreter. + + The word immediately following the #! is the interpreter to execute. + A single argument to the interpreter is allowed. */ + +static int +execute_shell_script (sample, sample_len, command, args, env) + char *sample; + int sample_len; + char *command; + char **args, **env; +{ + char *execname, *firstarg; + int i, start, size_increment, larry; + + /* Find the name of the interpreter to exec. */ + execname = getinterp (sample, sample_len, &i); + size_increment = 1; + + /* Now the argument, if any. */ + for (firstarg = (char *)NULL, start = i; WHITECHAR(i); i++) + ; + + /* If there is more text on the line, then it is an argument for the + interpreter. */ + + if (STRINGCHAR(i)) + { + for (start = i; STRINGCHAR(i); i++) + ; + firstarg = substring ((char *)sample, start, i); + size_increment = 2; + } + + larry = strvec_len (args) + size_increment; + args = strvec_resize (args, larry + 1); + + for (i = larry - 1; i; i--) + args[i] = args[i - size_increment]; + + args[0] = execname; + if (firstarg) + { + args[1] = firstarg; + args[2] = command; + } + else + args[1] = command; + + args[larry] = (char *)NULL; + + return (shell_execve (execname, args, env)); +} +#undef STRINGCHAR +#undef WHITECHAR + +#endif /* !HAVE_HASH_BANG_EXEC */ + +static void +initialize_subshell () +{ +#if defined (ALIAS) + /* Forget about any aliases that we knew of. We are in a subshell. */ + delete_all_aliases (); +#endif /* ALIAS */ + +#if defined (HISTORY) + /* Forget about the history lines we have read. This is a non-interactive + subshell. */ + history_lines_this_session = 0; +#endif + +#if defined (JOB_CONTROL) + /* Forget about the way job control was working. We are in a subshell. */ + without_job_control (); + set_sigchld_handler (); +#endif /* JOB_CONTROL */ + + /* Reset the values of the shell flags and options. */ + reset_shell_flags (); + reset_shell_options (); + reset_shopt_options (); + + /* Zero out builtin_env, since this could be a shell script run from a + sourced file with a temporary environment supplied to the `source/.' + builtin. Such variables are not supposed to be exported (empirical + testing with sh and ksh). Just throw it away; don't worry about a + memory leak. */ + if (vc_isbltnenv (shell_variables)) + shell_variables = shell_variables->down; + + clear_unwind_protect_list (0); + + /* We're no longer inside a shell function. */ + variable_context = return_catch_flag = 0; + + /* If we're not interactive, close the file descriptor from which we're + reading the current shell script. */ + if (interactive_shell == 0) + unset_bash_input (0); +} + +#if defined (HAVE_SETOSTYPE) && defined (_POSIX_SOURCE) +# define SETOSTYPE(x) __setostype(x) +#else +# define SETOSTYPE(x) +#endif + +#define READ_SAMPLE_BUF(file, buf, len) \ + do \ + { \ + fd = open(file, O_RDONLY); \ + if (fd >= 0) \ + { \ + len = read (fd, buf, 80); \ + close (fd); \ + } \ + else \ + len = -1; \ + } \ + while (0) + +/* Call execve (), handling interpreting shell scripts, and handling + exec failures. */ +int +shell_execve (command, args, env) + char *command; + char **args, **env; +{ + struct stat finfo; + int larray, i, fd; + char sample[80]; + int sample_len; + + SETOSTYPE (0); /* Some systems use for USG/POSIX semantics */ + execve (command, args, env); + i = errno; /* error from execve() */ + SETOSTYPE (1); + + /* If we get to this point, then start checking out the file. + Maybe it is something we can hack ourselves. */ + if (i != ENOEXEC) + { + if ((stat (command, &finfo) == 0) && (S_ISDIR (finfo.st_mode))) + internal_error (_("%s: is a directory"), command); + else if (executable_file (command) == 0) + { + errno = i; + file_error (command); + } + else + { + /* The file has the execute bits set, but the kernel refuses to + run it for some reason. See why. */ +#if defined (HAVE_HASH_BANG_EXEC) + READ_SAMPLE_BUF (command, sample, sample_len); + if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') + { + char *interp; + + interp = getinterp (sample, sample_len, (int *)NULL); + errno = i; + sys_error (_("%s: %s: bad interpreter"), command, interp ? interp : ""); + FREE (interp); + return (EX_NOEXEC); + } +#endif + errno = i; + file_error (command); + } + return ((i == ENOENT) ? EX_NOTFOUND : EX_NOEXEC); /* XXX Posix.2 says that exit status is 126 */ + } + + /* This file is executable. + If it begins with #!, then help out people with losing operating + systems. Otherwise, check to see if it is a binary file by seeing + if the contents of the first line (or up to 80 characters) are in the + ASCII set. If it's a text file, execute the contents as shell commands, + otherwise return 126 (EX_BINARY_FILE). */ + READ_SAMPLE_BUF (command, sample, sample_len); + + if (sample_len == 0) + return (EXECUTION_SUCCESS); + + /* Is this supposed to be an executable script? + If so, the format of the line is "#! interpreter [argument]". + A single argument is allowed. The BSD kernel restricts + the length of the entire line to 32 characters (32 bytes + being the size of the BSD exec header), but we allow 80 + characters. */ + if (sample_len > 0) + { +#if !defined (HAVE_HASH_BANG_EXEC) + if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') + return (execute_shell_script (sample, sample_len, command, args, env)); + else +#endif + if (check_binary_file (sample, sample_len)) + { + internal_error (_("%s: cannot execute binary file"), command); + return (EX_BINARY_FILE); + } + } + + /* We have committed to attempting to execute the contents of this file + as shell commands. */ + + initialize_subshell (); + + set_sigint_handler (); + + /* Insert the name of this shell into the argument list. */ + larray = strvec_len (args) + 1; + args = strvec_resize (args, larray + 1); + + for (i = larray - 1; i; i--) + args[i] = args[i - 1]; + + args[0] = shell_name; + args[1] = command; + args[larray] = (char *)NULL; + + if (args[0][0] == '-') + args[0]++; + +#if defined (RESTRICTED_SHELL) + if (restricted) + change_flag ('r', FLAG_OFF); +#endif + + if (subshell_argv) + { + /* Can't free subshell_argv[0]; that is shell_name. */ + for (i = 1; i < subshell_argc; i++) + free (subshell_argv[i]); + free (subshell_argv); + } + + dispose_command (currently_executing_command); /* XXX */ + currently_executing_command = (COMMAND *)NULL; + + subshell_argc = larray; + subshell_argv = args; + subshell_envp = env; + + unbind_args (); /* remove the positional parameters */ + + longjmp (subshell_top_level, 1); + /*NOTREACHED*/ +} + +static int +execute_intern_function (name, function) + WORD_DESC *name; + COMMAND *function; +{ + SHELL_VAR *var; + + if (check_identifier (name, posixly_correct) == 0) + { + if (posixly_correct && interactive_shell == 0) + { + last_command_exit_value = EX_USAGE; + jump_to_top_level (ERREXIT); + } + return (EXECUTION_FAILURE); + } + + var = find_function (name->word); + if (var && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + internal_error (_("%s: readonly function"), var->name); + return (EXECUTION_FAILURE); + } + + bind_function (name->word, function); + return (EXECUTION_SUCCESS); +} + +#if defined (INCLUDE_UNUSED) +#if defined (PROCESS_SUBSTITUTION) +void +close_all_files () +{ + register int i, fd_table_size; + + fd_table_size = getdtablesize (); + if (fd_table_size > 256) /* clamp to a reasonable value */ + fd_table_size = 256; + + for (i = 3; i < fd_table_size; i++) + close (i); +} +#endif /* PROCESS_SUBSTITUTION */ +#endif + +static void +close_pipes (in, out) + int in, out; +{ + if (in >= 0) + close (in); + if (out >= 0) + close (out); +} + +static void +dup_error (oldd, newd) + int oldd, newd; +{ + sys_error (_("cannot duplicate fd %d to fd %d"), oldd, newd); +} + +/* Redirect input and output to be from and to the specified pipes. + NO_PIPE and REDIRECT_BOTH are handled correctly. */ +static void +do_piping (pipe_in, pipe_out) + int pipe_in, pipe_out; +{ + if (pipe_in != NO_PIPE) + { + if (dup2 (pipe_in, 0) < 0) + dup_error (pipe_in, 0); + if (pipe_in > 0) + close (pipe_in); + } + if (pipe_out != NO_PIPE) + { + if (pipe_out != REDIRECT_BOTH) + { + if (dup2 (pipe_out, 1) < 0) + dup_error (pipe_out, 1); + if (pipe_out == 0 || pipe_out > 1) + close (pipe_out); + } + else + { + if (dup2 (1, 2) < 0) + dup_error (1, 2); + } + } +} diff --git a/externs.h b/externs.h index 3799580..a015d78 100644 --- a/externs.h +++ b/externs.h @@ -52,6 +52,7 @@ extern void print_cond_command __P((COND_COM *)); /* set -x support */ extern char *indirection_level_string __P((void)); +extern void xtrace_print_assignment __P((char *, char *, int, int)); extern void xtrace_print_word_list __P((WORD_LIST *, int)); extern void xtrace_print_for_command_head __P((FOR_COM *)); #if defined (SELECT_COMMAND) diff --git a/externs.h~ b/externs.h~ new file mode 100644 index 0000000..a015d78 --- /dev/null +++ b/externs.h~ @@ -0,0 +1,375 @@ +/* externs.h -- extern function declarations which do not appear in their + own header file. */ + +/* Copyright (C) 1993-2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +/* Make sure that this is included *after* config.h! */ + +#if !defined (_EXTERNS_H_) +# define _EXTERNS_H_ + +#include "stdc.h" + +/* Functions from expr.c. */ +extern intmax_t evalexp __P((char *, int *)); + +/* Functions from print_cmd.c. */ +extern char *make_command_string __P((COMMAND *)); +extern char *named_function_string __P((char *, COMMAND *, int)); + +extern void print_command __P((COMMAND *)); +extern void print_simple_command __P((SIMPLE_COM *)); +extern void print_word_list __P((WORD_LIST *, char *)); + +/* debugger support */ +extern void print_for_command_head __P((FOR_COM *)); +#if defined (SELECT_COMMAND) +extern void print_select_command_head __P((SELECT_COM *)); +#endif +extern void print_case_command_head __P((CASE_COM *)); +#if defined (DPAREN_ARITHMETIC) +extern void print_arith_command __P((WORD_LIST *)); +#endif +#if defined (COND_COMMAND) +extern void print_cond_command __P((COND_COM *)); +#endif + +/* set -x support */ +extern char *indirection_level_string __P((void)); +extern void xtrace_print_assignment __P((char *, char *, int, int)); +extern void xtrace_print_word_list __P((WORD_LIST *, int)); +extern void xtrace_print_for_command_head __P((FOR_COM *)); +#if defined (SELECT_COMMAND) +extern void xtrace_print_select_command_head __P((SELECT_COM *)); +#endif +extern void xtrace_print_case_command_head __P((CASE_COM *)); +#if defined (DPAREN_ARITHMETIC) +extern void xtrace_print_arith_cmd __P((WORD_LIST *)); +#endif +#if defined (COND_COMMAND) +extern void xtrace_print_cond_term __P((int, int, WORD_DESC *, char *, char *)); +#endif + +/* Functions from shell.c. */ +extern void exit_shell __P((int)) __attribute__((__noreturn__)); +extern void sh_exit __P((int)) __attribute__((__noreturn__)); +extern void disable_priv_mode __P((void)); +extern void unbind_args __P((void)); + +#if defined (RESTRICTED_SHELL) +extern int shell_is_restricted __P((char *)); +extern int maybe_make_restricted __P((char *)); +#endif + +extern void unset_bash_input __P((int)); +extern void get_current_user_info __P((void)); + +/* Functions from eval.c. */ +extern int reader_loop __P((void)); +extern int parse_command __P((void)); +extern int read_command __P((void)); + +/* Functions from braces.c. */ +#if defined (BRACE_EXPANSION) +extern char **brace_expand __P((char *)); +#endif + +/* Miscellaneous functions from parse.y */ +extern int yyparse __P((void)); +extern int return_EOF __P((void)); +extern void reset_parser __P((void)); +extern WORD_LIST *parse_string_to_word_list __P((char *, int, const char *)); + +extern void free_pushed_string_input __P((void)); + +extern char *decode_prompt_string __P((char *)); + +extern int get_current_prompt_level __P((void)); +extern void set_current_prompt_level __P((int)); + +#if defined (HISTORY) +extern char *history_delimiting_chars __P((void)); +#endif + +/* Declarations for functions defined in locale.c */ +extern void set_default_locale __P((void)); +extern void set_default_locale_vars __P((void)); +extern int set_locale_var __P((char *, char *)); +extern int set_lang __P((char *, char *)); +extern char *get_locale_var __P((char *)); +extern char *localetrans __P((char *, int, int *)); +extern char *mk_msgstr __P((char *, int *)); +extern char *localeexpand __P((char *, int, int, int, int *)); + +/* Declarations for functions defined in list.c. */ +extern void list_walk __P((GENERIC_LIST *, sh_glist_func_t *)); +extern void wlist_walk __P((WORD_LIST *, sh_icpfunc_t *)); +extern GENERIC_LIST *list_reverse (); +extern int list_length (); +extern GENERIC_LIST *list_append (); +extern GENERIC_LIST *list_remove (); + +/* Declarations for functions defined in stringlib.c */ +extern int find_string_in_alist __P((char *, STRING_INT_ALIST *, int)); +extern char *find_token_in_alist __P((int, STRING_INT_ALIST *, int)); +extern int find_index_in_alist __P((char *, STRING_INT_ALIST *, int)); + +extern char *substring __P((char *, int, int)); +extern char *strsub __P((char *, char *, char *, int)); +extern char *strcreplace __P((char *, int, char *, int)); +extern void strip_leading __P((char *)); +extern void strip_trailing __P((char *, int, int)); +extern void xbcopy __P((char *, char *, int)); + +/* Functions from version.c. */ +extern char *shell_version_string __P((void)); +extern void show_shell_version __P((int)); + +/* Functions from the bash library, lib/sh/libsh.a. These should really + go into a separate include file. */ + +/* declarations for functions defined in lib/sh/clktck.c */ +extern long get_clk_tck __P((void)); + +/* declarations for functions defined in lib/sh/clock.c */ +extern void clock_t_to_secs (); +extern void print_clock_t (); + +/* Declarations for functions defined in lib/sh/fmtulong.c */ +#define FL_PREFIX 0x01 /* add 0x, 0X, or 0 prefix as appropriate */ +#define FL_ADDBASE 0x02 /* add base# prefix to converted value */ +#define FL_HEXUPPER 0x04 /* use uppercase when converting to hex */ +#define FL_UNSIGNED 0x08 /* don't add any sign */ + +extern char *fmtulong __P((unsigned long int, int, char *, size_t, int)); + +/* Declarations for functions defined in lib/sh/fmtulong.c */ +#if defined (HAVE_LONG_LONG) +extern char *fmtullong __P((unsigned long long int, int, char *, size_t, int)); +#endif + +/* Declarations for functions defined in lib/sh/fmtumax.c */ +extern char *fmtumax __P((uintmax_t, int, char *, size_t, int)); + +/* Declarations for functions defined in lib/sh/getcwd.c */ +#if !defined (HAVE_GETCWD) +extern char *getcwd __P((char *, size_t)); +#endif + +/* Declarations for functions defined in lib/sh/itos.c */ +extern char *inttostr __P((intmax_t, char *, size_t)); +extern char *itos __P((intmax_t)); +extern char *uinttostr __P((uintmax_t, char *, size_t)); +extern char *uitos __P((uintmax_t)); + +/* declarations for functions defined in lib/sh/makepath.c */ +#define MP_DOTILDE 0x01 +#define MP_DOCWD 0x02 +#define MP_RMDOT 0x04 + +extern char *sh_makepath __P((const char *, const char *, int)); + +/* declarations for functions defined in lib/sh/netconn.c */ +extern int isnetconn __P((int)); + +/* declarations for functions defined in lib/sh/netopen.c */ +extern int netopen __P((char *)); + +/* Declarations for functions defined in lib/sh/oslib.c */ + +#if !defined (HAVE_DUP2) || defined (DUP2_BROKEN) +extern int dup2 __P((int, int)); +#endif + +#if !defined (HAVE_GETDTABLESIZE) +extern int getdtablesize __P((void)); +#endif /* !HAVE_GETDTABLESIZE */ + +#if !defined (HAVE_GETHOSTNAME) +extern int gethostname __P((char *, int)); +#endif /* !HAVE_GETHOSTNAME */ + +extern int getmaxgroups __P((void)); +extern long getmaxchild __P((void)); + +/* declarations for functions defined in lib/sh/pathcanon.c */ +#define PATH_CHECKDOTDOT 0x0001 +#define PATH_CHECKEXISTS 0x0002 +#define PATH_HARDPATH 0x0004 +#define PATH_NOALLOC 0x0008 + +extern char *sh_canonpath __P((char *, int)); + +/* declarations for functions defined in lib/sh/pathphys.c */ +extern char *sh_physpath __P((char *, int)); +extern char *sh_realpath __P((const char *, char *)); + +/* declarations for functions defined in lib/sh/setlinebuf.c */ +#ifdef NEED_SH_SETLINEBUF_DECL +extern int sh_setlinebuf __P((FILE *)); +#endif + +/* declarations for functions defined in lib/sh/shmatch.c */ +extern int sh_regmatch __P((const char *, const char *, int)); + +/* defines for flags argument to sh_regmatch. */ +#define SHMAT_SUBEXP 0x001 /* save subexpressions in SH_REMATCH */ +#define SHMAT_PWARN 0x002 /* print a warning message on invalid regexp */ + +/* declarations for functions defined in lib/sh/shquote.c */ +extern char *sh_single_quote __P((char *)); +extern char *sh_double_quote __P((char *)); +extern char *sh_un_double_quote __P((char *)); +extern char *sh_backslash_quote __P((char *)); +extern char *sh_backslash_quote_for_double_quotes __P((char *)); +extern int sh_contains_shell_metas __P((char *)); + +/* declarations for functions defined in lib/sh/spell.c */ +extern int spname __P((char *, char *)); + +/* declarations for functions defined in lib/sh/strcasecmp.c */ +#if !defined (HAVE_STRCASECMP) +extern int strncasecmp __P((const char *, const char *, int)); +extern int strcasecmp __P((const char *, const char *)); +#endif /* HAVE_STRCASECMP */ + +/* declarations for functions defined in lib/sh/strerror.c */ +#if !defined (strerror) +extern char *strerror __P((int)); +#endif + +/* declarations for functions defined in lib/sh/strftime.c */ +#if !defined (HAVE_STRFTIME) && defined (NEED_STRFTIME_DECL) +extern size_t strftime __P((char *, size_t, const char *, const struct tm *)); +#endif + +/* declarations for functions defined in lib/sh/strindex.c */ +extern char *strindex __P((const char *, const char *)); + +/* declarations for functions and structures defined in lib/sh/stringlist.c */ + +/* This is a general-purpose argv-style array struct. */ +typedef struct _list_of_strings { + char **list; + int list_size; + int list_len; +} STRINGLIST; + +typedef int sh_strlist_map_func_t __P((char *)); + +extern STRINGLIST *strlist_create __P((int)); +extern STRINGLIST *strlist_resize __P((STRINGLIST *, int)); +extern void strlist_flush __P((STRINGLIST *)); +extern void strlist_dispose __P((STRINGLIST *)); +extern int strlist_remove __P((STRINGLIST *, char *)); +extern STRINGLIST *strlist_copy __P((STRINGLIST *)); +extern STRINGLIST *strlist_merge __P((STRINGLIST *, STRINGLIST *)); +extern STRINGLIST *strlist_append __P((STRINGLIST *, STRINGLIST *)); +extern STRINGLIST *strlist_prefix_suffix __P((STRINGLIST *, char *, char *)); +extern void strlist_print __P((STRINGLIST *, char *)); +extern void strlist_walk __P((STRINGLIST *, sh_strlist_map_func_t *)); +extern void strlist_sort __P((STRINGLIST *)); + +/* declarations for functions defined in lib/sh/stringvec.c */ + +extern char **strvec_create __P((int)); +extern char **strvec_resize __P((char **, int)); +extern void strvec_flush __P((char **)); +extern void strvec_dispose __P((char **)); +extern int strvec_remove __P((char **, char *)); +extern int strvec_len __P((char **)); +extern int strvec_search __P((char **, char *)); +extern char **strvec_copy __P((char **)); +extern int strvec_strcmp __P((char **, char **)); +extern void strvec_sort __P((char **)); + +extern char **strvec_from_word_list __P((WORD_LIST *, int, int, int *)); +extern WORD_LIST *strvec_to_word_list __P((char **, int, int)); + +/* declarations for functions defined in lib/sh/strtod.c */ +#if !defined (HAVE_STRTOD) +extern double strtod __P((const char *, char **)); +#endif + +/* declarations for functions defined in lib/sh/strtol.c */ +#if !HAVE_DECL_STRTOL +extern long strtol __P((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strtoll.c */ +#if defined (HAVE_LONG_LONG) && !HAVE_DECL_STRTOLL +extern long long strtoll __P((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strtoul.c */ +#if !HAVE_DECL_STRTOUL +extern unsigned long strtoul __P((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strtoull.c */ +#if defined (HAVE_LONG_LONG) && !HAVE_DECL_STRTOULL +extern unsigned long long strtoull __P((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strimax.c */ +#if !HAVE_DECL_STRTOIMAX +extern intmax_t strtoimax __P((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strumax.c */ +#if !HAVE_DECL_STRTOUMAX +extern uintmax_t strtoumax __P((const char *, char **, int)); +#endif + +/* declarations for functions defined in lib/sh/strtrans.c */ +extern char *ansicstr __P((char *, int, int, int *, int *)); +extern char *ansic_quote __P((char *, int, int *)); +extern int ansic_shouldquote __P((const char *)); +extern char *ansiexpand __P((char *, int, int, int *)); + +/* declarations for functions defined in lib/sh/timeval.c. No prototypes + so we don't have to count on having a definition of struct timeval in + scope when this file is included. */ +extern void timeval_to_secs (); +extern void print_timeval (); + +/* declarations for functions defined in lib/sh/tmpfile.c */ +#define MT_USETMPDIR 0x0001 +#define MT_READWRITE 0x0002 +#define MT_USERANDOM 0x0004 + +extern char *sh_mktmpname __P((char *, int)); +extern int sh_mktmpfd __P((char *, int, char **)); +/* extern FILE *sh_mktmpfp __P((char *, int, char **)); */ + +/* declarations for functions defined in lib/sh/xstrchr.c */ +#undef xstrchr +extern char *xstrchr __P((const char *, int)); + +/* declarations for functions defined in lib/sh/zread.c */ +extern ssize_t zread __P((int, char *, size_t)); +extern ssize_t zreadintr __P((int, char *, size_t)); +extern ssize_t zreadc __P((int, char *)); +extern void zreset __P((void)); +extern void zsyncfd __P((int)); + +/* declarations for functions defined in lib/sh/zwrite.c */ +extern int zwrite __P((int, char *, size_t)); + +#endif /* _EXTERNS_H_ */ diff --git a/general.c b/general.c index 1435ac5..df4b113 100644 --- a/general.c +++ b/general.c @@ -238,6 +238,22 @@ check_identifier (word, check_word) return (1); } +/* Return 1 if STRING comprises a valid alias name. The shell accepts + essentially all characters except those which must be quoted to the + parser (which disqualifies them from alias expansion anyway) and `/'. */ +int +legal_alias_name (string, flags) + char *string; + int flags; +{ + register char *s; + + for (s = string; *s; s++) + if (shellbreak (*s) || shellxquote (*s) || shellexp (*s) || (*s == '/')) + return 0; + return 1; +} + /* Returns non-zero if STRING is an assignment statement. The returned value is the index of the `=' sign. */ int diff --git a/general.c~ b/general.c~ new file mode 100644 index 0000000..74c00f7 --- /dev/null +++ b/general.c~ @@ -0,0 +1,958 @@ +/* general.c -- Stuff that is used by all files. */ + +/* Copyright (C) 1987-2004 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include "bashtypes.h" +#ifndef _MINIX +# include +#endif +#include "posixstat.h" + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "filecntl.h" +#include "bashansi.h" +#include +#include "chartypes.h" +#include + +#include "bashintl.h" + +#include "shell.h" +#include + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +extern int expand_aliases; +extern int interrupt_immediately; +extern int interactive_comments; +extern int check_hashed_filenames; +extern int source_uses_path; +extern int source_searches_cwd; + +static char *bash_special_tilde_expansions __P((char *)); +static int unquoted_tilde_word __P((const char *)); +static void initialize_group_array __P((void)); + +/* A standard error message to use when getcwd() returns NULL. */ +char *bash_getcwd_errstr = N_("getcwd: cannot access parent directories"); + +/* Do whatever is necessary to initialize `Posix mode'. */ +void +posix_initialize (on) + int on; +{ + /* Things that should be turned on when posix mode is enabled. */ + if (on != 0) + { + interactive_comments = source_uses_path = expand_aliases = 1; + } + + /* Things that should be turned on when posix mode is disabled. */ + if (on == 0) + { + source_searches_cwd = 1; + expand_aliases = interactive_shell; + } +} + +/* **************************************************************** */ +/* */ +/* Functions to convert to and from and display non-standard types */ +/* */ +/* **************************************************************** */ + +#if defined (RLIMTYPE) +RLIMTYPE +string_to_rlimtype (s) + char *s; +{ + RLIMTYPE ret; + int neg; + + ret = 0; + neg = 0; + while (s && *s && whitespace (*s)) + s++; + if (*s == '-' || *s == '+') + { + neg = *s == '-'; + s++; + } + for ( ; s && *s && DIGIT (*s); s++) + ret = (ret * 10) + TODIGIT (*s); + return (neg ? -ret : ret); +} + +void +print_rlimtype (n, addnl) + RLIMTYPE n; + int addnl; +{ + char s[INT_STRLEN_BOUND (RLIMTYPE) + 1], *p; + + p = s + sizeof(s); + *--p = '\0'; + + if (n < 0) + { + do + *--p = '0' - n % 10; + while ((n /= 10) != 0); + + *--p = '-'; + } + else + { + do + *--p = '0' + n % 10; + while ((n /= 10) != 0); + } + + printf ("%s%s", p, addnl ? "\n" : ""); +} +#endif /* RLIMTYPE */ + +/* **************************************************************** */ +/* */ +/* Input Validation Functions */ +/* */ +/* **************************************************************** */ + +/* Return non-zero if all of the characters in STRING are digits. */ +int +all_digits (string) + char *string; +{ + register char *s; + + for (s = string; *s; s++) + if (DIGIT (*s) == 0) + return (0); + + return (1); +} + +/* Return non-zero if the characters pointed to by STRING constitute a + valid number. Stuff the converted number into RESULT if RESULT is + not null. */ +int +legal_number (string, result) + char *string; + intmax_t *result; +{ + intmax_t value; + char *ep; + + if (result) + *result = 0; + + errno = 0; + value = strtoimax (string, &ep, 10); + if (errno) + return 0; /* errno is set on overflow or underflow */ + + /* Skip any trailing whitespace, since strtoimax does not. */ + while (whitespace (*ep)) + ep++; + + /* If *string is not '\0' but *ep is '\0' on return, the entire string + is valid. */ + if (string && *string && *ep == '\0') + { + if (result) + *result = value; + /* The SunOS4 implementation of strtol() will happily ignore + overflow conditions, so this cannot do overflow correctly + on those systems. */ + return 1; + } + + return (0); +} + +/* Return 1 if this token is a legal shell `identifier'; that is, it consists + solely of letters, digits, and underscores, and does not begin with a + digit. */ +int +legal_identifier (name) + char *name; +{ + register char *s; + unsigned char c; + + if (!name || !(c = *name) || (legal_variable_starter (c) == 0)) + return (0); + + for (s = name + 1; (c = *s) != 0; s++) + { + if (legal_variable_char (c) == 0) + return (0); + } + return (1); +} + +/* Make sure that WORD is a valid shell identifier, i.e. + does not contain a dollar sign, nor is quoted in any way. Nor + does it consist of all digits. If CHECK_WORD is non-zero, + the word is checked to ensure that it consists of only letters, + digits, and underscores. */ +int +check_identifier (word, check_word) + WORD_DESC *word; + int check_word; +{ + if ((word->flags & (W_HASDOLLAR|W_QUOTED)) || all_digits (word->word)) + { + internal_error (_("`%s': not a valid identifier"), word->word); + return (0); + } + else if (check_word && legal_identifier (word->word) == 0) + { + internal_error (_("`%s': not a valid identifier"), word->word); + return (0); + } + else + return (1); +} + +/* Return 1 if STRING comprises a valid alias name. The shell accepts + essentially all characters except those which must be quoted to the + parser (which disqualifies them from alias expansion anyway) and `/'. */ +int +legal_alias_name (string, flags) + char *string; + int flags; +{ + register char *s; + + for (s = string; *s; s++) + if (shellbreak (*s) || shellquote (*s) || shellexp (*s) || (*s == '/')) + return 0; + return 1; +} + +/* Returns non-zero if STRING is an assignment statement. The returned value + is the index of the `=' sign. */ +int +assignment (string, flags) + const char *string; + int flags; +{ + register unsigned char c; + register int newi, indx; + + c = string[indx = 0]; + +#if defined (ARRAY_VARS) + if ((legal_variable_starter (c) == 0) && (flags && c != '[')) /* ] */ +#else + if (legal_variable_starter (c) == 0) +#endif + return (0); + + while (c = string[indx]) + { + /* The following is safe. Note that '=' at the start of a word + is not an assignment statement. */ + if (c == '=') + return (indx); + +#if defined (ARRAY_VARS) + if (c == '[') + { + newi = skipsubscript (string, indx); + if (string[newi++] != ']') + return (0); + return ((string[newi] == '=') ? newi : 0); + } +#endif /* ARRAY_VARS */ + + /* Variable names in assignment statements may contain only letters, + digits, and `_'. */ + if (legal_variable_char (c) == 0) + return (0); + + indx++; + } + return (0); +} + +/* **************************************************************** */ +/* */ +/* Functions to manage files and file descriptors */ +/* */ +/* **************************************************************** */ + +/* A function to unset no-delay mode on a file descriptor. Used in shell.c + to unset it on the fd passed as stdin. Should be called on stdin if + readline gets an EAGAIN or EWOULDBLOCK when trying to read input. */ + +#if !defined (O_NDELAY) +# if defined (FNDELAY) +# define O_NDELAY FNDELAY +# endif +#endif /* O_NDELAY */ + +/* Make sure no-delay mode is not set on file descriptor FD. */ +int +sh_unset_nodelay_mode (fd) + int fd; +{ + int flags, bflags; + + if ((flags = fcntl (fd, F_GETFL, 0)) < 0) + return -1; + + bflags = 0; + + /* This is defined to O_NDELAY in filecntl.h if O_NONBLOCK is not present + and O_NDELAY is defined. */ +#ifdef O_NONBLOCK + bflags |= O_NONBLOCK; +#endif + +#ifdef O_NDELAY + bflags |= O_NDELAY; +#endif + + if (flags & bflags) + { + flags &= ~bflags; + return (fcntl (fd, F_SETFL, flags)); + } + + return 0; +} + +/* Return 1 if file descriptor FD is valid; 0 otherwise. */ +int +sh_validfd (fd) + int fd; +{ + return (fcntl (fd, F_GETFD, 0) >= 0); +} + +/* There is a bug in the NeXT 2.1 rlogind that causes opens + of /dev/tty to fail. */ + +#if defined (__BEOS__) +/* On BeOS, opening in non-blocking mode exposes a bug in BeOS, so turn it + into a no-op. This should probably go away in the future. */ +# undef O_NONBLOCK +# define O_NONBLOCK 0 +#endif /* __BEOS__ */ + +void +check_dev_tty () +{ + int tty_fd; + char *tty; + + tty_fd = open ("/dev/tty", O_RDWR|O_NONBLOCK); + + if (tty_fd < 0) + { + tty = (char *)ttyname (fileno (stdin)); + if (tty == 0) + return; + tty_fd = open (tty, O_RDWR|O_NONBLOCK); + } + close (tty_fd); +} + +/* Return 1 if PATH1 and PATH2 are the same file. This is kind of + expensive. If non-NULL STP1 and STP2 point to stat structures + corresponding to PATH1 and PATH2, respectively. */ +int +same_file (path1, path2, stp1, stp2) + char *path1, *path2; + struct stat *stp1, *stp2; +{ + struct stat st1, st2; + + if (stp1 == NULL) + { + if (stat (path1, &st1) != 0) + return (0); + stp1 = &st1; + } + + if (stp2 == NULL) + { + if (stat (path2, &st2) != 0) + return (0); + stp2 = &st2; + } + + return ((stp1->st_dev == stp2->st_dev) && (stp1->st_ino == stp2->st_ino)); +} + +/* Move FD to a number close to the maximum number of file descriptors + allowed in the shell process, to avoid the user stepping on it with + redirection and causing us extra work. If CHECK_NEW is non-zero, + we check whether or not the file descriptors are in use before + duplicating FD onto them. MAXFD says where to start checking the + file descriptors. If it's less than 20, we get the maximum value + available from getdtablesize(2). */ +int +move_to_high_fd (fd, check_new, maxfd) + int fd, check_new, maxfd; +{ + int script_fd, nfds, ignore; + + if (maxfd < 20) + { + nfds = getdtablesize (); + if (nfds <= 0) + nfds = 20; + if (nfds > HIGH_FD_MAX) + nfds = HIGH_FD_MAX; /* reasonable maximum */ + } + else + nfds = maxfd; + + for (nfds--; check_new && nfds > 3; nfds--) + if (fcntl (nfds, F_GETFD, &ignore) == -1) + break; + + if (nfds > 3 && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1) + { + if (check_new == 0 || fd != fileno (stderr)) /* don't close stderr */ + close (fd); + return (script_fd); + } + + /* OK, we didn't find one less than our artificial maximum; return the + original file descriptor. */ + return (fd); +} + +/* Return non-zero if the characters from SAMPLE are not all valid + characters to be found in the first line of a shell script. We + check up to the first newline, or SAMPLE_LEN, whichever comes first. + All of the characters must be printable or whitespace. */ + +int +check_binary_file (sample, sample_len) + char *sample; + int sample_len; +{ + register int i; + unsigned char c; + + for (i = 0; i < sample_len; i++) + { + c = sample[i]; + if (c == '\n') + return (0); + + if (ISSPACE (c) == 0 && ISPRINT (c) == 0) + return (1); + } + + return (0); +} + +/* **************************************************************** */ +/* */ +/* Functions to inspect pathnames */ +/* */ +/* **************************************************************** */ + +int +file_isdir (fn) + char *fn; +{ + struct stat sb; + + return ((stat (fn, &sb) == 0) && S_ISDIR (sb.st_mode)); +} + +int +file_iswdir (fn) + char *fn; +{ + return (file_isdir (fn) && test_eaccess (fn, W_OK) == 0); +} + + +/* **************************************************************** */ +/* */ +/* Functions to manipulate pathnames */ +/* */ +/* **************************************************************** */ + +/* Turn STRING (a pathname) into an absolute pathname, assuming that + DOT_PATH contains the symbolic location of `.'. This always + returns a new string, even if STRING was an absolute pathname to + begin with. */ +char * +make_absolute (string, dot_path) + char *string, *dot_path; +{ + char *result; + + if (dot_path == 0 || ABSPATH(string)) +#ifdef __CYGWIN__ + { + char pathbuf[PATH_MAX + 1]; + + cygwin_conv_to_full_posix_path (string, pathbuf); + result = savestring (pathbuf); + } +#else + result = savestring (string); +#endif + else + result = sh_makepath (dot_path, string, 0); + + return (result); +} + +/* Return 1 if STRING contains an absolute pathname, else 0. Used by `cd' + to decide whether or not to look up a directory name in $CDPATH. */ +int +absolute_pathname (string) + const char *string; +{ + if (string == 0 || *string == '\0') + return (0); + + if (ABSPATH(string)) + return (1); + + if (string[0] == '.' && PATHSEP(string[1])) /* . and ./ */ + return (1); + + if (string[0] == '.' && string[1] == '.' && PATHSEP(string[2])) /* .. and ../ */ + return (1); + + return (0); +} + +/* Return 1 if STRING is an absolute program name; it is absolute if it + contains any slashes. This is used to decide whether or not to look + up through $PATH. */ +int +absolute_program (string) + const char *string; +{ + return ((char *)xstrchr (string, '/') != (char *)NULL); +} + +/* Return the `basename' of the pathname in STRING (the stuff after the + last '/'). If STRING is not a full pathname, simply return it. */ +char * +base_pathname (string) + char *string; +{ + char *p; + + if (absolute_pathname (string) == 0) + return (string); + + p = (char *)strrchr (string, '/'); + return (p ? ++p : string); +} + +/* Return the full pathname of FILE. Easy. Filenames that begin + with a '/' are returned as themselves. Other filenames have + the current working directory prepended. A new string is + returned in either case. */ +char * +full_pathname (file) + char *file; +{ + char *ret; + + file = (*file == '~') ? bash_tilde_expand (file, 0) : savestring (file); + + if (ABSPATH(file)) + return (file); + + ret = sh_makepath ((char *)NULL, file, (MP_DOCWD|MP_RMDOT)); + free (file); + + return (ret); +} + +/* A slightly related function. Get the prettiest name of this + directory possible. */ +static char tdir[PATH_MAX]; + +/* Return a pretty pathname. If the first part of the pathname is + the same as $HOME, then replace that with `~'. */ +char * +polite_directory_format (name) + char *name; +{ + char *home; + int l; + + home = get_string_value ("HOME"); + l = home ? strlen (home) : 0; + if (l > 1 && strncmp (home, name, l) == 0 && (!name[l] || name[l] == '/')) + { + strncpy (tdir + 1, name + l, sizeof(tdir) - 2); + tdir[0] = '~'; + tdir[sizeof(tdir) - 1] = '\0'; + return (tdir); + } + else + return (name); +} + +/* Given a string containing units of information separated by colons, + return the next one pointed to by (P_INDEX), or NULL if there are no more. + Advance (P_INDEX) to the character after the colon. */ +char * +extract_colon_unit (string, p_index) + char *string; + int *p_index; +{ + int i, start, len; + char *value; + + if (string == 0) + return (string); + + len = strlen (string); + if (*p_index >= len) + return ((char *)NULL); + + i = *p_index; + + /* Each call to this routine leaves the index pointing at a colon if + there is more to the path. If I is > 0, then increment past the + `:'. If I is 0, then the path has a leading colon. Trailing colons + are handled OK by the `else' part of the if statement; an empty + string is returned in that case. */ + if (i && string[i] == ':') + i++; + + for (start = i; string[i] && string[i] != ':'; i++) + ; + + *p_index = i; + + if (i == start) + { + if (string[i]) + (*p_index)++; + /* Return "" in the case of a trailing `:'. */ + value = (char *)xmalloc (1); + value[0] = '\0'; + } + else + value = substring (string, start, i); + + return (value); +} + +/* **************************************************************** */ +/* */ +/* Tilde Initialization and Expansion */ +/* */ +/* **************************************************************** */ + +#if defined (PUSHD_AND_POPD) +extern char *get_dirstack_from_string __P((char *)); +#endif + +static char **bash_tilde_prefixes; +static char **bash_tilde_suffixes; + +/* If tilde_expand hasn't been able to expand the text, perhaps it + is a special shell expansion. This function is installed as the + tilde_expansion_preexpansion_hook. It knows how to expand ~- and ~+. + If PUSHD_AND_POPD is defined, ~[+-]N expands to directories from the + directory stack. */ +static char * +bash_special_tilde_expansions (text) + char *text; +{ + char *result; + + result = (char *)NULL; + + if (text[0] == '+' && text[1] == '\0') + result = get_string_value ("PWD"); + else if (text[0] == '-' && text[1] == '\0') + result = get_string_value ("OLDPWD"); +#if defined (PUSHD_AND_POPD) + else if (DIGIT (*text) || ((*text == '+' || *text == '-') && DIGIT (text[1]))) + result = get_dirstack_from_string (text); +#endif + + return (result ? savestring (result) : (char *)NULL); +} + +/* Initialize the tilde expander. In Bash, we handle `~-' and `~+', as + well as handling special tilde prefixes; `:~" and `=~' are indications + that we should do tilde expansion. */ +void +tilde_initialize () +{ + static int times_called = 0; + + /* Tell the tilde expander that we want a crack first. */ + tilde_expansion_preexpansion_hook = bash_special_tilde_expansions; + + /* Tell the tilde expander about special strings which start a tilde + expansion, and the special strings that end one. Only do this once. + tilde_initialize () is called from within bashline_reinitialize (). */ + if (times_called++ == 0) + { + bash_tilde_prefixes = strvec_create (3); + bash_tilde_prefixes[0] = "=~"; + bash_tilde_prefixes[1] = ":~"; + bash_tilde_prefixes[2] = (char *)NULL; + + tilde_additional_prefixes = bash_tilde_prefixes; + + bash_tilde_suffixes = strvec_create (3); + bash_tilde_suffixes[0] = ":"; + bash_tilde_suffixes[1] = "=~"; /* XXX - ?? */ + bash_tilde_suffixes[2] = (char *)NULL; + + tilde_additional_suffixes = bash_tilde_suffixes; + } +} + +/* POSIX.2, 3.6.1: A tilde-prefix consists of an unquoted tilde character + at the beginning of the word, followed by all of the characters preceding + the first unquoted slash in the word, or all the characters in the word + if there is no slash...If none of the characters in the tilde-prefix are + quoted, the characters in the tilde-prefix following the tilde shell be + treated as a possible login name. */ + +#define TILDE_END(c) ((c) == '\0' || (c) == '/' || (c) == ':') + +static int +unquoted_tilde_word (s) + const char *s; +{ + const char *r; + + for (r = s; TILDE_END(*r) == 0; r++) + { + switch (*r) + { + case '\\': + case '\'': + case '"': + return 0; + } + } + return 1; +} + +/* Tilde-expand S by running it through the tilde expansion library. + ASSIGN_P is 1 if this is a variable assignment, so the alternate + tilde prefixes should be enabled (`=~' and `:~', see above). */ +char * +bash_tilde_expand (s, assign_p) + const char *s; + int assign_p; +{ + int old_immed, r; + char *ret; + + old_immed = interrupt_immediately; + interrupt_immediately = 1; + tilde_additional_prefixes = assign_p ? bash_tilde_prefixes : (char **)0; + r = (*s == '~') ? unquoted_tilde_word (s) : 1; + ret = r ? tilde_expand (s) : savestring (s); + interrupt_immediately = old_immed; + return (ret); +} + +/* **************************************************************** */ +/* */ +/* Functions to manipulate and search the group list */ +/* */ +/* **************************************************************** */ + +static int ngroups, maxgroups; + +/* The set of groups that this user is a member of. */ +static GETGROUPS_T *group_array = (GETGROUPS_T *)NULL; + +#if !defined (NOGROUP) +# define NOGROUP (gid_t) -1 +#endif + +static void +initialize_group_array () +{ + register int i; + + if (maxgroups == 0) + maxgroups = getmaxgroups (); + + ngroups = 0; + group_array = (GETGROUPS_T *)xrealloc (group_array, maxgroups * sizeof (GETGROUPS_T)); + +#if defined (HAVE_GETGROUPS) + ngroups = getgroups (maxgroups, group_array); +#endif + + /* If getgroups returns nothing, or the OS does not support getgroups(), + make sure the groups array includes at least the current gid. */ + if (ngroups == 0) + { + group_array[0] = current_user.gid; + ngroups = 1; + } + + /* If the primary group is not in the groups array, add it as group_array[0] + and shuffle everything else up 1, if there's room. */ + for (i = 0; i < ngroups; i++) + if (current_user.gid == (gid_t)group_array[i]) + break; + if (i == ngroups && ngroups < maxgroups) + { + for (i = ngroups; i > 0; i--) + group_array[i] = group_array[i - 1]; + group_array[0] = current_user.gid; + ngroups++; + } + + /* If the primary group is not group_array[0], swap group_array[0] and + whatever the current group is. The vast majority of systems should + not need this; a notable exception is Linux. */ + if (group_array[0] != current_user.gid) + { + for (i = 0; i < ngroups; i++) + if (group_array[i] == current_user.gid) + break; + if (i < ngroups) + { + group_array[i] = group_array[0]; + group_array[0] = current_user.gid; + } + } +} + +/* Return non-zero if GID is one that we have in our groups list. */ +int +#if defined (__STDC__) || defined ( _MINIX) +group_member (gid_t gid) +#else +group_member (gid) + gid_t gid; +#endif /* !__STDC__ && !_MINIX */ +{ +#if defined (HAVE_GETGROUPS) + register int i; +#endif + + /* Short-circuit if possible, maybe saving a call to getgroups(). */ + if (gid == current_user.gid || gid == current_user.egid) + return (1); + +#if defined (HAVE_GETGROUPS) + if (ngroups == 0) + initialize_group_array (); + + /* In case of error, the user loses. */ + if (ngroups <= 0) + return (0); + + /* Search through the list looking for GID. */ + for (i = 0; i < ngroups; i++) + if (gid == (gid_t)group_array[i]) + return (1); +#endif + + return (0); +} + +char ** +get_group_list (ngp) + int *ngp; +{ + static char **group_vector = (char **)NULL; + register int i; + + if (group_vector) + { + if (ngp) + *ngp = ngroups; + return group_vector; + } + + if (ngroups == 0) + initialize_group_array (); + + if (ngroups <= 0) + { + if (ngp) + *ngp = 0; + return (char **)NULL; + } + + group_vector = strvec_create (ngroups); + for (i = 0; i < ngroups; i++) + group_vector[i] = itos (group_array[i]); + + if (ngp) + *ngp = ngroups; + return group_vector; +} + +int * +get_group_array (ngp) + int *ngp; +{ + int i; + static int *group_iarray = (int *)NULL; + + if (group_iarray) + { + if (ngp) + *ngp = ngroups; + return (group_iarray); + } + + if (ngroups == 0) + initialize_group_array (); + + if (ngroups <= 0) + { + if (ngp) + *ngp = 0; + return (int *)NULL; + } + + group_iarray = (int *)xmalloc (ngroups * sizeof (int)); + for (i = 0; i < ngroups; i++) + group_iarray[i] = (int)group_array[i]; + + if (ngp) + *ngp = ngroups; + return group_iarray; +} diff --git a/general.h b/general.h index 9f39a48..afc7fb0 100644 --- a/general.h +++ b/general.h @@ -277,6 +277,7 @@ extern int all_digits __P((char *)); extern int legal_number __P((char *, intmax_t *)); extern int legal_identifier __P((char *)); extern int check_identifier __P((WORD_DESC *, int)); +extern int legal_alias_name __P((char *, int)); extern int assignment __P((const char *, int)); extern int sh_unset_nodelay_mode __P((int)); diff --git a/general.h~ b/general.h~ new file mode 100644 index 0000000..9f39a48 --- /dev/null +++ b/general.h~ @@ -0,0 +1,311 @@ +/* general.h -- defines that everybody likes to use. */ + +/* Copyright (C) 1993-2004 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#if !defined (_GENERAL_H_) +#define _GENERAL_H_ + +#include "stdc.h" + +#include "bashtypes.h" + +#if defined (HAVE_SYS_RESOURCE_H) && defined (RLIMTYPE) +# if defined (HAVE_SYS_TIME_H) +# include +# endif +# include +#endif + +#if defined (HAVE_STRING_H) +# include +#else +# include +#endif /* !HAVE_STRING_H */ + +#if defined (HAVE_LIMITS_H) +# include +#endif + +#include "xmalloc.h" + +/* NULL pointer type. */ +#if !defined (NULL) +# if defined (__STDC__) +# define NULL ((void *) 0) +# else +# define NULL 0x0 +# endif /* !__STDC__ */ +#endif /* !NULL */ + +/* Hardly used anymore */ +#define pointer_to_int(x) (int)((char *)x - (char *)0) + +#if defined (alpha) && defined (__GNUC__) && !defined (strchr) && !defined (__STDC__) +extern char *strchr (), *strrchr (); +#endif + +#if !defined (strcpy) && (defined (HAVE_DECL_STRCPY) && !HAVE_DECL_STRCPY) +extern char *strcpy __P((char *, const char *)); +#endif + +#if !defined (savestring) +# define savestring(x) (char *)strcpy (xmalloc (1 + strlen (x)), (x)) +#endif + +#ifndef member +# define member(c, s) ((c) ? ((char *)xstrchr ((s), (c)) != (char *)NULL) : 0) +#endif + +#ifndef whitespace +#define whitespace(c) (((c) == ' ') || ((c) == '\t')) +#endif + +#ifndef CHAR_MAX +# ifdef __CHAR_UNSIGNED__ +# define CHAR_MAX 0xff +# else +# define CHAR_MAX 0x7f +# endif +#endif + +#ifndef CHAR_BIT +# define CHAR_BIT 8 +#endif + +/* Nonzero if the integer type T is signed. */ +#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) + +/* Bound on length of the string representing an integer value of type T. + Subtract one for the sign bit if T is signed; + 302 / 1000 is log10 (2) rounded up; + add one for integer division truncation; + add one more for a minus sign if t is signed. */ +#define INT_STRLEN_BOUND(t) \ + ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 \ + + 1 + TYPE_SIGNED (t)) + + +/* Define exactly what a legal shell identifier consists of. */ +#define legal_variable_starter(c) (ISALPHA(c) || (c == '_')) +#define legal_variable_char(c) (ISALNUM(c) || c == '_') + +/* Definitions used in subst.c and by the `read' builtin for field + splitting. */ +#define spctabnl(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') + +/* All structs which contain a `next' field should have that field + as the first field in the struct. This means that functions + can be written to handle the general case for linked lists. */ +typedef struct g_list { + struct g_list *next; +} GENERIC_LIST; + +/* Here is a generic structure for associating character strings + with integers. It is used in the parser for shell tokenization. */ +typedef struct { + char *word; + int token; +} STRING_INT_ALIST; + +/* A macro to avoid making an uneccessary function call. */ +#define REVERSE_LIST(list, type) \ + ((list && list->next) ? (type)list_reverse ((GENERIC_LIST *)list) \ + : (type)(list)) + +#if __GNUC__ > 1 +# define FASTCOPY(s, d, n) __builtin_memcpy (d, s, n) +#else /* !__GNUC__ */ +# if !defined (HAVE_BCOPY) +# if !defined (HAVE_MEMMOVE) +# define FASTCOPY(s, d, n) memcpy (d, s, n) +# else +# define FASTCOPY(s, d, n) memmove (d, s, n) +# endif /* !HAVE_MEMMOVE */ +# else /* HAVE_BCOPY */ +# define FASTCOPY(s, d, n) bcopy (s, d, n) +# endif /* HAVE_BCOPY */ +#endif /* !__GNUC__ */ + +/* String comparisons that possibly save a function call each. */ +#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0) +#define STREQN(a, b, n) ((n == 0) ? (1) \ + : ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)) + +/* More convenience definitions that possibly save system or libc calls. */ +#define STRLEN(s) (((s) && (s)[0]) ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0) +#define FREE(s) do { if (s) free (s); } while (0) +#define MEMBER(c, s) (((c) && c == (s)[0] && !(s)[1]) || (member(c, s))) + +/* A fairly hairy macro to check whether an allocated string has more room, + and to resize it using xrealloc if it does not. + STR is the string (char *) + CIND is the current index into the string (int) + ROOM is the amount of additional room we need in the string (int) + CSIZE is the currently-allocated size of STR (int) + SINCR is how much to increment CSIZE before calling xrealloc (int) */ + +#define RESIZE_MALLOCED_BUFFER(str, cind, room, csize, sincr) \ + do { \ + if ((cind) + (room) >= csize) \ + { \ + while ((cind) + (room) >= csize) \ + csize += (sincr); \ + str = xrealloc (str, csize); \ + } \ + } while (0) + +/* Function pointers can be declared as (Function *)foo. */ +#if !defined (_FUNCTION_DEF) +# define _FUNCTION_DEF +typedef int Function (); +typedef void VFunction (); +typedef char *CPFunction (); /* no longer used */ +typedef char **CPPFunction (); /* no longer used */ +#endif /* _FUNCTION_DEF */ + +#ifndef SH_FUNCTION_TYPEDEF +# define SH_FUNCTION_TYPEDEF + +/* Shell function typedefs with prototypes */ +/* `Generic' function pointer typedefs */ + +typedef int sh_intfunc_t __P((int)); +typedef int sh_ivoidfunc_t __P((void)); +typedef int sh_icpfunc_t __P((char *)); +typedef int sh_icppfunc_t __P((char **)); +typedef int sh_iptrfunc_t __P((PTR_T)); + +typedef void sh_voidfunc_t __P((void)); +typedef void sh_vintfunc_t __P((int)); +typedef void sh_vcpfunc_t __P((char *)); +typedef void sh_vcppfunc_t __P((char **)); +typedef void sh_vptrfunc_t __P((PTR_T)); + +typedef int sh_wdesc_func_t __P((WORD_DESC *)); +typedef int sh_wlist_func_t __P((WORD_LIST *)); + +typedef int sh_glist_func_t __P((GENERIC_LIST *)); + +typedef char *sh_string_func_t __P((char *)); /* like savestring, et al. */ + +typedef int sh_msg_func_t __P((const char *, ...)); /* printf(3)-like */ +typedef void sh_vmsg_func_t __P((const char *, ...)); /* printf(3)-like */ + +/* Specific function pointer typedefs. Most of these could be done + with #defines. */ +typedef void sh_sv_func_t __P((char *)); /* sh_vcpfunc_t */ +typedef void sh_free_func_t __P((PTR_T)); /* sh_vptrfunc_t */ +typedef void sh_resetsig_func_t __P((int)); /* sh_vintfunc_t */ + +typedef int sh_ignore_func_t __P((const char *)); /* sh_icpfunc_t */ + +typedef int sh_assign_func_t __P((const char *)); /* sh_icpfunc_t */ + +typedef int sh_builtin_func_t __P((WORD_LIST *)); /* sh_wlist_func_t */ + +#endif /* SH_FUNCTION_TYPEDEF */ + +#define NOW ((time_t) time ((time_t *) 0)) + +/* Some defines for calling file status functions. */ +#define FS_EXISTS 0x1 +#define FS_EXECABLE 0x2 +#define FS_EXEC_PREFERRED 0x4 +#define FS_EXEC_ONLY 0x8 +#define FS_DIRECTORY 0x10 +#define FS_NODIRS 0x20 + +/* Default maximum for move_to_high_fd */ +#define HIGH_FD_MAX 256 + +/* The type of function passed as the fourth argument to qsort(3). */ +#ifdef __STDC__ +typedef int QSFUNC (const void *, const void *); +#else +typedef int QSFUNC (); +#endif + +/* Some useful definitions for Unix pathnames. Argument convention: + x == string, c == character */ + +#if !defined (__CYGWIN__) +# define ABSPATH(x) ((x)[0] == '/') +# define RELPATH(x) ((x)[0] != '/') +#else /* __CYGWIN__ */ +# define ABSPATH(x) (((x)[0] && ISALPHA((unsigned char)(x)[0]) && (x)[1] == ':' && (x)[2] == '/') || (x)[0] == '/') +# define RELPATH(x) (!(x)[0] || ((x)[1] != ':' && (x)[0] != '/')) +#endif /* __CYGWIN__ */ + +#define ROOTEDPATH(x) (ABSPATH(x)) + +#define DIRSEP '/' +#define ISDIRSEP(c) ((c) == '/') +#define PATHSEP(c) (ISDIRSEP(c) || (c) == 0) + +#if 0 +/* Declarations for functions defined in xmalloc.c */ +extern PTR_T xmalloc __P((size_t)); +extern PTR_T xrealloc __P((void *, size_t)); +extern void xfree __P((void *)); +#endif + +/* Declarations for functions defined in general.c */ +extern void posix_initialize __P((int)); + +#if defined (RLIMTYPE) +extern RLIMTYPE string_to_rlimtype __P((char *)); +extern void print_rlimtype __P((RLIMTYPE, int)); +#endif + +extern int all_digits __P((char *)); +extern int legal_number __P((char *, intmax_t *)); +extern int legal_identifier __P((char *)); +extern int check_identifier __P((WORD_DESC *, int)); +extern int assignment __P((const char *, int)); + +extern int sh_unset_nodelay_mode __P((int)); +extern int sh_validfd __P((int)); +extern void check_dev_tty __P((void)); +extern int move_to_high_fd __P((int, int, int)); +extern int check_binary_file __P((char *, int)); + +#ifdef _POSIXSTAT_H_ +extern int same_file __P((char *, char *, struct stat *, struct stat *)); +#endif + +extern int file_isdir __P((char *)); +extern int file_iswdir __P((char *)); + +extern char *make_absolute __P((char *, char *)); +extern int absolute_pathname __P((const char *)); +extern int absolute_program __P((const char *)); +extern char *base_pathname __P((char *)); +extern char *full_pathname __P((char *)); +extern char *polite_directory_format __P((char *)); + +extern char *extract_colon_unit __P((char *, int *)); + +extern void tilde_initialize __P((void)); +extern char *bash_tilde_expand __P((const char *, int)); + +extern int group_member __P((gid_t)); +extern char **get_group_list __P((int *)); +extern int *get_group_array __P((int *)); + +#endif /* _GENERAL_H_ */ diff --git a/include/shmbutil.h b/include/shmbutil.h index 492f6af..a737780 100644 --- a/include/shmbutil.h +++ b/include/shmbutil.h @@ -1,6 +1,6 @@ /* shmbutil.h -- utility functions for multibyte characters. */ -/* Copyright (C) 2002 Free Software Foundation, Inc. +/* Copyright (C) 2002-2004 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -23,46 +23,8 @@ #include "stdc.h" -/************************************************/ -/* check multibyte capability for I18N code */ -/************************************************/ - -/* For platforms which support the ISO C amendement 1 functionality we - support user defined character classes. */ - /* Solaris 2.5 has a bug: must be included before . */ -#if defined (HAVE_WCTYPE_H) && defined (HAVE_WCHAR_H) -# include -# include -# if defined (HAVE_MBSRTOWCS) /* system is supposed to support XPG5 */ -# define HANDLE_MULTIBYTE 1 -# endif -#endif /* HAVE_WCTYPE_H && HAVE_WCHAR_H */ - -/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */ -#if HANDLE_MULTIBYTE && !defined (HAVE_MBSTATE_T) -# define wcsrtombs(dest, src, len, ps) (wcsrtombs) (dest, src, len, 0) -# define mbsrtowcs(dest, src, len, ps) (mbsrtowcs) (dest, src, len, 0) -# define wcrtomb(s, wc, ps) (wcrtomb) (s, wc, 0) -# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0) -# define mbrlen(s, n, ps) (mbrlen) (s, n, 0) -# define mbstate_t int -#endif /* HANDLE_MULTIBYTE && !HAVE_MBSTATE_T */ - -/* Make sure MB_LEN_MAX is at least 16 on systems that claim to be able to - handle multibyte chars (some systems define MB_LEN_MAX as 1) */ -#ifdef HANDLE_MULTIBYTE -# include -# if defined(MB_LEN_MAX) && (MB_LEN_MAX < 16) -# undef MB_LEN_MAX -# endif -# if !defined (MB_LEN_MAX) -# define MB_LEN_MAX 16 -# endif -#endif /* HANDLE_MULTIBYTE */ - -/************************************************/ -/* end of multibyte capability checks for I18N */ -/************************************************/ +/* Include config.h for HANDLE_MULTIBYTE */ +#include #if defined (HANDLE_MULTIBYTE) diff --git a/include/shmbutil.h.save1 b/include/shmbutil.h.save1 new file mode 100644 index 0000000..492f6af --- /dev/null +++ b/include/shmbutil.h.save1 @@ -0,0 +1,470 @@ +/* shmbutil.h -- utility functions for multibyte characters. */ + +/* Copyright (C) 2002 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#if !defined (_SH_MBUTIL_H_) +#define _SH_MBUTIL_H_ + +#include "stdc.h" + +/************************************************/ +/* check multibyte capability for I18N code */ +/************************************************/ + +/* For platforms which support the ISO C amendement 1 functionality we + support user defined character classes. */ + /* Solaris 2.5 has a bug: must be included before . */ +#if defined (HAVE_WCTYPE_H) && defined (HAVE_WCHAR_H) +# include +# include +# if defined (HAVE_MBSRTOWCS) /* system is supposed to support XPG5 */ +# define HANDLE_MULTIBYTE 1 +# endif +#endif /* HAVE_WCTYPE_H && HAVE_WCHAR_H */ + +/* Some systems, like BeOS, have multibyte encodings but lack mbstate_t. */ +#if HANDLE_MULTIBYTE && !defined (HAVE_MBSTATE_T) +# define wcsrtombs(dest, src, len, ps) (wcsrtombs) (dest, src, len, 0) +# define mbsrtowcs(dest, src, len, ps) (mbsrtowcs) (dest, src, len, 0) +# define wcrtomb(s, wc, ps) (wcrtomb) (s, wc, 0) +# define mbrtowc(pwc, s, n, ps) (mbrtowc) (pwc, s, n, 0) +# define mbrlen(s, n, ps) (mbrlen) (s, n, 0) +# define mbstate_t int +#endif /* HANDLE_MULTIBYTE && !HAVE_MBSTATE_T */ + +/* Make sure MB_LEN_MAX is at least 16 on systems that claim to be able to + handle multibyte chars (some systems define MB_LEN_MAX as 1) */ +#ifdef HANDLE_MULTIBYTE +# include +# if defined(MB_LEN_MAX) && (MB_LEN_MAX < 16) +# undef MB_LEN_MAX +# endif +# if !defined (MB_LEN_MAX) +# define MB_LEN_MAX 16 +# endif +#endif /* HANDLE_MULTIBYTE */ + +/************************************************/ +/* end of multibyte capability checks for I18N */ +/************************************************/ + +#if defined (HANDLE_MULTIBYTE) + +extern size_t xmbsrtowcs __P((wchar_t *, const char **, size_t, mbstate_t *)); +extern size_t xdupmbstowcs __P((wchar_t **, char ***, const char *)); + +extern char *xstrchr __P((const char *, int)); + +#ifndef MB_INVALIDCH +#define MB_INVALIDCH(x) ((x) == (size_t)-1 || (x) == (size_t)-2) +#define MB_NULLWCH(x) ((x) == 0) +#endif + +#else /* !HANDLE_MULTIBYTE */ + +#undef MB_LEN_MAX +#undef MB_CUR_MAX + +#define MB_LEN_MAX 1 +#define MB_CUR_MAX 1 + +#undef xstrchr +#define xstrchr(s, c) strchr(s, c) + +#ifndef MB_INVALIDCH +#define MB_INVALIDCH(x) (0) +#define MB_NULLWCH(x) (0) +#endif + +#endif /* !HANDLE_MULTIBYTE */ + +/* Declare and initialize a multibyte state. Call must be terminated + with `;'. */ +#if defined (HANDLE_MULTIBYTE) +# define DECLARE_MBSTATE \ + mbstate_t state; \ + memset (&state, '\0', sizeof (mbstate_t)) +#else +# define DECLARE_MBSTATE +#endif /* !HANDLE_MULTIBYTE */ + +/* Initialize or reinitialize a multibyte state named `state'. Call must be + terminated with `;'. */ +#if defined (HANDLE_MULTIBYTE) +# define INITIALIZE_MBSTATE memset (&state, '\0', sizeof (mbstate_t)) +#else +# define INITIALIZE_MBSTATE +#endif /* !HANDLE_MULTIBYTE */ + +/* Advance one (possibly multi-byte) character in string _STR of length + _STRSIZE, starting at index _I. STATE must have already been declared. */ +#if defined (HANDLE_MULTIBYTE) +# define ADVANCE_CHAR(_str, _strsize, _i) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_str) + (_i), (_strsize) - (_i), &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + (_i)++; \ + } \ + else if (mblength == 0) \ + (_i)++; \ + else \ + (_i) += mblength; \ + } \ + else \ + (_i)++; \ + } \ + while (0) +#else +# define ADVANCE_CHAR(_str, _strsize, _i) (_i)++ +#endif /* !HANDLE_MULTIBYTE */ + +/* Advance one (possibly multibyte) character in the string _STR of length + _STRSIZE. + SPECIAL: assume that _STR will be incremented by 1 after this call. */ +#if defined (HANDLE_MULTIBYTE) +# define ADVANCE_CHAR_P(_str, _strsize) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_str), (_strsize), &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + (_str) += (mblength < 1) ? 0 : (mblength - 1); \ + } \ + } \ + while (0) +#else +# define ADVANCE_CHAR_P(_str, _strsize) +#endif /* !HANDLE_MULTIBYTE */ + +/* Back up one (possibly multi-byte) character in string _STR of length + _STRSIZE, starting at index _I. STATE must have already been declared. */ +#if defined (HANDLE_MULTIBYTE) +# define BACKUP_CHAR(_str, _strsize, _i) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _x, _p; /* _x == temp index into string, _p == prev index */ \ +\ + _x = _p = 0; \ + while (_x < (_i)) \ + { \ + state_bak = state; \ + mblength = mbrlen ((_str) + (_x), (_strsize) - (_x), &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + _x++; \ + } \ + else if (mblength == 0) \ + _x++; \ + else \ + { \ + _p = _x; /* _p == start of prev mbchar */ \ + _x += mblength; \ + } \ + } \ + (_i) = _p; \ + } \ + else \ + (_i)--; \ + } \ + while (0) +#else +# define BACKUP_CHAR(_str, _strsize, _i) (_i)-- +#endif /* !HANDLE_MULTIBYTE */ + +/* Back up one (possibly multibyte) character in the string _BASE of length + _STRSIZE starting at _STR (_BASE <= _STR <= (_BASE + _STRSIZE) ). + SPECIAL: DO NOT assume that _STR will be decremented by 1 after this call. */ +#if defined (HANDLE_MULTIBYTE) +# define BACKUP_CHAR_P(_base, _strsize, _str) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + char *_x, _p; /* _x == temp pointer into string, _p == prev pointer */ \ +\ + _x = _p = _base; \ + while (_x < (_str)) \ + { \ + state_bak = state; \ + mblength = mbrlen (_x, (_strsize) - _x, &state); \ +\ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + _x++; \ + } \ + else if (mblength == 0) \ + _x++; \ + else \ + { \ + _p = _x; /* _p == start of prev mbchar */ \ + _x += mblength; \ + } \ + } \ + (_str) = _p; \ + } \ + else \ + (_str)--; \ + } \ + while (0) +#else +# define BACKUP_CHAR_P(_base, _strsize, _str) (_str)-- +#endif /* !HANDLE_MULTIBYTE */ + +/* Copy a single character from the string _SRC to the string _DST. + _SRCEND is a pointer to the end of _SRC. */ +#if defined (HANDLE_MULTIBYTE) +# define COPY_CHAR_P(_dst, _src, _srcend) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _k; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src), (_srcend) - (_src), &state); \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + for (_k = 0; _k < mblength; _k++) \ + *(_dst)++ = *(_src)++; \ + } \ + else \ + *(_dst)++ = *(_src)++; \ + } \ + while (0) +#else +# define COPY_CHAR_P(_dst, _src, _srcend) *(_dst)++ = *(_src)++ +#endif /* !HANDLE_MULTIBYTE */ + +/* Copy a single character from the string _SRC at index _SI to the string + _DST at index _DI. _SRCEND is a pointer to the end of _SRC. */ +#if defined (HANDLE_MULTIBYTE) +# define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _k; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src)+(_si)), &state); \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + for (_k = 0; _k < mblength; _k++) \ + _dst[_di++] = _src[_si++]; \ + } \ + else \ + _dst[_di++] = _src[_si++]; \ + } \ + while (0) +#else +# define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) _dst[_di++] = _src[_si++] +#endif /* !HANDLE_MULTIBYTE */ + +/**************************************************************** + * * + * The following are only guaranteed to work in subst.c * + * * + ****************************************************************/ + +#if defined (HANDLE_MULTIBYTE) +# define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ + int _i; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_slen) - (_si), &state); \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + temp = xmalloc (mblength + 2); \ + temp[0] = _escchar; \ + for (_i = 0; _i < mblength; _i++) \ + temp[_i + 1] = _src[_si++]; \ + temp[mblength + 1] = '\0'; \ +\ + goto add_string; \ + } \ + else \ + { \ + _dst[0] = _escchar; \ + _dst[1] = _sc; \ + } \ + } \ + while (0) +#else +# define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \ + _dst[0] = _escchar; \ + _dst[1] = _sc +#endif /* !HANDLE_MULTIBYTE */ + +#if defined (HANDLE_MULTIBYTE) +# define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src) + (_si)), &state); \ + if (mblength == (size_t)-2 || mblength == (size_t)-1) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + else \ + mblength = (mblength < 1) ? 1 : mblength; \ +\ + FASTCOPY(((_src) + (_si)), (_dst), mblength); \ +\ + (_dst) += mblength; \ + (_si) += mblength; \ + } \ + else \ + { \ + *(_dst)++ = _src[(_si)]; \ + (_si)++; \ + } \ + } \ + while (0) +#else +# define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \ + *(_dst)++ = _src[(_si)]; \ + (_si)++ +#endif /* !HANDLE_MULTIBYTE */ + +#if HANDLE_MULTIBYTE +# define SADD_MBCHAR(_dst, _src, _si, _srcsize) \ + do \ + { \ + if (MB_CUR_MAX > 1) \ + { \ + int i; \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \ + if (mblength == (size_t)-1 || mblength == (size_t)-2) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + if (mblength < 1) \ + mblength = 1; \ +\ + _dst = (char *)xmalloc (mblength + 1); \ + for (i = 0; i < mblength; i++) \ + (_dst)[i] = (_src)[(_si)++]; \ + (_dst)[mblength] = '\0'; \ +\ + goto add_string; \ + } \ + } \ + while (0) + +#else +# define SADD_MBCHAR(_dst, _src, _si, _srcsize) +#endif + +/* Watch out when using this -- it's just straight textual subsitution */ +#if defined (HANDLE_MULTIBYTE) +# define SADD_MBQCHAR_BODY(_dst, _src, _si, _srcsize) \ +\ + int i; \ + mbstate_t state_bak; \ + size_t mblength; \ +\ + state_bak = state; \ + mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \ + if (mblength == (size_t)-1 || mblength == (size_t)-2) \ + { \ + state = state_bak; \ + mblength = 1; \ + } \ + if (mblength < 1) \ + mblength = 1; \ +\ + (_dst) = (char *)xmalloc (mblength + 2); \ + (_dst)[0] = CTLESC; \ + for (i = 0; i < mblength; i++) \ + (_dst)[i+1] = (_src)[(_si)++]; \ + (_dst)[mblength+1] = '\0'; \ +\ + goto add_string + +#endif /* HANDLE_MULTIBYTE */ +#endif /* _SH_MBUTIL_H_ */ diff --git a/include/shmbutil.h~ b/include/shmbutil.h~ index 30d310e..492f6af 100644 --- a/include/shmbutil.h~ +++ b/include/shmbutil.h~ @@ -67,6 +67,7 @@ #if defined (HANDLE_MULTIBYTE) extern size_t xmbsrtowcs __P((wchar_t *, const char **, size_t, mbstate_t *)); +extern size_t xdupmbstowcs __P((wchar_t **, char ***, const char *)); extern char *xstrchr __P((const char *, int)); diff --git a/jobs.c b/jobs.c index eeb19ba..3950b93 100644 --- a/jobs.c +++ b/jobs.c @@ -3433,6 +3433,12 @@ set_job_control (arg) old = job_control; job_control = arg; + + /* If we're turning on job control, reset pipeline_pgrp so make_child will + put new child processes into the right pgrp */ + if (job_control != old && job_control) + pipeline_pgrp = 0; + return (old); } diff --git a/jobs.c~ b/jobs.c~ new file mode 100644 index 0000000..b4e06c7 --- /dev/null +++ b/jobs.c~ @@ -0,0 +1,3535 @@ +/* The thing that makes children, remembers them, and contains wait loops. */ + +/* This file works with both POSIX and BSD systems. It implements job + control. */ + +/* Copyright (C) 1989-2003 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include "bashtypes.h" +#include "trap.h" +#include +#include +#include + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include "posixtime.h" + +#if defined (HAVE_SYS_RESOURCE_H) && defined (HAVE_WAIT3) && !defined (_POSIX_VERSION) && !defined (RLIMTYPE) +# include +#endif /* !_POSIX_VERSION && HAVE_SYS_RESOURCE_H && HAVE_WAIT3 && !RLIMTYPE */ + +#if defined (HAVE_SYS_FILE_H) +# include +#endif + +#include "filecntl.h" +#include +#include + +#if defined (BUFFERED_INPUT) +# include "input.h" +#endif + +/* Need to include this up here for *_TTY_DRIVER definitions. */ +#include "shtty.h" + +/* Define this if your output is getting swallowed. It's a no-op on + machines with the termio or termios tty drivers. */ +/* #define DRAIN_OUTPUT */ + +/* For the TIOCGPGRP and TIOCSPGRP ioctl parameters on HP-UX */ +#if defined (hpux) && !defined (TERMIOS_TTY_DRIVER) +# include +#endif /* hpux && !TERMIOS_TTY_DRIVER */ + +#if !defined (STRUCT_WINSIZE_IN_SYS_IOCTL) +/* For struct winsize on SCO */ +/* sys/ptem.h has winsize but needs mblk_t from sys/stream.h */ +# if defined (HAVE_SYS_PTEM_H) && defined (TIOCGWINSZ) && defined (SIGWINCH) +# if defined (HAVE_SYS_STREAM_H) +# include +# endif +# include +# endif /* HAVE_SYS_PTEM_H && TIOCGWINSZ && SIGWINCH */ +#endif /* !STRUCT_WINSIZE_IN_SYS_IOCTL */ + +#include "bashansi.h" +#include "bashintl.h" +#include "shell.h" +#include "jobs.h" +#include "flags.h" + +#include "builtins/builtext.h" +#include "builtins/common.h" + +#if !defined (errno) +extern int errno; +#endif /* !errno */ + +#define DEFAULT_CHILD_MAX 32 +#define MAX_JOBS_IN_ARRAY 4096 /* testing */ + +/* Take care of system dependencies that must be handled when waiting for + children. The arguments to the WAITPID macro match those to the Posix.1 + waitpid() function. */ + +#if defined (ultrix) && defined (mips) && defined (_POSIX_VERSION) +# define WAITPID(pid, statusp, options) \ + wait3 ((union wait *)statusp, options, (struct rusage *)0) +#else +# if defined (_POSIX_VERSION) || defined (HAVE_WAITPID) +# define WAITPID(pid, statusp, options) \ + waitpid ((pid_t)pid, statusp, options) +# else +# if defined (HAVE_WAIT3) +# define WAITPID(pid, statusp, options) \ + wait3 (statusp, options, (struct rusage *)0) +# else +# define WAITPID(pid, statusp, options) \ + wait3 (statusp, options, (int *)0) +# endif /* HAVE_WAIT3 */ +# endif /* !_POSIX_VERSION && !HAVE_WAITPID*/ +#endif /* !(Ultrix && mips && _POSIX_VERSION) */ + +/* getpgrp () varies between systems. Even systems that claim to be + Posix.1 compatible lie sometimes (Ultrix, SunOS4, apollo). */ +#if defined (GETPGRP_VOID) +# define getpgid(p) getpgrp () +#else +# define getpgid(p) getpgrp (p) +#endif /* !GETPGRP_VOID */ + +/* If the system needs it, REINSTALL_SIGCHLD_HANDLER will reinstall the + handler for SIGCHLD. */ +#if defined (MUST_REINSTALL_SIGHANDLERS) +# define REINSTALL_SIGCHLD_HANDLER signal (SIGCHLD, sigchld_handler) +#else +# define REINSTALL_SIGCHLD_HANDLER +#endif /* !MUST_REINSTALL_SIGHANDLERS */ + +/* Some systems let waitpid(2) tell callers about stopped children. */ +#if !defined (WCONTINUED) +# define WCONTINUED 0 +#endif +#if !defined (WIFCONTINUED) +# define WIFCONTINUED(s) (0) +#endif + +/* The number of additional slots to allocate when we run out. */ +#define JOB_SLOTS 8 + +typedef int sh_job_map_func_t __P((JOB *, int, int, int)); + +#if defined (READLINE) +extern void rl_set_screen_size __P((int, int)); +#endif + +/* Variables used here but defined in other files. */ +extern int subshell_environment, line_number; +extern int posixly_correct, shell_level; +extern int interrupt_immediately; +extern int last_command_exit_value, last_command_exit_signal; +extern int loop_level, breaking; +extern int sourcelevel; +extern sh_builtin_func_t *this_shell_builtin; +extern char *shell_name, *this_command_name; +extern sigset_t top_level_mask; +extern procenv_t wait_intr_buf; +extern int wait_signal_received; +extern WORD_LIST *subst_assign_varlist; + +/* The array of known jobs. */ +JOB **jobs = (JOB **)NULL; + +/* The number of slots currently allocated to JOBS. */ +int job_slots = 0; + +/* The controlling tty for this shell. */ +int shell_tty = -1; + +/* The shell's process group. */ +pid_t shell_pgrp = NO_PID; + +/* The terminal's process group. */ +pid_t terminal_pgrp = NO_PID; + +/* The process group of the shell's parent. */ +pid_t original_pgrp = NO_PID; + +/* The process group of the pipeline currently being made. */ +pid_t pipeline_pgrp = (pid_t)0; + +#if defined (PGRP_PIPE) +/* Pipes which each shell uses to communicate with the process group leader + until all of the processes in a pipeline have been started. Then the + process leader is allowed to continue. */ +int pgrp_pipe[2] = { -1, -1 }; +#endif + +/* The job which is current; i.e. the one that `%+' stands for. */ +int current_job = NO_JOB; + +/* The previous job; i.e. the one that `%-' stands for. */ +int previous_job = NO_JOB; + +/* Last child made by the shell. */ +pid_t last_made_pid = NO_PID; + +/* Pid of the last asynchronous child. */ +pid_t last_asynchronous_pid = NO_PID; + +/* The pipeline currently being built. */ +PROCESS *the_pipeline = (PROCESS *)NULL; + +/* If this is non-zero, do job control. */ +int job_control = 1; + +/* Call this when you start making children. */ +int already_making_children = 0; + +/* If this is non-zero, $LINES and $COLUMNS are reset after every process + exits from get_tty_state(). */ +int check_window_size; + +/* Functions local to this file. */ + +static void get_new_window_size __P((int)); + +static void run_sigchld_trap __P((int)); + +static sighandler wait_sigint_handler __P((int)); +static sighandler sigchld_handler __P((int)); +static sighandler sigwinch_sighandler __P((int)); +static sighandler sigcont_sighandler __P((int)); +static sighandler sigstop_sighandler __P((int)); + +static int waitchld __P((pid_t, int)); + +static PROCESS *find_pipeline __P((pid_t, int, int *)); + +static char *current_working_directory __P((void)); +static char *job_working_directory __P((void)); +static char *j_strsignal __P((int)); +static char *printable_job_status __P((int, PROCESS *, int)); + +static pid_t find_last_pid __P((int, int)); + +static int set_new_line_discipline __P((int)); +static int map_over_jobs __P((sh_job_map_func_t *, int, int)); +static int job_last_stopped __P((int)); +static int job_last_running __P((int)); +static int most_recent_job_in_state __P((int, JOB_STATE)); +static int find_job __P((pid_t, int)); +static int print_job __P((JOB *, int, int, int)); +static int process_exit_status __P((WAIT)); +static int process_exit_signal __P((WAIT)); +static int job_exit_status __P((int)); +static int job_exit_signal __P((int)); +static int set_job_status_and_cleanup __P((int)); + +static WAIT raw_job_exit_status __P((int)); + +static void notify_of_job_status __P((void)); +static void cleanup_dead_jobs __P((void)); +static int compact_jobs_list __P((int)); +static void discard_pipeline __P((PROCESS *)); +static void add_process __P((char *, pid_t)); +static void print_pipeline __P((PROCESS *, int, int, FILE *)); +static void pretty_print_job __P((int, int, FILE *)); +static void set_current_job __P((int)); +static void reset_current __P((void)); +static void set_job_running __P((int)); +static void setjstatus __P((int)); +static void mark_all_jobs_as_dead __P((void)); +static void mark_dead_jobs_as_notified __P((int)); +static void restore_sigint_handler __P((void)); +#if defined (PGRP_PIPE) +static void pipe_read __P((int *)); +static void pipe_close __P((int *)); +#endif + +#if defined (ARRAY_VARS) +static int *pstatuses; /* list of pipeline statuses */ +static int statsize; +#endif + +/* Used to synchronize between wait_for and other functions and the SIGCHLD + signal handler. */ +static int sigchld; +static int queue_sigchld; + +#define QUEUE_SIGCHLD(os) (os) = sigchld, queue_sigchld++ + +#define UNQUEUE_SIGCHLD(os) \ + do { \ + queue_sigchld--; \ + if (queue_sigchld == 0 && os != sigchld) \ + waitchld (-1, 0); \ + } while (0) + +static SigHandler *old_tstp, *old_ttou, *old_ttin; +static SigHandler *old_cont = (SigHandler *)SIG_DFL; + +#if defined (TIOCGWINSZ) && defined (SIGWINCH) +static SigHandler *old_winch = (SigHandler *)SIG_DFL; +#endif + +/* A place to temporarily save the current pipeline. */ +static PROCESS *saved_pipeline; +static int saved_already_making_children; + +/* Set this to non-zero whenever you don't want the jobs list to change at + all: no jobs deleted and no status change notifications. This is used, + for example, when executing SIGCHLD traps, which may run arbitrary + commands. */ +static int jobs_list_frozen; + +static char retcode_name_buffer[64]; + +static long child_max = -1L; + +#if !defined (_POSIX_VERSION) + +/* These are definitions to map POSIX 1003.1 functions onto existing BSD + library functions and system calls. */ +#define setpgid(pid, pgrp) setpgrp (pid, pgrp) +#define tcsetpgrp(fd, pgrp) ioctl ((fd), TIOCSPGRP, &(pgrp)) + +pid_t +tcgetpgrp (fd) + int fd; +{ + pid_t pgrp; + + /* ioctl will handle setting errno correctly. */ + if (ioctl (fd, TIOCGPGRP, &pgrp) < 0) + return (-1); + return (pgrp); +} + +#endif /* !_POSIX_VERSION */ + +/* Return the working directory for the current process. Unlike + job_working_directory, this does not call malloc (), nor do any + of the functions it calls. This is so that it can safely be called + from a signal handler. */ +static char * +current_working_directory () +{ + char *dir; + static char d[PATH_MAX]; + + dir = get_string_value ("PWD"); + + if (dir == 0 && the_current_working_directory && no_symbolic_links) + dir = the_current_working_directory; + + if (dir == 0) + { + dir = getcwd (d, sizeof(d)); + if (dir) + dir = d; + } + + return (dir == 0) ? "" : dir; +} + +/* Return the working directory for the current process. */ +static char * +job_working_directory () +{ + char *dir; + + dir = get_string_value ("PWD"); + if (dir) + return (savestring (dir)); + + dir = get_working_directory ("job-working-directory"); + if (dir) + return (dir); + + return (savestring ("")); +} + +void +making_children () +{ + if (already_making_children) + return; + + already_making_children = 1; + start_pipeline (); +} + +void +stop_making_children () +{ + already_making_children = 0; +} + +void +cleanup_the_pipeline () +{ + if (the_pipeline) + { + discard_pipeline (the_pipeline); + the_pipeline = (PROCESS *)NULL; + } +} + +void +save_pipeline (clear) + int clear; +{ + saved_pipeline = the_pipeline; + saved_already_making_children = already_making_children; + if (clear) + the_pipeline = (PROCESS *)NULL; +} + +void +restore_pipeline (discard) + int discard; +{ + PROCESS *old_pipeline; + + old_pipeline = the_pipeline; + the_pipeline = saved_pipeline; + already_making_children = saved_already_making_children; + if (discard) + discard_pipeline (old_pipeline); +} + +/* Start building a pipeline. */ +void +start_pipeline () +{ + if (the_pipeline) + { + cleanup_the_pipeline (); + pipeline_pgrp = 0; +#if defined (PGRP_PIPE) + pipe_close (pgrp_pipe); +#endif + } + +#if defined (PGRP_PIPE) + if (job_control) + { + if (pipe (pgrp_pipe) == -1) + sys_error ("start_pipeline: pgrp pipe"); + } +#endif +} + +/* Stop building a pipeline. Install the process list in the job array. + This returns the index of the newly installed job. + DEFERRED is a command structure to be executed upon satisfactory + execution exit of this pipeline. */ +int +stop_pipeline (async, deferred) + int async; + COMMAND *deferred; +{ + register int i, j; + JOB *newjob; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + +#if defined (PGRP_PIPE) + /* The parent closes the process group synchronization pipe. */ + pipe_close (pgrp_pipe); +#endif + + cleanup_dead_jobs (); + + if (job_slots == 0) + { + job_slots = JOB_SLOTS; + jobs = (JOB **)xmalloc (job_slots * sizeof (JOB *)); + + /* Now blank out these new entries. */ + for (i = 0; i < job_slots; i++) + jobs[i] = (JOB *)NULL; + } + + /* Scan from the last slot backward, looking for the next free one. */ + if (interactive) + { + for (i = job_slots; i; i--) + if (jobs[i - 1]) + break; + } + else + { + /* If we're not interactive, we don't need to monotonically increase + the job number (in fact, we don't care about the job number at all), + so we can simply scan for the first free slot. This helps to keep + us from continuously reallocating the jobs array when running + certain kinds of shell loops, and saves time spent searching. */ + for (i = 0; i < job_slots; i++) + if (jobs[i] == 0) + break; + } + + /* Do we need more room? */ + + /* First try compaction */ + if (subshell_environment && interactive_shell && i == job_slots && job_slots >= MAX_JOBS_IN_ARRAY) + i = compact_jobs_list (0); + + /* If we can't compact, reallocate */ + if (i == job_slots) + { + job_slots += JOB_SLOTS; + jobs = (JOB **)xrealloc (jobs, ((1 + job_slots) * sizeof (JOB *))); + + for (j = i; j < job_slots; j++) + jobs[j] = (JOB *)NULL; + } + + /* Add the current pipeline to the job list. */ + if (the_pipeline) + { + register PROCESS *p; + int any_alive, any_stopped; + + newjob = (JOB *)xmalloc (sizeof (JOB)); + + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + p->next = (PROCESS *)NULL; + newjob->pipe = REVERSE_LIST (the_pipeline, PROCESS *); + for (p = newjob->pipe; p->next; p = p->next) + ; + p->next = newjob->pipe; + + the_pipeline = (PROCESS *)NULL; + newjob->pgrp = pipeline_pgrp; + pipeline_pgrp = 0; + + newjob->flags = 0; + + /* Flag to see if in another pgrp. */ + if (job_control) + newjob->flags |= J_JOBCONTROL; + + /* Set the state of this pipeline. */ + p = newjob->pipe; + any_alive = any_stopped = 0; + do + { + any_alive |= p->running; + any_stopped |= WIFSTOPPED (p->status); + p = p->next; + } + while (p != newjob->pipe); + + newjob->state = any_alive ? JRUNNING : (any_stopped ? JSTOPPED : JDEAD); + newjob->wd = job_working_directory (); + newjob->deferred = deferred; + + newjob->j_cleanup = (sh_vptrfunc_t *)NULL; + newjob->cleanarg = (PTR_T) NULL; + + jobs[i] = newjob; + if (newjob->state == JDEAD && (newjob->flags & J_FOREGROUND)) + setjstatus (i); + } + else + newjob = (JOB *)NULL; + + if (async) + { + if (newjob) + newjob->flags &= ~J_FOREGROUND; + reset_current (); + } + else + { + if (newjob) + { + newjob->flags |= J_FOREGROUND; + /* + * !!!!! NOTE !!!!! (chet@ins.cwru.edu) + * + * The currently-accepted job control wisdom says to set the + * terminal's process group n+1 times in an n-step pipeline: + * once in the parent and once in each child. This is where + * the parent gives it away. + * + */ + if (job_control && newjob->pgrp) + give_terminal_to (newjob->pgrp, 0); + } + } + + stop_making_children (); + UNBLOCK_CHILD (oset); + return (current_job); +} + +/* Delete all DEAD jobs that the user had received notification about. */ +static void +cleanup_dead_jobs () +{ + register int i; + int os; + + if (job_slots == 0 || jobs_list_frozen) + return; + + QUEUE_SIGCHLD(os); + + for (i = 0; i < job_slots; i++) + if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i)) + delete_job (i, 0); + + UNQUEUE_SIGCHLD(os); +} + +/* Compact the jobs list by removing dead jobs. Assumed that we have filled + the jobs array to some predefined maximum. Called when the shell is not + the foreground process (subshell_environment != 0). Returns the first + available slot in the compacted list. If that value is job_slots, then + the list needs to be reallocated. The jobs array is in new memory if + this returns > 0 and < job_slots. FLAGS is reserved for future use. */ +static int +compact_jobs_list (flags) + int flags; +{ + sigset_t set, oset; + register int i, j; + int nremove, ndel; + JOB **newlist; + + if (job_slots == 0 || jobs_list_frozen) + return job_slots; + + if (child_max < 0) + child_max = getmaxchild (); + + /* Take out at most a quarter of the jobs in the jobs array, but leave at + least child_max */ + nremove = job_slots >> 2; + if ((job_slots - nremove) < child_max) + nremove = job_slots - child_max; + + /* need to increase jobs list to at least CHILD_MAX entries */ + if (nremove < 0) + return job_slots; + + BLOCK_CHILD (set, oset); + + for (ndel = i = 0; i < job_slots; i++) + if (jobs[i]) + { + if (DEADJOB (i) && (find_last_pid (i, 0) != last_asynchronous_pid)) + { + delete_job (i, 0); + ndel++; + if (ndel == nremove) + break; + } + } + + if (ndel == 0) + { + UNBLOCK_CHILD (oset); + return job_slots; + } + + newlist = (JOB **)xmalloc ((1 + job_slots) * sizeof (JOB *)); + for (i = j = 0; i < job_slots; i++) + if (jobs[i]) + newlist[j++] = jobs[i]; + + ndel = j; + for ( ; j < job_slots; j++) + newlist[j] = (JOB *)NULL; + + free (jobs); + jobs = newlist; + + UNBLOCK_CHILD (oset); + + return ndel; +} + +/* Delete the job at INDEX from the job list. Must be called + with SIGCHLD blocked. */ +void +delete_job (job_index, warn_stopped) + int job_index, warn_stopped; +{ + register JOB *temp; + + if (job_slots == 0 || jobs_list_frozen) + return; + + if (warn_stopped && subshell_environment == 0 && STOPPED (job_index)) + internal_warning (_("deleting stopped job %d with process group %ld"), job_index+1, (long)jobs[job_index]->pgrp); + + temp = jobs[job_index]; + if (job_index == current_job || job_index == previous_job) + reset_current (); + + jobs[job_index] = (JOB *)NULL; + + free (temp->wd); + discard_pipeline (temp->pipe); + + if (temp->deferred) + dispose_command (temp->deferred); + + free (temp); +} + +/* Must be called with SIGCHLD blocked. */ +void +nohup_job (job_index) + int job_index; +{ + register JOB *temp; + + if (job_slots == 0) + return; + + if (temp = jobs[job_index]) + temp->flags |= J_NOHUP; +} + +/* Get rid of the data structure associated with a process chain. */ +static void +discard_pipeline (chain) + register PROCESS *chain; +{ + register PROCESS *this, *next; + + this = chain; + do + { + next = this->next; + FREE (this->command); + free (this); + this = next; + } + while (this != chain); +} + +/* Add this process to the chain being built in the_pipeline. + NAME is the command string that will be exec'ed later. + PID is the process id of the child. */ +static void +add_process (name, pid) + char *name; + pid_t pid; +{ + PROCESS *t, *p; + + t = (PROCESS *)xmalloc (sizeof (PROCESS)); + t->next = the_pipeline; + t->pid = pid; + WSTATUS (t->status) = 0; + t->running = PS_RUNNING; + t->command = name; + the_pipeline = t; + + if (t->next == 0) + t->next = t; + else + { + p = t->next; + while (p->next != t->next) + p = p->next; + p->next = t; + } +} + +#if 0 +/* Take the last job and make it the first job. Must be called with + SIGCHLD blocked. */ +int +rotate_the_pipeline () +{ + PROCESS *p; + + if (the_pipeline->next == the_pipeline) + return; + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + the_pipeline = p; +} + +/* Reverse the order of the processes in the_pipeline. Must be called with + SIGCHLD blocked. */ +int +reverse_the_pipeline () +{ + PROCESS *p, *n; + + if (the_pipeline->next == the_pipeline) + return; + + for (p = the_pipeline; p->next != the_pipeline; p = p->next) + ; + p->next = (PROCESS *)NULL; + + n = REVERSE_LIST (the_pipeline, PROCESS *); + + the_pipeline = n; + for (p = the_pipeline; p->next; p = p->next) + ; + p->next = the_pipeline; +} +#endif + +/* Map FUNC over the list of jobs. If FUNC returns non-zero, + then it is time to stop mapping, and that is the return value + for map_over_jobs. FUNC is called with a JOB, arg1, arg2, + and INDEX. */ +static int +map_over_jobs (func, arg1, arg2) + sh_job_map_func_t *func; + int arg1, arg2; +{ + register int i; + int result; + sigset_t set, oset; + + if (job_slots == 0) + return 0; + + BLOCK_CHILD (set, oset); + + for (i = result = 0; i < job_slots; i++) + { + if (jobs[i]) + { + result = (*func)(jobs[i], arg1, arg2, i); + if (result) + break; + } + } + + UNBLOCK_CHILD (oset); + + return (result); +} + +/* Cause all the jobs in the current pipeline to exit. */ +void +terminate_current_pipeline () +{ + if (pipeline_pgrp && pipeline_pgrp != shell_pgrp) + { + killpg (pipeline_pgrp, SIGTERM); + killpg (pipeline_pgrp, SIGCONT); + } +} + +/* Cause all stopped jobs to exit. */ +void +terminate_stopped_jobs () +{ + register int i; + + for (i = 0; i < job_slots; i++) + { + if (jobs[i] && STOPPED (i)) + { + killpg (jobs[i]->pgrp, SIGTERM); + killpg (jobs[i]->pgrp, SIGCONT); + } + } +} + +/* Cause all jobs, running or stopped, to receive a hangup signal. If + a job is marked J_NOHUP, don't send the SIGHUP. */ +void +hangup_all_jobs () +{ + register int i; + + for (i = 0; i < job_slots; i++) + { + if (jobs[i]) + { + if ((jobs[i]->flags & J_NOHUP) == 0) + killpg (jobs[i]->pgrp, SIGHUP); + if (STOPPED (i)) + killpg (jobs[i]->pgrp, SIGCONT); + } + } +} + +void +kill_current_pipeline () +{ + stop_making_children (); + start_pipeline (); +} + +/* Return the pipeline that PID belongs to. Note that the pipeline + doesn't have to belong to a job. Must be called with SIGCHLD blocked. */ +static PROCESS * +find_pipeline (pid, running_only, jobp) + pid_t pid; + int running_only; + int *jobp; /* index into jobs list or NO_JOB */ +{ + int job; + register PROCESS *p; + + /* See if this process is in the pipeline that we are building. */ + if (jobp) + *jobp = NO_JOB; + if (the_pipeline) + { + p = the_pipeline; + do + { + /* Return it if we found it. */ + if (p->pid == pid) + { + if ((running_only && PRUNNING(p)) || (running_only == 0)) + return (p); + } + + p = p->next; + } + while (p != the_pipeline); + } + + job = find_job (pid, running_only); + if (jobp) + *jobp = job; + return (job == NO_JOB) ? (PROCESS *)NULL : jobs[job]->pipe; +} + +/* Return the job index that PID belongs to, or NO_JOB if it doesn't + belong to any job. Must be called with SIGCHLD blocked. */ +static int +find_job (pid, running_only) + pid_t pid; + int running_only; +{ + register int i; + register PROCESS *p; + + for (i = 0; i < job_slots; i++) + { + if (jobs[i]) + { + p = jobs[i]->pipe; + + do + { + if (p->pid == pid) + { + if ((running_only && PRUNNING(p)) || (running_only == 0)) + return (i); + } + + p = p->next; + } + while (p != jobs[i]->pipe); + } + } + + return (NO_JOB); +} + +/* Find a job given a PID. If BLOCK is non-zero, block SIGCHLD as + required by find_job. */ +int +get_job_by_pid (pid, block) + pid_t pid; + int block; +{ + int job; + sigset_t set, oset; + + if (block) + BLOCK_CHILD (set, oset); + + job = find_job (pid, 0); + + if (block) + UNBLOCK_CHILD (oset); + + return job; +} + +/* Print descriptive information about the job with leader pid PID. */ +void +describe_pid (pid) + pid_t pid; +{ + int job; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + job = find_job (pid, 0); + + if (job != NO_JOB) + printf ("[%d] %ld\n", job + 1, (long)pid); + else + programming_error (_("describe_pid: %ld: no such pid"), (long)pid); + + UNBLOCK_CHILD (oset); +} + +static char * +j_strsignal (s) + int s; +{ + char *x; + + x = strsignal (s); + if (x == 0) + { + x = retcode_name_buffer; + sprintf (x, "Signal %d", s); + } + return x; +} + +static char * +printable_job_status (j, p, format) + int j; + PROCESS *p; + int format; +{ + static char *temp; + int es; + + temp = "Done"; + + if (STOPPED (j) && format == 0) + { + if (posixly_correct == 0 || p == 0 || (WIFSTOPPED (p->status) == 0)) + temp = "Stopped"; + else + { + temp = retcode_name_buffer; + sprintf (temp, "Stopped(%s)", signal_name (WSTOPSIG (p->status))); + } + } + else if (RUNNING (j)) + temp = "Running"; + else + { + if (WIFSTOPPED (p->status)) + temp = j_strsignal (WSTOPSIG (p->status)); + else if (WIFSIGNALED (p->status)) + temp = j_strsignal (WTERMSIG (p->status)); + else if (WIFEXITED (p->status)) + { + temp = retcode_name_buffer; + es = WEXITSTATUS (p->status); + if (es == 0) + strcpy (temp, "Done"); + else if (posixly_correct) + sprintf (temp, "Done(%d)", es); + else + sprintf (temp, "Exit %d", es); + } + else + temp = "Unknown status"; + } + + return temp; +} + +/* This is the way to print out information on a job if you + know the index. FORMAT is: + + JLIST_NORMAL) [1]+ Running emacs + JLIST_LONG ) [1]+ 2378 Running emacs + -1 ) [1]+ 2378 emacs + + JLIST_NORMAL) [1]+ Stopped ls | more + JLIST_LONG ) [1]+ 2369 Stopped ls + 2367 | more + JLIST_PID_ONLY) + Just list the pid of the process group leader (really + the process group). + JLIST_CHANGED_ONLY) + Use format JLIST_NORMAL, but list only jobs about which + the user has not been notified. */ + +/* Print status for pipeline P. If JOB_INDEX is >= 0, it is the index into + the JOBS array corresponding to this pipeline. FORMAT is as described + above. Must be called with SIGCHLD blocked. + + If you're printing a pipeline that's not in the jobs array, like the + current pipeline as it's being created, pass -1 for JOB_INDEX */ +static void +print_pipeline (p, job_index, format, stream) + PROCESS *p; + int job_index, format; + FILE *stream; +{ + PROCESS *first, *last, *show; + int es, name_padding; + char *temp; + + if (p == 0) + return; + + first = last = p; + while (last->next != first) + last = last->next; + + for (;;) + { + if (p != first) + fprintf (stream, format ? " " : " |"); + + if (format != JLIST_STANDARD) + fprintf (stream, "%5ld", (long)p->pid); + + fprintf (stream, " "); + + if (format > -1 && job_index >= 0) + { + show = format ? p : last; + temp = printable_job_status (job_index, show, format); + + if (p != first) + { + if (format) + { + if (show->running == first->running && + WSTATUS (show->status) == WSTATUS (first->status)) + temp = ""; + } + else + temp = (char *)NULL; + } + + if (temp) + { + fprintf (stream, "%s", temp); + + es = STRLEN (temp); + if (es == 0) + es = 2; /* strlen ("| ") */ + name_padding = LONGEST_SIGNAL_DESC - es; + + fprintf (stream, "%*s", name_padding, ""); + + if ((WIFSTOPPED (show->status) == 0) && + (WIFCONTINUED (show->status) == 0) && + WIFCORED (show->status)) + fprintf (stream, "(core dumped) "); + } + } + + if (p != first && format) + fprintf (stream, "| "); + + if (p->command) + fprintf (stream, "%s", p->command); + + if (p == last && job_index >= 0) + { + temp = current_working_directory (); + + if (RUNNING (job_index) && (IS_FOREGROUND (job_index) == 0)) + fprintf (stream, " &"); + + if (strcmp (temp, jobs[job_index]->wd) != 0) + fprintf (stream, + " (wd: %s)", polite_directory_format (jobs[job_index]->wd)); + } + + if (format || (p == last)) + { + /* We need to add a CR only if this is an interactive shell, and + we're reporting the status of a completed job asynchronously. + We can't really check whether this particular job is being + reported asynchronously, so just add the CR if the shell is + currently interactive and asynchronous notification is enabled. */ + if (asynchronous_notification && interactive) + fprintf (stream, "\r\n"); + else + fprintf (stream, "\n"); + } + + if (p == last) + break; + p = p->next; + } + fflush (stream); +} + +/* Print information to STREAM about jobs[JOB_INDEX] according to FORMAT. + Must be called with SIGCHLD blocked or queued with queue_sigchld */ +static void +pretty_print_job (job_index, format, stream) + int job_index, format; + FILE *stream; +{ + register PROCESS *p; + + /* Format only pid information about the process group leader? */ + if (format == JLIST_PID_ONLY) + { + fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid); + return; + } + + if (format == JLIST_CHANGED_ONLY) + { + if (IS_NOTIFIED (job_index)) + return; + format = JLIST_STANDARD; + } + + if (format != JLIST_NONINTERACTIVE) + fprintf (stream, "[%d]%c ", job_index + 1, + (job_index == current_job) ? '+': + (job_index == previous_job) ? '-' : ' '); + + if (format == JLIST_NONINTERACTIVE) + format = JLIST_LONG; + + p = jobs[job_index]->pipe; + + print_pipeline (p, job_index, format, stream); + + /* We have printed information about this job. When the job's + status changes, waitchld () sets the notification flag to 0. */ + jobs[job_index]->flags |= J_NOTIFIED; +} + +static int +print_job (job, format, state, job_index) + JOB *job; + int format, state, job_index; +{ + if (state == -1 || (JOB_STATE)state == job->state) + pretty_print_job (job_index, format, stdout); + return (0); +} + +void +list_one_job (job, format, ignore, job_index) + JOB *job; + int format, ignore, job_index; +{ + pretty_print_job (job_index, format, stdout); +} + +void +list_stopped_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, (int)JSTOPPED); +} + +void +list_running_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, (int)JRUNNING); +} + +/* List jobs. If FORMAT is non-zero, then the long form of the information + is printed, else just a short version. */ +void +list_all_jobs (format) + int format; +{ + cleanup_dead_jobs (); + map_over_jobs (print_job, format, -1); +} + +/* Fork, handling errors. Returns the pid of the newly made child, or 0. + COMMAND is just for remembering the name of the command; we don't do + anything else with it. ASYNC_P says what to do with the tty. If + non-zero, then don't give it away. */ +pid_t +make_child (command, async_p) + char *command; + int async_p; +{ + sigset_t set, oset; + pid_t pid; + + sigemptyset (&set); + sigaddset (&set, SIGCHLD); + sigaddset (&set, SIGINT); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + + making_children (); + +#if defined (BUFFERED_INPUT) + /* If default_buffered_input is active, we are reading a script. If + the command is asynchronous, we have already duplicated /dev/null + as fd 0, but have not changed the buffered stream corresponding to + the old fd 0. We don't want to sync the stream in this case. */ + if (default_buffered_input != -1 && + (!async_p || default_buffered_input > 0)) + sync_buffered_stream (default_buffered_input); +#endif /* BUFFERED_INPUT */ + + /* Create the child, handle severe errors. */ + if ((pid = fork ()) < 0) + { + sys_error ("fork"); + + /* Kill all of the processes in the current pipeline. */ + terminate_current_pipeline (); + + /* Discard the current pipeline, if any. */ + if (the_pipeline) + kill_current_pipeline (); + + throw_to_top_level (); /* Reset signals, etc. */ + } + + if (pid == 0) + { + /* In the child. Give this child the right process group, set the + signals to the default state for a new process. */ + pid_t mypid; + + mypid = getpid (); +#if defined (BUFFERED_INPUT) + /* Close default_buffered_input if it's > 0. We don't close it if it's + 0 because that's the file descriptor used when redirecting input, + and it's wrong to close the file in that case. */ + unset_bash_input (0); +#endif /* BUFFERED_INPUT */ + + /* Restore top-level signal mask. */ + sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); + + if (job_control) + { + /* All processes in this pipeline belong in the same + process group. */ + + if (pipeline_pgrp == 0) /* This is the first child. */ + pipeline_pgrp = mypid; + + /* Check for running command in backquotes. */ + if (pipeline_pgrp == shell_pgrp) + ignore_tty_job_signals (); + else + default_tty_job_signals (); + + /* Set the process group before trying to mess with the terminal's + process group. This is mandated by POSIX. */ + /* This is in accordance with the Posix 1003.1 standard, + section B.7.2.4, which says that trying to set the terminal + process group with tcsetpgrp() to an unused pgrp value (like + this would have for the first child) is an error. Section + B.4.3.3, p. 237 also covers this, in the context of job control + shells. */ + if (setpgid (mypid, pipeline_pgrp) < 0) + sys_error ("child setpgid (%ld to %ld)", (long)mypid, (long)pipeline_pgrp); + +itrace("make_child: setpgid (pid = %d, pgrp = %d)", mypid, pipeline_pgrp); + /* By convention (and assumption above), if + pipeline_pgrp == shell_pgrp, we are making a child for + command substitution. + In this case, we don't want to give the terminal to the + shell's process group (we could be in the middle of a + pipeline, for example). */ + if (async_p == 0 && pipeline_pgrp != shell_pgrp) + give_terminal_to (pipeline_pgrp, 0); + +#if defined (PGRP_PIPE) + if (pipeline_pgrp == mypid) + pipe_read (pgrp_pipe); +#endif + } + else /* Without job control... */ + { + if (pipeline_pgrp == 0) + pipeline_pgrp = shell_pgrp; + + /* If these signals are set to SIG_DFL, we encounter the curious + situation of an interactive ^Z to a running process *working* + and stopping the process, but being unable to do anything with + that process to change its state. On the other hand, if they + are set to SIG_IGN, jobs started from scripts do not stop when + the shell running the script gets a SIGTSTP and stops. */ + + default_tty_job_signals (); + } + +#if defined (PGRP_PIPE) + /* Release the process group pipe, since our call to setpgid () + is done. The last call to pipe_close is done in stop_pipeline. */ + pipe_close (pgrp_pipe); +#endif /* PGRP_PIPE */ + + if (async_p) + last_asynchronous_pid = getpid (); + } + else + { + /* In the parent. Remember the pid of the child just created + as the proper pgrp if this is the first child. */ + + if (job_control) + { + if (pipeline_pgrp == 0) + { + pipeline_pgrp = pid; + /* Don't twiddle terminal pgrps in the parent! This is the bug, + not the good thing of twiddling them in the child! */ + /* give_terminal_to (pipeline_pgrp, 0); */ + } + /* This is done on the recommendation of the Rationale section of + the POSIX 1003.1 standard, where it discusses job control and + shells. It is done to avoid possible race conditions. (Ref. + 1003.1 Rationale, section B.4.3.3, page 236). */ + setpgid (pid, pipeline_pgrp); + } + else + { + if (pipeline_pgrp == 0) + pipeline_pgrp = shell_pgrp; + } + + /* Place all processes into the jobs array regardless of the + state of job_control. */ + add_process (command, pid); + + if (async_p) + last_asynchronous_pid = pid; + + last_made_pid = pid; + + /* Unblock SIGINT and SIGCHLD. */ + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + } + + return (pid); +} + +/* These two functions are called only in child processes. */ +void +ignore_tty_job_signals () +{ + set_signal_handler (SIGTSTP, SIG_IGN); + set_signal_handler (SIGTTIN, SIG_IGN); + set_signal_handler (SIGTTOU, SIG_IGN); +} + +void +default_tty_job_signals () +{ + set_signal_handler (SIGTSTP, SIG_DFL); + set_signal_handler (SIGTTIN, SIG_DFL); + set_signal_handler (SIGTTOU, SIG_DFL); +} + +/* When we end a job abnormally, or if we stop a job, we set the tty to the + state kept in here. When a job ends normally, we set the state in here + to the state of the tty. */ + +static TTYSTRUCT shell_tty_info; + +#if defined (NEW_TTY_DRIVER) +static struct tchars shell_tchars; +static struct ltchars shell_ltchars; +#endif /* NEW_TTY_DRIVER */ + +#if defined (NEW_TTY_DRIVER) && defined (DRAIN_OUTPUT) +/* Since the BSD tty driver does not allow us to change the tty modes + while simultaneously waiting for output to drain and preserving + typeahead, we have to drain the output ourselves before calling + ioctl. We cheat by finding the length of the output queue, and + using select to wait for an appropriate length of time. This is + a hack, and should be labeled as such (it's a hastily-adapted + mutation of a `usleep' implementation). It's only reason for + existing is the flaw in the BSD tty driver. */ + +static int ttspeeds[] = +{ + 0, 50, 75, 110, 134, 150, 200, 300, 600, 1200, + 1800, 2400, 4800, 9600, 19200, 38400 +}; + +static void +draino (fd, ospeed) + int fd, ospeed; +{ + register int delay = ttspeeds[ospeed]; + int n; + + if (!delay) + return; + + while ((ioctl (fd, TIOCOUTQ, &n) == 0) && n) + { + if (n > (delay / 100)) + { + struct timeval tv; + + n *= 10; /* 2 bits more for conservativeness. */ + tv.tv_sec = n / delay; + tv.tv_usec = ((n % delay) * 1000000) / delay; + select (fd, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv); + } + else + break; + } +} +#endif /* NEW_TTY_DRIVER && DRAIN_OUTPUT */ + +/* Return the fd from which we are actually getting input. */ +#define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr) + +/* Fill the contents of shell_tty_info with the current tty info. */ +int +get_tty_state () +{ + int tty; + + tty = input_tty (); + if (tty != -1) + { +#if defined (NEW_TTY_DRIVER) + ioctl (tty, TIOCGETP, &shell_tty_info); + ioctl (tty, TIOCGETC, &shell_tchars); + ioctl (tty, TIOCGLTC, &shell_ltchars); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) + ioctl (tty, TCGETA, &shell_tty_info); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) + if (tcgetattr (tty, &shell_tty_info) < 0) + { +#if 0 + /* Only print an error message if we're really interactive at + this time. */ + if (interactive) + sys_error ("[%ld: %d] tcgetattr", (long)getpid (), shell_level); +#endif + return -1; + } +#endif /* TERMIOS_TTY_DRIVER */ + if (check_window_size) + get_new_window_size (0); + } + return 0; +} + +/* Make the current tty use the state in shell_tty_info. */ +int +set_tty_state () +{ + int tty; + + tty = input_tty (); + if (tty != -1) + { +#if defined (NEW_TTY_DRIVER) +# if defined (DRAIN_OUTPUT) + draino (tty, shell_tty_info.sg_ospeed); +# endif /* DRAIN_OUTPUT */ + ioctl (tty, TIOCSETN, &shell_tty_info); + ioctl (tty, TIOCSETC, &shell_tchars); + ioctl (tty, TIOCSLTC, &shell_ltchars); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) + ioctl (tty, TCSETAW, &shell_tty_info); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) + if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0) + { + /* Only print an error message if we're really interactive at + this time. */ + if (interactive) + sys_error ("[%ld: %d] tcsetattr", (long)getpid (), shell_level); + return -1; + } +#endif /* TERMIOS_TTY_DRIVER */ + } + return 0; +} + +/* Given an index into the jobs array JOB, return the pid of the last + process in that job's pipeline. This is the one whose exit status + counts. Must be called with SIGCHLD blocked or queued. */ +static pid_t +find_last_pid (job, block) + int job; + int block; +{ + register PROCESS *p; + sigset_t set, oset; + + if (block) + BLOCK_CHILD (set, oset); + + p = jobs[job]->pipe; + while (p->next != jobs[job]->pipe) + p = p->next; + + if (block) + UNBLOCK_CHILD (oset); + + return (p->pid); +} + +/* Wait for a particular child of the shell to finish executing. + This low-level function prints an error message if PID is not + a child of this shell. It returns -1 if it fails, or whatever + wait_for returns otherwise. If the child is not found in the + jobs table, it returns 127. */ +int +wait_for_single_pid (pid) + pid_t pid; +{ + register PROCESS *child; + sigset_t set, oset; + int r, job; + + BLOCK_CHILD (set, oset); + child = find_pipeline (pid, 0, (int *)NULL); + UNBLOCK_CHILD (oset); + + if (child == 0) + { + internal_error (_("wait: pid %ld is not a child of this shell"), (long)pid); + return (127); + } + + r = wait_for (pid); + + /* POSIX.2: if we just waited for a job, we can remove it from the jobs + table. */ + BLOCK_CHILD (set, oset); + job = find_job (pid, 0); + if (job != NO_JOB && jobs[job] && DEADJOB (job)) + jobs[job]->flags |= J_NOTIFIED; + UNBLOCK_CHILD (oset); + + return r; +} + +/* Wait for all of the backgrounds of this shell to finish. */ +void +wait_for_background_pids () +{ + register int i, r, waited_for; + sigset_t set, oset; + pid_t pid; + + for (waited_for = 0;;) + { + BLOCK_CHILD (set, oset); + + /* find first running job; if none running in foreground, break */ + for (i = 0; i < job_slots; i++) + if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0) + break; + + if (i == job_slots) + { + UNBLOCK_CHILD (oset); + break; + } + + /* now wait for the last pid in that job. */ + pid = find_last_pid (i, 0); + UNBLOCK_CHILD (oset); + QUIT; + errno = 0; /* XXX */ + r = wait_for_single_pid (pid); + if (r == -1) + { + /* If we're mistaken about job state, compensate. */ + if (errno == ECHILD) + mark_all_jobs_as_dead (); + } + else + waited_for++; + } + + /* POSIX.2 says the shell can discard the statuses of all completed jobs if + `wait' is called with no arguments. */ + mark_dead_jobs_as_notified (1); + cleanup_dead_jobs (); +} + +/* Make OLD_SIGINT_HANDLER the SIGINT signal handler. */ +#define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pids +static SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER; + +static void +restore_sigint_handler () +{ + if (old_sigint_handler != INVALID_SIGNAL_HANDLER) + { + set_signal_handler (SIGINT, old_sigint_handler); + old_sigint_handler = INVALID_SIGNAL_HANDLER; + } +} + +static int wait_sigint_received; + +/* Handle SIGINT while we are waiting for children in a script to exit. + The `wait' builtin should be interruptible, but all others should be + effectively ignored (i.e. not cause the shell to exit). */ +static sighandler +wait_sigint_handler (sig) + int sig; +{ + SigHandler *sigint_handler; + + if (interrupt_immediately || + (this_shell_builtin && this_shell_builtin == wait_builtin)) + { + last_command_exit_value = EXECUTION_FAILURE; + restore_sigint_handler (); + /* If we got a SIGINT while in `wait', and SIGINT is trapped, do + what POSIX.2 says (see builtins/wait.def for more info). */ + if (this_shell_builtin && this_shell_builtin == wait_builtin && + signal_is_trapped (SIGINT) && + ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler)) + { + interrupt_immediately = 0; + trap_handler (SIGINT); /* set pending_traps[SIGINT] */ + wait_signal_received = SIGINT; + longjmp (wait_intr_buf, 1); + } + + ADDINTERRUPT; + QUIT; + } + + /* XXX - should this be interrupt_state? If it is, the shell will act + as if it got the SIGINT interrupt. */ + wait_sigint_received = 1; + + /* Otherwise effectively ignore the SIGINT and allow the running job to + be killed. */ + SIGRETURN (0); +} + +static int +process_exit_signal (status) + WAIT status; +{ + return (WIFSIGNALED (status) ? WTERMSIG (status) : 0); +} + +static int +process_exit_status (status) + WAIT status; +{ + if (WIFSIGNALED (status)) + return (128 + WTERMSIG (status)); + else if (WIFSTOPPED (status) == 0) + return (WEXITSTATUS (status)); + else + return (EXECUTION_SUCCESS); +} + +/* Return the exit status of the last process in the pipeline for job JOB. + This is the exit status of the entire job. */ +static WAIT +raw_job_exit_status (job) + int job; +{ + register PROCESS *p; + int fail; + + if (pipefail_opt) + { + fail = 0; + for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next) + if (p->status != EXECUTION_SUCCESS) fail = p->status; + return fail; + } + + for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next) + ; + return (p->status); +} + +/* Return the exit status of job JOB. This is the exit status of the last + (rightmost) process in the job's pipeline, modified if the job was killed + by a signal or stopped. */ +static int +job_exit_status (job) + int job; +{ + return (process_exit_status (raw_job_exit_status (job))); +} + +static int +job_exit_signal (job) + int job; +{ + return (process_exit_signal (raw_job_exit_status (job))); +} + +#define FIND_CHILD(pid, child) \ + do \ + { \ + child = find_pipeline (pid, 0, (int *)NULL); \ + if (child == 0) \ + { \ + give_terminal_to (shell_pgrp, 0); \ + UNBLOCK_CHILD (oset); \ + internal_error (_("wait_for: No record of process %ld"), (long)pid); \ + restore_sigint_handler (); \ + return (termination_state = 127); \ + } \ + } \ + while (0) + +/* Wait for pid (one of our children) to terminate, then + return the termination state. Returns 127 if PID is not found in + the jobs table. Returns -1 if waitchld() returns -1, indicating + that there are no unwaited-for child processes. */ +int +wait_for (pid) + pid_t pid; +{ + int job, termination_state, r; + WAIT s; + register PROCESS *child; + sigset_t set, oset; + register PROCESS *p; + + /* In the case that this code is interrupted, and we longjmp () out of it, + we are relying on the code in throw_to_top_level () to restore the + top-level signal mask. */ + BLOCK_CHILD (set, oset); + + /* Ignore interrupts while waiting for a job run without job control + to finish. We don't want the shell to exit if an interrupt is + received, only if one of the jobs run is killed via SIGINT. If + job control is not set, the job will be run in the same pgrp as + the shell, and the shell will see any signals the job gets. */ + + /* This is possibly a race condition -- should it go in stop_pipeline? */ + wait_sigint_received = 0; + if (job_control == 0) + old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler); + + termination_state = last_command_exit_value; + + if (interactive && job_control == 0) + QUIT; + + /* If we say wait_for (), then we have a record of this child somewhere. + If it and none of its peers are running, don't call waitchld(). */ + + job = NO_JOB; + do + { + FIND_CHILD (pid, child); + + /* If this child is part of a job, then we are really waiting for the + job to finish. Otherwise, we are waiting for the child to finish. + We check for JDEAD in case the job state has been set by waitchld + after receipt of a SIGCHLD. */ + if (job == NO_JOB) + job = find_job (pid, 0); + + /* waitchld() takes care of setting the state of the job. If the job + has already exited before this is called, sigchld_handler will have + called waitchld and the state will be set to JDEAD. */ + + if (child->running || (job != NO_JOB && RUNNING (job))) + { +#if defined (WAITPID_BROKEN) /* SCOv4 */ + sigset_t suspend_set; + sigemptyset (&suspend_set); + sigsuspend (&suspend_set); +#else /* !WAITPID_BROKEN */ +# if defined (MUST_UNBLOCK_CHLD) + struct sigaction act, oact; + sigset_t nullset, chldset; + + sigemptyset (&nullset); + sigemptyset (&chldset); + sigprocmask (SIG_SETMASK, &nullset, &chldset); + act.sa_handler = SIG_DFL; + sigemptyset (&act.sa_mask); + sigemptyset (&oact.sa_mask); + act.sa_flags = 0; + sigaction (SIGCHLD, &act, &oact); +# endif + queue_sigchld = 1; + r = waitchld (pid, 1); +# if defined (MUST_UNBLOCK_CHLD) + sigaction (SIGCHLD, &oact, (struct sigaction *)NULL); + sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL); +# endif + queue_sigchld = 0; + if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin) + { + termination_state = -1; + goto wait_for_return; + } + + /* If child is marked as running, but waitpid() returns -1/ECHILD, + there is something wrong. Somewhere, wait should have returned + that child's pid. Mark the child as not running and the job, + if it exists, as JDEAD. */ + if (r == -1 && errno == ECHILD) + { + child->running = PS_DONE; + child->status = 0; /* XXX -- can't find true status */ + if (job != NO_JOB) + jobs[job]->state = JDEAD; + } +#endif /* WAITPID_BROKEN */ + } + + /* If the shell is interactive, and job control is disabled, see + if the foreground process has died due to SIGINT and jump out + of the wait loop if it has. waitchld has already restored the + old SIGINT signal handler. */ + if (interactive && job_control == 0) + QUIT; + } + while (child->running || (job != NO_JOB && RUNNING (job))); + + /* The exit state of the command is either the termination state of the + child, or the termination state of the job. If a job, the status + of the last child in the pipeline is the significant one. If the command + or job was terminated by a signal, note that value also. */ + termination_state = (job != NO_JOB) ? job_exit_status (job) + : process_exit_status (child->status); + last_command_exit_signal = (job != NO_JOB) ? job_exit_signal (job) + : process_exit_signal (child->status); + + if (job == NO_JOB || IS_JOBCONTROL (job)) + { + /* XXX - under what circumstances is a job not present in the jobs + table (job == NO_JOB)? + 1. command substitution + + In the case of command substitution, at least, it's probably not + the right thing to give the terminal to the shell's process group, + even though there is code in subst.c:command_substitute to work + around it. + + Things that don't: + $PROMPT_COMMAND execution + process substitution + */ +#if 0 +if (job == NO_JOB) + itrace("wait_for: job == NO_JOB, giving the terminal to shell_pgrp (%ld)", (long)shell_pgrp); +#endif + + give_terminal_to (shell_pgrp, 0); + } + + /* If the command did not exit cleanly, or the job is just + being stopped, then reset the tty state back to what it + was before this command. Reset the tty state and notify + the user of the job termination only if the shell is + interactive. Clean up any dead jobs in either case. */ + if (job != NO_JOB) + { + if (interactive_shell && subshell_environment == 0) + { + /* This used to use `child->status'. That's wrong, however, for + pipelines. `child' is the first process in the pipeline. It's + likely that the process we want to check for abnormal termination + or stopping is the last process in the pipeline, especially if + it's long-lived and the first process is short-lived. Since we + know we have a job here, we can check all the processes in this + job's pipeline and see if one of them stopped or terminated due + to a signal. We might want to change this later to just check + the last process in the pipeline. If no process exits due to a + signal, S is left as the status of the last job in the pipeline. */ + p = jobs[job]->pipe; + do + { + s = p->status; + if (WIFSIGNALED(s) || WIFSTOPPED(s)) + break; + p = p->next; + } + while (p != jobs[job]->pipe); + + if (WIFSIGNALED (s) || WIFSTOPPED (s)) + { + set_tty_state (); + + /* If the current job was stopped or killed by a signal, and + the user has requested it, get a possibly new window size */ + if (check_window_size && (job == current_job || IS_FOREGROUND (job))) + get_new_window_size (0); + } + else + get_tty_state (); + + /* If job control is enabled, the job was started with job + control, the job was the foreground job, and it was killed + by SIGINT, then print a newline to compensate for the kernel + printing the ^C without a trailing newline. */ + if (job_control && IS_JOBCONTROL (job) && IS_FOREGROUND (job) && + WIFSIGNALED (s) && WTERMSIG (s) == SIGINT) + { + /* If SIGINT is not trapped and the shell is in a for, while, + or until loop, act as if the shell received SIGINT as + well, so the loop can be broken. This doesn't call the + SIGINT signal handler; maybe it should. */ + if (signal_is_trapped (SIGINT) == 0 && loop_level) + ADDINTERRUPT; + else + { + putchar ('\n'); + fflush (stdout); + } + } + } + + /* Moved here from set_job_status_and_cleanup, which is in the SIGCHLD + signal handler path */ + if (DEADJOB (job) && IS_FOREGROUND (job) /*&& subshell_environment == 0*/) + setjstatus (job); + + /* If this job is dead, notify the user of the status. If the shell + is interactive, this will display a message on the terminal. If + the shell is not interactive, make sure we turn on the notify bit + so we don't get an unwanted message about the job's termination, + and so delete_job really clears the slot in the jobs table. */ + notify_and_cleanup (); + } + +wait_for_return: + + UNBLOCK_CHILD (oset); + + /* Restore the original SIGINT signal handler before we return. */ + restore_sigint_handler (); + + return (termination_state); +} + +/* Wait for the last process in the pipeline for JOB. Returns whatever + wait_for returns: the last process's termination state or -1 if there + are no unwaited-for child processes or an error occurs. */ +int +wait_for_job (job) + int job; +{ + pid_t pid; + int r; + sigset_t set, oset; + + BLOCK_CHILD(set, oset); + if (JOBSTATE (job) == JSTOPPED) + internal_warning (_("wait_for_job: job %d is stopped"), job+1); + + pid = find_last_pid (job, 0); + UNBLOCK_CHILD(oset); + r = wait_for (pid); + + /* POSIX.2: we can remove the job from the jobs table if we just waited + for it. */ + BLOCK_CHILD (set, oset); + if (job != NO_JOB && jobs[job] && DEADJOB (job)) + jobs[job]->flags |= J_NOTIFIED; + UNBLOCK_CHILD (oset); + + return r; +} + +/* Print info about dead jobs, and then delete them from the list + of known jobs. This does not actually delete jobs when the + shell is not interactive, because the dead jobs are not marked + as notified. */ +void +notify_and_cleanup () +{ + if (jobs_list_frozen) + return; + + if (interactive || interactive_shell == 0 || sourcelevel) + notify_of_job_status (); + + cleanup_dead_jobs (); +} + +/* Make dead jobs disappear from the jobs array without notification. + This is used when the shell is not interactive. */ +void +reap_dead_jobs () +{ + mark_dead_jobs_as_notified (0); + cleanup_dead_jobs (); +} + +/* Return the next closest (chronologically) job to JOB which is in + STATE. STATE can be JSTOPPED, JRUNNING. NO_JOB is returned if + there is no next recent job. */ +static int +most_recent_job_in_state (job, state) + int job; + JOB_STATE state; +{ + register int i, result; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + for (result = NO_JOB, i = job - 1; i >= 0; i--) + { + if (jobs[i] && (JOBSTATE (i) == state)) + { + result = i; + break; + } + } + + UNBLOCK_CHILD (oset); + + return (result); +} + +/* Return the newest *stopped* job older than JOB, or NO_JOB if not + found. */ +static int +job_last_stopped (job) + int job; +{ + return (most_recent_job_in_state (job, JSTOPPED)); +} + +/* Return the newest *running* job older than JOB, or NO_JOB if not + found. */ +static int +job_last_running (job) + int job; +{ + return (most_recent_job_in_state (job, JRUNNING)); +} + +/* Make JOB be the current job, and make previous be useful. Must be + called with SIGCHLD blocked. */ +static void +set_current_job (job) + int job; +{ + int candidate; + + if (current_job != job) + { + previous_job = current_job; + current_job = job; + } + + /* First choice for previous_job is the old current_job. */ + if (previous_job != current_job && + previous_job != NO_JOB && + jobs[previous_job] && + STOPPED (previous_job)) + return; + + /* Second choice: Newest stopped job that is older than + the current job. */ + candidate = NO_JOB; + if (STOPPED (current_job)) + { + candidate = job_last_stopped (current_job); + + if (candidate != NO_JOB) + { + previous_job = candidate; + return; + } + } + + /* If we get here, there is either only one stopped job, in which case it is + the current job and the previous job should be set to the newest running + job, or there are only running jobs and the previous job should be set to + the newest running job older than the current job. We decide on which + alternative to use based on whether or not JOBSTATE(current_job) is + JSTOPPED. */ + + candidate = RUNNING (current_job) ? job_last_running (current_job) + : job_last_running (job_slots); + + if (candidate != NO_JOB) + { + previous_job = candidate; + return; + } + + /* There is only a single job, and it is both `+' and `-'. */ + previous_job = current_job; +} + +/* Make current_job be something useful, if it isn't already. */ + +/* Here's the deal: The newest non-running job should be `+', and the + next-newest non-running job should be `-'. If there is only a single + stopped job, the previous_job is the newest non-running job. If there + are only running jobs, the newest running job is `+' and the + next-newest running job is `-'. Must be called with SIGCHLD blocked. */ + +static void +reset_current () +{ + int candidate; + + if (job_slots && current_job != NO_JOB && jobs[current_job] && STOPPED (current_job)) + candidate = current_job; + else + { + candidate = NO_JOB; + + /* First choice: the previous job. */ + if (previous_job != NO_JOB && jobs[previous_job] && STOPPED (previous_job)) + candidate = previous_job; + + /* Second choice: the most recently stopped job. */ + if (candidate == NO_JOB) + candidate = job_last_stopped (job_slots); + + /* Third choice: the newest running job. */ + if (candidate == NO_JOB) + candidate = job_last_running (job_slots); + } + + /* If we found a job to use, then use it. Otherwise, there + are no jobs period. */ + if (candidate != NO_JOB) + set_current_job (candidate); + else + current_job = previous_job = NO_JOB; +} + +/* Set up the job structures so we know the job and its processes are + all running. */ +static void +set_job_running (job) + int job; +{ + register PROCESS *p; + + /* Each member of the pipeline is now running. */ + p = jobs[job]->pipe; + + do + { + if (WIFSTOPPED (p->status)) + p->running = PS_RUNNING; /* XXX - could be PS_STOPPED */ + p = p->next; + } + while (p != jobs[job]->pipe); + + /* This means that the job is running. */ + JOBSTATE (job) = JRUNNING; +} + +/* Start a job. FOREGROUND if non-zero says to do that. Otherwise, + start the job in the background. JOB is a zero-based index into + JOBS. Returns -1 if it is unable to start a job, and the return + status of the job otherwise. */ +int +start_job (job, foreground) + int job, foreground; +{ + register PROCESS *p; + int already_running; + sigset_t set, oset; + char *wd; + static TTYSTRUCT save_stty; + + BLOCK_CHILD (set, oset); + + if (DEADJOB (job)) + { + internal_error (_("%s: job has terminated"), this_command_name); + UNBLOCK_CHILD (oset); + return (-1); + } + + already_running = RUNNING (job); + + if (foreground == 0 && already_running) + { + internal_error (_("%s: job %d already in background"), this_command_name, job + 1); + UNBLOCK_CHILD (oset); + return (-1); + } + + wd = current_working_directory (); + + /* You don't know about the state of this job. Do you? */ + jobs[job]->flags &= ~J_NOTIFIED; + + if (foreground) + { + set_current_job (job); + jobs[job]->flags |= J_FOREGROUND; + } + + /* Tell the outside world what we're doing. */ + p = jobs[job]->pipe; + + if (foreground == 0) + fprintf (stderr, "[%d]%c ", job + 1, + (job == current_job) ? '+': ((job == previous_job) ? '-' : ' ')); + + do + { + fprintf (stderr, "%s%s", + p->command ? p->command : "", + p->next != jobs[job]->pipe? " | " : ""); + p = p->next; + } + while (p != jobs[job]->pipe); + + if (foreground == 0) + fprintf (stderr, " &"); + + if (strcmp (wd, jobs[job]->wd) != 0) + fprintf (stderr, " (wd: %s)", polite_directory_format (jobs[job]->wd)); + + fprintf (stderr, "\n"); + + /* Run the job. */ + if (already_running == 0) + set_job_running (job); + + /* Save the tty settings before we start the job in the foreground. */ + if (foreground) + { + get_tty_state (); + save_stty = shell_tty_info; + /* Give the terminal to this job. */ + if (IS_JOBCONTROL (job)) + give_terminal_to (jobs[job]->pgrp, 0); + } + else + jobs[job]->flags &= ~J_FOREGROUND; + + /* If the job is already running, then don't bother jump-starting it. */ + if (already_running == 0) + { + jobs[job]->flags |= J_NOTIFIED; + killpg (jobs[job]->pgrp, SIGCONT); + } + + if (foreground) + { + pid_t pid; + int s; + + pid = find_last_pid (job, 0); + UNBLOCK_CHILD (oset); + s = wait_for (pid); + shell_tty_info = save_stty; + set_tty_state (); + return (s); + } + else + { + reset_current (); + UNBLOCK_CHILD (oset); + return (0); + } +} + +/* Give PID SIGNAL. This determines what job the pid belongs to (if any). + If PID does belong to a job, and the job is stopped, then CONTinue the + job after giving it SIGNAL. Returns -1 on failure. If GROUP is non-null, + then kill the process group associated with PID. */ +int +kill_pid (pid, sig, group) + pid_t pid; + int sig, group; +{ + register PROCESS *p; + int job, result; + sigset_t set, oset; + + result = EXECUTION_SUCCESS; + if (group) + { + BLOCK_CHILD (set, oset); + p = find_pipeline (pid, 0, &job); + + if (job != NO_JOB) + { + jobs[job]->flags &= ~J_NOTIFIED; + + /* Kill process in backquotes or one started without job control? */ + if (jobs[job]->pgrp == shell_pgrp) + { + p = jobs[job]->pipe; + + do + { + kill (p->pid, sig); + if (p->running == PS_DONE && (sig == SIGTERM || sig == SIGHUP)) + kill (p->pid, SIGCONT); + p = p->next; + } + while (p != jobs[job]->pipe); + } + else + { + result = killpg (jobs[job]->pgrp, sig); + if (p && STOPPED (job) && (sig == SIGTERM || sig == SIGHUP)) + killpg (jobs[job]->pgrp, SIGCONT); + /* If we're continuing a stopped job via kill rather than bg or + fg, emulate the `bg' behavior. */ + if (p && STOPPED (job) && (sig == SIGCONT)) + { + set_job_running (job); + jobs[job]->flags &= ~J_FOREGROUND; + jobs[job]->flags |= J_NOTIFIED; + } + } + } + else + result = killpg (pid, sig); + + UNBLOCK_CHILD (oset); + } + else + result = kill (pid, sig); + + return (result); +} + +/* sigchld_handler () flushes at least one of the children that we are + waiting for. It gets run when we have gotten a SIGCHLD signal. */ +static sighandler +sigchld_handler (sig) + int sig; +{ + int n, oerrno; + + oerrno = errno; + REINSTALL_SIGCHLD_HANDLER; + sigchld++; + n = 0; + if (queue_sigchld == 0) + n = waitchld (-1, 0); + errno = oerrno; + SIGRETURN (n); +} + +/* waitchld() reaps dead or stopped children. It's called by wait_for and + sigchld_handler, and runs until there aren't any children terminating any + more. + If BLOCK is 1, this is to be a blocking wait for a single child, although + an arriving SIGCHLD could cause the wait to be non-blocking. It returns + the number of children reaped, or -1 if there are no unwaited-for child + processes. */ +static int +waitchld (wpid, block) + pid_t wpid; + int block; +{ + WAIT status; + PROCESS *child; + pid_t pid; + int call_set_current, last_stopped_job, job, children_exited, waitpid_flags; + + call_set_current = children_exited = 0; + last_stopped_job = NO_JOB; + + do + { + /* We don't want to be notified about jobs stopping if job control + is not active. XXX - was interactive_shell instead of job_control */ + waitpid_flags = (job_control && subshell_environment == 0) + ? (WUNTRACED|WCONTINUED) + : 0; + if (sigchld || block == 0) + waitpid_flags |= WNOHANG; + pid = WAITPID (-1, &status, waitpid_flags); + + /* The check for WNOHANG is to make sure we decrement sigchld only + if it was non-zero before we called waitpid. */ + if (sigchld > 0 && (waitpid_flags & WNOHANG)) + sigchld--; + + /* If waitpid returns -1 with errno == ECHILD, there are no more + unwaited-for child processes of this shell. */ + if (pid < 0 && errno == ECHILD) + { + if (children_exited == 0) + return -1; + else + break; + } + + /* If waitpid returns 0, there are running children. If it returns -1, + the only other error POSIX says it can return is EINTR. */ + if (pid <= 0) + continue; /* jumps right to the test */ + + /* children_exited is used to run traps on SIGCHLD. We don't want to + run the trap if a process is just being continued. */ + if (WIFCONTINUED(status) == 0) + children_exited++; + + /* Locate our PROCESS for this pid. */ + child = find_pipeline (pid, 1, &job); /* want running procs only */ + + /* It is not an error to have a child terminate that we did + not have a record of. This child could have been part of + a pipeline in backquote substitution. Even so, I'm not + sure child is ever non-zero. */ + if (child == 0) + continue; + + while (child->pid != pid) + child = child->next; + + /* Remember status, and whether or not the process is running. */ + child->status = status; + child->running = WIFCONTINUED(status) ? PS_RUNNING : PS_DONE; + + if (job == NO_JOB) + continue; + + call_set_current += set_job_status_and_cleanup (job); + + if (STOPPED (job)) + last_stopped_job = job; + else if (DEADJOB (job) && last_stopped_job == job) + last_stopped_job = NO_JOB; + } + while ((sigchld || block == 0) && pid > (pid_t)0); + + /* If a job was running and became stopped, then set the current + job. Otherwise, don't change a thing. */ + if (call_set_current) + { + if (last_stopped_job != NO_JOB) + set_current_job (last_stopped_job); + else + reset_current (); + } + + /* Call a SIGCHLD trap handler for each child that exits, if one is set. */ + if (job_control && signal_is_trapped (SIGCHLD) && children_exited && + trap_list[SIGCHLD] != (char *)IGNORE_SIG) + run_sigchld_trap (children_exited); + + /* We have successfully recorded the useful information about this process + that has just changed state. If we notify asynchronously, and the job + that this process belongs to is no longer running, then notify the user + of that fact now. */ + if (asynchronous_notification && interactive) + notify_of_job_status (); + + return (children_exited); +} + +/* Set the status of JOB and perform any necessary cleanup if the job is + marked as JDEAD. + + Currently, the cleanup activity is restricted to handling any SIGINT + received while waiting for a foreground job to finish. */ +static int +set_job_status_and_cleanup (job) + int job; +{ + PROCESS *child; + int tstatus, job_state, any_stopped, any_tstped, call_set_current; + SigHandler *temp_handler; + + child = jobs[job]->pipe; + jobs[job]->flags &= ~J_NOTIFIED; + + call_set_current = 0; + + /* + * COMPUTE JOB STATUS + */ + + /* If all children are not running, but any of them is stopped, then + the job is stopped, not dead. */ + job_state = any_stopped = any_tstped = 0; + do + { + job_state |= child->running; + if (child->running == PS_DONE && (WIFSTOPPED (child->status))) + { + any_stopped = 1; + any_tstped |= interactive && job_control && + (WSTOPSIG (child->status) == SIGTSTP); + } + child = child->next; + } + while (child != jobs[job]->pipe); + + /* If job_state != 0, the job is still running, so don't bother with + setting the process exit status and job state unless we're + transitioning from stopped to running. */ + if (job_state != 0 && JOBSTATE(job) != JSTOPPED) + return 0; + + /* + * SET JOB STATUS + */ + + /* The job is either stopped or dead. Set the state of the job accordingly. */ + if (any_stopped) + { + jobs[job]->state = JSTOPPED; + jobs[job]->flags &= ~J_FOREGROUND; + call_set_current++; + /* Suspending a job with SIGTSTP breaks all active loops. */ + if (any_tstped && loop_level) + breaking = loop_level; + } + else if (job_state != 0) /* was stopped, now running */ + { + jobs[job]->state = JRUNNING; + call_set_current++; + } + else + { + jobs[job]->state = JDEAD; + +#if 0 + if (IS_FOREGROUND (job)) + setjstatus (job); +#endif + + /* If this job has a cleanup function associated with it, call it + with `cleanarg' as the single argument, then set the function + pointer to NULL so it is not inadvertently called twice. The + cleanup function is responsible for deallocating cleanarg. */ + if (jobs[job]->j_cleanup) + { + (*jobs[job]->j_cleanup) (jobs[job]->cleanarg); + jobs[job]->j_cleanup = (sh_vptrfunc_t *)NULL; + } + } + + /* + * CLEANUP + * + * Currently, we just do special things if we got a SIGINT while waiting + * for a foreground job to complete + */ + + if (jobs[job]->state == JDEAD) + { + /* If we're running a shell script and we get a SIGINT with a + SIGINT trap handler, but the foreground job handles it and + does not exit due to SIGINT, run the trap handler but do not + otherwise act as if we got the interrupt. */ + if (wait_sigint_received && interactive_shell == 0 && + WIFSIGNALED (child->status) == 0 && IS_FOREGROUND (job) && + signal_is_trapped (SIGINT)) + { + int old_frozen; + wait_sigint_received = 0; + last_command_exit_value = process_exit_status (child->status); + + old_frozen = jobs_list_frozen; + jobs_list_frozen = 1; + tstatus = maybe_call_trap_handler (SIGINT); + jobs_list_frozen = old_frozen; + } + + /* If the foreground job is killed by SIGINT when job control is not + active, we need to perform some special handling. + + The check of wait_sigint_received is a way to determine if the + SIGINT came from the keyboard (in which case the shell has already + seen it, and wait_sigint_received is non-zero, because keyboard + signals are sent to process groups) or via kill(2) to the foreground + process by another process (or itself). If the shell did receive the + SIGINT, it needs to perform normal SIGINT processing. */ + else if (wait_sigint_received && (WTERMSIG (child->status) == SIGINT) && + IS_FOREGROUND (job) && IS_JOBCONTROL (job) == 0) + { + int old_frozen; + + wait_sigint_received = 0; + + /* If SIGINT is trapped, set the exit status so that the trap + handler can see it. */ + if (signal_is_trapped (SIGINT)) + last_command_exit_value = process_exit_status (child->status); + + /* If the signal is trapped, let the trap handler get it no matter + what and simply return if the trap handler returns. + maybe_call_trap_handler() may cause dead jobs to be removed from + the job table because of a call to execute_command. We work + around this by setting JOBS_LIST_FROZEN. */ + old_frozen = jobs_list_frozen; + jobs_list_frozen = 1; + tstatus = maybe_call_trap_handler (SIGINT); + jobs_list_frozen = old_frozen; + if (tstatus == 0 && old_sigint_handler != INVALID_SIGNAL_HANDLER) + { + /* wait_sigint_handler () has already seen SIGINT and + allowed the wait builtin to jump out. We need to + call the original SIGINT handler, if necessary. If + the original handler is SIG_DFL, we need to resend + the signal to ourselves. */ + + temp_handler = old_sigint_handler; + + /* Bogus. If we've reset the signal handler as the result + of a trap caught on SIGINT, then old_sigint_handler + will point to trap_handler, which now knows nothing about + SIGINT (if we reset the sighandler to the default). + In this case, we have to fix things up. What a crock. */ + if (temp_handler == trap_handler && signal_is_trapped (SIGINT) == 0) + temp_handler = trap_to_sighandler (SIGINT); + restore_sigint_handler (); + if (temp_handler == SIG_DFL) + termination_unwind_protect (SIGINT); + else if (temp_handler != SIG_IGN) + (*temp_handler) (SIGINT); + } + } + } + + return call_set_current; +} + +/* Build the array of values for the $PIPESTATUS variable from the set of + exit statuses of all processes in the job J. */ +static void +setjstatus (j) + int j; +{ +#if defined (ARRAY_VARS) + register int i; + register PROCESS *p; + + for (i = 1, p = jobs[j]->pipe; p->next != jobs[j]->pipe; p = p->next, i++) + ; + i++; + if (statsize < i) + { + pstatuses = (int *)xrealloc (pstatuses, i * sizeof (int)); + statsize = i; + } + i = 0; + p = jobs[j]->pipe; + do + { + pstatuses[i++] = process_exit_status (p->status); + p = p->next; + } + while (p != jobs[j]->pipe); + + pstatuses[i] = -1; /* sentinel */ + set_pipestatus_array (pstatuses, i); +#endif +} + +static void +run_sigchld_trap (nchild) + int nchild; +{ + char *trap_command; + int i; + + /* Turn off the trap list during the call to parse_and_execute () + to avoid potentially infinite recursive calls. Preserve the + values of last_command_exit_value, last_made_pid, and the_pipeline + around the execution of the trap commands. */ + trap_command = savestring (trap_list[SIGCHLD]); + + begin_unwind_frame ("SIGCHLD trap"); + unwind_protect_int (last_command_exit_value); + unwind_protect_int (last_command_exit_signal); + unwind_protect_var (last_made_pid); + unwind_protect_int (interrupt_immediately); + unwind_protect_int (jobs_list_frozen); + unwind_protect_pointer (the_pipeline); + unwind_protect_pointer (subst_assign_varlist); + + /* We have to add the commands this way because they will be run + in reverse order of adding. We don't want maybe_set_sigchld_trap () + to reference freed memory. */ + add_unwind_protect (xfree, trap_command); + add_unwind_protect (maybe_set_sigchld_trap, trap_command); + + subst_assign_varlist = (WORD_LIST *)NULL; + the_pipeline = (PROCESS *)NULL; + + restore_default_signal (SIGCHLD); + jobs_list_frozen = 1; + for (i = 0; i < nchild; i++) + { + interrupt_immediately = 1; + parse_and_execute (savestring (trap_command), "trap", SEVAL_NOHIST|SEVAL_RESETLINE); + } + + run_unwind_frame ("SIGCHLD trap"); +} + +/* Function to call when you want to notify people of changes + in job status. This prints out all jobs which are pending + notification to stderr, and marks those printed as already + notified, thus making them candidates for cleanup. */ +static void +notify_of_job_status () +{ + register int job, termsig; + char *dir; + sigset_t set, oset; + WAIT s; + + if (jobs == 0 || job_slots == 0) + return; + + if (old_ttou != 0) + { + sigemptyset (&set); + sigaddset (&set, SIGCHLD); + sigaddset (&set, SIGTTOU); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + } + else + queue_sigchld++; + + for (job = 0, dir = (char *)NULL; job < job_slots; job++) + { + if (jobs[job] && IS_NOTIFIED (job) == 0) + { + s = raw_job_exit_status (job); + termsig = WTERMSIG (s); + + /* POSIX.2 says we have to hang onto the statuses of at most the + last CHILD_MAX background processes if the shell is running a + script. If the shell is not interactive, don't print anything + unless the job was killed by a signal. */ + if (startup_state == 0 && WIFSIGNALED (s) == 0 && + ((DEADJOB (job) && IS_FOREGROUND (job) == 0) || STOPPED (job))) + continue; + +#if 0 + /* If job control is disabled, don't print the status messages. + Mark dead jobs as notified so that they get cleaned up. If + startup_state == 2, we were started to run `-c command', so + don't print anything. */ + if ((job_control == 0 && interactive_shell) || startup_state == 2) +#else + /* If job control is disabled, don't print the status messages. + Mark dead jobs as notified so that they get cleaned up. If + startup_state == 2 and subshell_environment has the + SUBSHELL_COMSUB bit turned on, we were started to run a command + substitution, so don't print anything. */ + if ((job_control == 0 && interactive_shell) || + (startup_state == 2 && (subshell_environment & SUBSHELL_COMSUB))) +#endif + { + /* POSIX.2 compatibility: if the shell is not interactive, + hang onto the job corresponding to the last asynchronous + pid until the user has been notified of its status or does + a `wait'. */ + if (DEADJOB (job) && (interactive_shell || (find_last_pid (job, 0) != last_asynchronous_pid))) + jobs[job]->flags |= J_NOTIFIED; + continue; + } + + /* Print info on jobs that are running in the background, + and on foreground jobs that were killed by anything + except SIGINT (and possibly SIGPIPE). */ + switch (JOBSTATE (job)) + { + case JDEAD: + if (interactive_shell == 0 && termsig && WIFSIGNALED (s) && + termsig != SIGINT && +#if defined (DONT_REPORT_SIGPIPE) + termsig != SIGPIPE && +#endif + signal_is_trapped (termsig) == 0) + { + /* Don't print `0' for a line number. */ + fprintf (stderr, "%s: line %d: ", get_name_for_error (), (line_number == 0) ? 1 : line_number); + pretty_print_job (job, JLIST_NONINTERACTIVE, stderr); + } + else if (IS_FOREGROUND (job)) + { +#if !defined (DONT_REPORT_SIGPIPE) + if (termsig && WIFSIGNALED (s) && termsig != SIGINT) +#else + if (termsig && WIFSIGNALED (s) && termsig != SIGINT && termsig != SIGPIPE) +#endif + { + fprintf (stderr, "%s", j_strsignal (termsig)); + + if (WIFCORED (s)) + fprintf (stderr, " (core dumped)"); + + fprintf (stderr, "\n"); + } + } + else + { + if (dir == 0) + dir = current_working_directory (); + pretty_print_job (job, JLIST_STANDARD, stderr); + if (dir && strcmp (dir, jobs[job]->wd) != 0) + fprintf (stderr, + "(wd now: %s)\n", polite_directory_format (dir)); + } + + jobs[job]->flags |= J_NOTIFIED; + break; + + case JSTOPPED: + fprintf (stderr, "\n"); + if (dir == 0) + dir = current_working_directory (); + pretty_print_job (job, JLIST_STANDARD, stderr); + if (dir && (strcmp (dir, jobs[job]->wd) != 0)) + fprintf (stderr, + "(wd now: %s)\n", polite_directory_format (dir)); + jobs[job]->flags |= J_NOTIFIED; + break; + + case JRUNNING: + case JMIXED: + break; + + default: + programming_error ("notify_of_job_status"); + } + } + } + if (old_ttou != 0) + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + else + queue_sigchld--; +} + +/* Initialize the job control mechanism, and set up the tty stuff. */ +int +initialize_job_control (force) + int force; +{ + shell_pgrp = getpgid (0); + + if (shell_pgrp == -1) + { + sys_error ("initialize_job_control: getpgrp failed"); + exit (1); + } + + /* We can only have job control if we are interactive. */ + if (interactive == 0) + { + job_control = 0; + original_pgrp = NO_PID; + shell_tty = fileno (stderr); + } + else + { + /* Get our controlling terminal. If job_control is set, or + interactive is set, then this is an interactive shell no + matter where fd 2 is directed. */ + shell_tty = dup (fileno (stderr)); /* fd 2 */ + + shell_tty = move_to_high_fd (shell_tty, 1, -1); + + /* Compensate for a bug in systems that compiled the BSD + rlogind with DEBUG defined, like NeXT and Alliant. */ + if (shell_pgrp == 0) + { + shell_pgrp = getpid (); + setpgid (0, shell_pgrp); + tcsetpgrp (shell_tty, shell_pgrp); + } + + while ((terminal_pgrp = tcgetpgrp (shell_tty)) != -1) + { + if (shell_pgrp != terminal_pgrp) + { + SigHandler *ottin; + + ottin = set_signal_handler(SIGTTIN, SIG_DFL); + kill (0, SIGTTIN); + set_signal_handler (SIGTTIN, ottin); + continue; + } + break; + } + + /* Make sure that we are using the new line discipline. */ + if (set_new_line_discipline (shell_tty) < 0) + { + sys_error ("initialize_job_control: line discipline"); + job_control = 0; + } + else + { + original_pgrp = shell_pgrp; + shell_pgrp = getpid (); + + if ((original_pgrp != shell_pgrp) && (setpgid (0, shell_pgrp) < 0)) + { + sys_error ("initialize_job_control: setpgid"); + shell_pgrp = original_pgrp; + } + + job_control = 1; + + /* If (and only if) we just set our process group to our pid, + thereby becoming a process group leader, and the terminal + is not in the same process group as our (new) process group, + then set the terminal's process group to our (new) process + group. If that fails, set our process group back to what it + was originally (so we can still read from the terminal) and + turn off job control. */ + if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp) + { + if (give_terminal_to (shell_pgrp, 0) < 0) + { + setpgid (0, original_pgrp); + shell_pgrp = original_pgrp; + job_control = 0; + } + } + } + if (job_control == 0) + internal_error (_("no job control in this shell")); + } + + if (shell_tty != fileno (stderr)) + SET_CLOSE_ON_EXEC (shell_tty); + + set_signal_handler (SIGCHLD, sigchld_handler); + + change_flag ('m', job_control ? '-' : '+'); + + if (interactive) + get_tty_state (); + + return job_control; +} + +#ifdef DEBUG +void +debug_print_pgrps () +{ + itrace("original_pgrp = %ld shell_pgrp = %ld terminal_pgrp = %ld", + (long)original_pgrp, (long)shell_pgrp, (long)terminal_pgrp); + itrace("tcgetpgrp(%d) -> %ld, getpgid(0) -> %ld", + shell_tty, (long)tcgetpgrp (shell_tty), (long)getpgid(0)); +} +#endif + +/* Set the line discipline to the best this system has to offer. + Return -1 if this is not possible. */ +static int +set_new_line_discipline (tty) + int tty; +{ +#if defined (NEW_TTY_DRIVER) + int ldisc; + + if (ioctl (tty, TIOCGETD, &ldisc) < 0) + return (-1); + + if (ldisc != NTTYDISC) + { + ldisc = NTTYDISC; + + if (ioctl (tty, TIOCSETD, &ldisc) < 0) + return (-1); + } + return (0); +#endif /* NEW_TTY_DRIVER */ + +#if defined (TERMIO_TTY_DRIVER) +# if defined (TERMIO_LDISC) && (NTTYDISC) + if (ioctl (tty, TCGETA, &shell_tty_info) < 0) + return (-1); + + if (shell_tty_info.c_line != NTTYDISC) + { + shell_tty_info.c_line = NTTYDISC; + if (ioctl (tty, TCSETAW, &shell_tty_info) < 0) + return (-1); + } +# endif /* TERMIO_LDISC && NTTYDISC */ + return (0); +#endif /* TERMIO_TTY_DRIVER */ + +#if defined (TERMIOS_TTY_DRIVER) +# if defined (TERMIOS_LDISC) && defined (NTTYDISC) + if (tcgetattr (tty, &shell_tty_info) < 0) + return (-1); + + if (shell_tty_info.c_line != NTTYDISC) + { + shell_tty_info.c_line = NTTYDISC; + if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0) + return (-1); + } +# endif /* TERMIOS_LDISC && NTTYDISC */ + return (0); +#endif /* TERMIOS_TTY_DRIVER */ + +#if !defined (NEW_TTY_DRIVER) && !defined (TERMIO_TTY_DRIVER) && !defined (TERMIOS_TTY_DRIVER) + return (-1); +#endif +} + +#if defined (TIOCGWINSZ) && defined (SIGWINCH) +static void +get_new_window_size (from_sig) + int from_sig; +{ + struct winsize win; + + if ((ioctl (shell_tty, TIOCGWINSZ, &win) == 0) && + win.ws_row > 0 && win.ws_col > 0) + { +#if defined (aixpc) + shell_tty_info.c_winsize = win; /* structure copying */ +#endif + sh_set_lines_and_columns (win.ws_row, win.ws_col); +#if defined (READLINE) + rl_set_screen_size (win.ws_row, win.ws_col); +#endif + } +} + +static sighandler +sigwinch_sighandler (sig) + int sig; +{ +#if defined (MUST_REINSTALL_SIGHANDLERS) + set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif /* MUST_REINSTALL_SIGHANDLERS */ + get_new_window_size (1); + SIGRETURN (0); +} +#else +static void +get_new_window_size (from_sig) + int from_sig; +{ +} +#endif /* TIOCGWINSZ && SIGWINCH */ + +void +set_sigwinch_handler () +{ +#if defined (TIOCGWINSZ) && defined (SIGWINCH) + old_winch = set_signal_handler (SIGWINCH, sigwinch_sighandler); +#endif +} + +void +unset_sigwinch_handler () +{ +#if defined (TIOCGWINSZ) && defined (SIGWINCH) + set_signal_handler (SIGWINCH, old_winch); +#endif +} + +/* Setup this shell to handle C-C, etc. */ +void +initialize_job_signals () +{ + if (interactive) + { + set_signal_handler (SIGINT, sigint_sighandler); + set_signal_handler (SIGTSTP, SIG_IGN); + set_signal_handler (SIGTTOU, SIG_IGN); + set_signal_handler (SIGTTIN, SIG_IGN); + set_sigwinch_handler (); + } + else if (job_control) + { + old_tstp = set_signal_handler (SIGTSTP, sigstop_sighandler); + old_ttin = set_signal_handler (SIGTTIN, sigstop_sighandler); + old_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler); + } + /* Leave these things alone for non-interactive shells without job + control. */ +} + +/* Here we handle CONT signals. */ +static sighandler +sigcont_sighandler (sig) + int sig; +{ + initialize_job_signals (); + set_signal_handler (SIGCONT, old_cont); + kill (getpid (), SIGCONT); + + SIGRETURN (0); +} + +/* Here we handle stop signals while we are running not as a login shell. */ +static sighandler +sigstop_sighandler (sig) + int sig; +{ + set_signal_handler (SIGTSTP, old_tstp); + set_signal_handler (SIGTTOU, old_ttou); + set_signal_handler (SIGTTIN, old_ttin); + + old_cont = set_signal_handler (SIGCONT, sigcont_sighandler); + + give_terminal_to (shell_pgrp, 0); + + kill (getpid (), sig); + + SIGRETURN (0); +} + +/* Give the terminal to PGRP. */ +int +give_terminal_to (pgrp, force) + pid_t pgrp; + int force; +{ + sigset_t set, oset; + int r; + + r = 0; + if (job_control || force) + { + sigemptyset (&set); + sigaddset (&set, SIGTTOU); + sigaddset (&set, SIGTTIN); + sigaddset (&set, SIGTSTP); + sigaddset (&set, SIGCHLD); + sigemptyset (&oset); + sigprocmask (SIG_BLOCK, &set, &oset); + + if (tcsetpgrp (shell_tty, pgrp) < 0) + { + /* Maybe we should print an error message? */ +#if 0 + sys_error ("tcsetpgrp(%d) failed: pid %ld to pgrp %ld", + shell_tty, (long)getpid(), (long)pgrp); +#endif + r = -1; + } + else + terminal_pgrp = pgrp; + sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); + } + + return r; +} + +/* Clear out any jobs in the job array. This is intended to be used by + children of the shell, who should not have any job structures as baggage + when they start executing (forking subshells for parenthesized execution + and functions with pipes are the two that spring to mind). If RUNNING_ONLY + is nonzero, only running jobs are removed from the table. */ +void +delete_all_jobs (running_only) + int running_only; +{ + register int i; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + if (job_slots) + { + current_job = previous_job = NO_JOB; + + for (i = 0; i < job_slots; i++) + if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i)))) + delete_job (i, 1); + + if (running_only == 0) + { + free ((char *)jobs); + job_slots = 0; + } + } + + UNBLOCK_CHILD (oset); +} + +/* Mark all jobs in the job array so that they don't get a SIGHUP when the + shell gets one. If RUNNING_ONLY is nonzero, mark only running jobs. */ +void +nohup_all_jobs (running_only) + int running_only; +{ + register int i; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + + if (job_slots) + { + for (i = 0; i < job_slots; i++) + if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i)))) + nohup_job (i); + } + + UNBLOCK_CHILD (oset); +} + +int +count_all_jobs () +{ + int i, n; + sigset_t set, oset; + + BLOCK_CHILD (set, oset); + for (i = n = 0; i < job_slots; i++) + if (jobs[i] && DEADJOB(i) == 0) + n++; + UNBLOCK_CHILD (oset); + return n; +} + +static void +mark_all_jobs_as_dead () +{ + register int i; + sigset_t set, oset; + + if (job_slots == 0) + return; + + BLOCK_CHILD (set, oset); + + for (i = 0; i < job_slots; i++) + if (jobs[i]) + jobs[i]->state = JDEAD; + + UNBLOCK_CHILD (oset); +} + +/* Mark all dead jobs as notified, so delete_job () cleans them out + of the job table properly. POSIX.2 says we need to save the + status of the last CHILD_MAX jobs, so we count the number of dead + jobs and mark only enough as notified to save CHILD_MAX statuses. */ +static void +mark_dead_jobs_as_notified (force) + int force; +{ + register int i, ndead; + sigset_t set, oset; + + if (job_slots == 0) + return; + + BLOCK_CHILD (set, oset); + + /* If FORCE is non-zero, we don't have to keep CHILD_MAX statuses + around; just run through the array. */ + if (force) + { + for (i = 0; i < job_slots; i++) + { + if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid))) + jobs[i]->flags |= J_NOTIFIED; + } + UNBLOCK_CHILD (oset); + return; + } + + /* Mark enough dead jobs as notified to keep CHILD_MAX jobs left in the + array not marked as notified. */ + + /* Count the number of dead jobs */ + for (i = ndead = 0; i < job_slots; i++) + { + if (jobs[i] && DEADJOB (i)) + ndead++; + } + + if (child_max < 0) + child_max = getmaxchild (); + if (child_max < 0) + child_max = DEFAULT_CHILD_MAX; + + /* Don't do anything if the number of dead jobs is less than CHILD_MAX and + we're not forcing a cleanup. */ + if (ndead <= child_max) + { + UNBLOCK_CHILD (oset); + return; + } + + /* Mark enough dead jobs as notified that we keep CHILD_MAX jobs in + the list. This isn't exactly right yet; changes need to be made + to stop_pipeline so we don't mark the newer jobs after we've + created CHILD_MAX slots in the jobs array. */ + for (i = 0; i < job_slots; i++) + { + if (jobs[i] && DEADJOB (i) && (interactive_shell || (find_last_pid (i, 0) != last_asynchronous_pid))) + { + jobs[i]->flags |= J_NOTIFIED; + if (--ndead <= child_max) + break; + } + } + + UNBLOCK_CHILD (oset); +} + +/* Here to allow other parts of the shell (like the trap stuff) to + unfreeze the jobs list. */ +void +unfreeze_jobs_list () +{ + jobs_list_frozen = 0; +} + +/* Allow or disallow job control to take place. Returns the old value + of job_control. */ +int +set_job_control (arg) + int arg; +{ + int old; + + old = job_control; + job_control = arg; + + /* If we're turning on job control, reset pipeline_pgrp so make_child will + put new child processes into the right pgrp */ + if (job_control != old && job_control) + pipeline_pgrp = 0; + + return (old); +} + +/* Turn off all traces of job control. This is run by children of the shell + which are going to do shellsy things, like wait (), etc. */ +void +without_job_control () +{ + stop_making_children (); + start_pipeline (); + delete_all_jobs (0); + set_job_control (0); +} + +/* If this shell is interactive, terminate all stopped jobs and + restore the original terminal process group. This is done + before the `exec' builtin calls shell_execve. */ +void +end_job_control () +{ + if (interactive_shell) /* XXX - should it be interactive? */ + { + terminate_stopped_jobs (); + + if (original_pgrp >= 0) + give_terminal_to (original_pgrp, 1); + } + + if (original_pgrp >= 0) + setpgid (0, original_pgrp); +} + +/* Restart job control by closing shell tty and reinitializing. This is + called after an exec fails in an interactive shell and we do not exit. */ +void +restart_job_control () +{ + if (shell_tty != -1) + close (shell_tty); + initialize_job_control (0); +} + +/* Set the handler to run when the shell receives a SIGCHLD signal. */ +void +set_sigchld_handler () +{ + set_signal_handler (SIGCHLD, sigchld_handler); +} + +#if defined (PGRP_PIPE) +/* Read from the read end of a pipe. This is how the process group leader + blocks until all of the processes in a pipeline have been made. */ +static void +pipe_read (pp) + int *pp; +{ + char ch; + + if (pp[1] >= 0) + { + close (pp[1]); + pp[1] = -1; + } + + if (pp[0] >= 0) + { + while (read (pp[0], &ch, 1) == -1 && errno == EINTR) + ; + } +} + +/* Close the read and write ends of PP, an array of file descriptors. */ +static void +pipe_close (pp) + int *pp; +{ + if (pp[0] >= 0) + close (pp[0]); + + if (pp[1] >= 0) + close (pp[1]); + + pp[0] = pp[1] = -1; +} + +/* Functional interface closes our local-to-job-control pipes. */ +void +close_pgrp_pipe () +{ + pipe_close (pgrp_pipe); +} + +#endif /* PGRP_PIPE */ diff --git a/print_cmd.c b/print_cmd.c index f78f460..c8c7757 100644 --- a/print_cmd.c +++ b/print_cmd.c @@ -347,6 +347,37 @@ indirection_level_string () return (indirection_string); } +void +xtrace_print_assignment (name, value, assign_list, xflags) + char *name, *value; + int assign_list, xflags; +{ + char *nval; + + if (xflags) + fprintf (stderr, "%s", indirection_level_string ()); + + /* VALUE should not be NULL when this is called. */ + if (*value == '\0' || assign_list) + nval = value; + else if (sh_contains_shell_metas (value)) + nval = sh_single_quote (value); + else if (ansic_shouldquote (value)) + nval = ansic_quote (value, 0, (int *)0); + else + nval = value; + + if (assign_list) + fprintf (stderr, "%s=(%s)\n", name, nval); + else + fprintf (stderr, "%s=%s\n", name, nval); + + if (nval != value) + FREE (nval); + + fflush (stderr); +} + /* A function to print the words of a simple command when set -x is on. */ void xtrace_print_word_list (list, xtflags) diff --git a/print_cmd.c~ b/print_cmd.c~ new file mode 100644 index 0000000..e63c457 --- /dev/null +++ b/print_cmd.c~ @@ -0,0 +1,1282 @@ +/* print_command -- A way to make readable commands from a command tree. */ + +/* Copyright (C) 1989-2004 Free Software Foundation, Inc. + +This file is part of GNU Bash, the Bourne Again SHell. + +Bash is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2, or (at your option) any later +version. + +Bash is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License along +with Bash; see the file COPYING. If not, write to the Free Software +Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#if defined (PREFER_STDARG) +# include +#else +# include +#endif + +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "flags.h" +#include /* use <...> so we pick it up from the build directory */ +#include "builtins/common.h" + +#if !HAVE_DECL_PRINTF +extern int printf __P((const char *, ...)); /* Yuck. Double yuck. */ +#endif + +extern int indirection_level; + +static int indentation; +static int indentation_amount = 4; + +#if defined (PREFER_STDARG) +typedef void PFUNC __P((const char *, ...)); + +static void cprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); +static void xprintf __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); +#else +#define PFUNC VFunction +static void cprintf (); +static void xprintf (); +#endif + +static void reset_locals __P((void)); +static void newline __P((char *)); +static void indent __P((int)); +static void semicolon __P((void)); +static void the_printed_command_resize __P((int)); + +static void make_command_string_internal __P((COMMAND *)); +static void _print_word_list __P((WORD_LIST *, char *, PFUNC *)); +static void command_print_word_list __P((WORD_LIST *, char *)); +static void print_case_clauses __P((PATTERN_LIST *)); +static void print_redirection_list __P((REDIRECT *)); +static void print_redirection __P((REDIRECT *)); + +static void print_for_command __P((FOR_COM *)); +#if defined (ARITH_FOR_COMMAND) +static void print_arith_for_command __P((ARITH_FOR_COM *)); +#endif +#if defined (SELECT_COMMAND) +static void print_select_command __P((SELECT_COM *)); +#endif +static void print_group_command __P((GROUP_COM *)); +static void print_case_command __P((CASE_COM *)); +static void print_while_command __P((WHILE_COM *)); +static void print_until_command __P((WHILE_COM *)); +static void print_until_or_while __P((WHILE_COM *, char *)); +static void print_if_command __P((IF_COM *)); +#if defined (COND_COMMAND) +static void print_cond_node __P((COND_COM *)); +#endif +static void print_function_def __P((FUNCTION_DEF *)); + +#define PRINTED_COMMAND_INITIAL_SIZE 64 +#define PRINTED_COMMAND_GROW_SIZE 128 + +char *the_printed_command = (char *)NULL; +int the_printed_command_size = 0; +int command_string_index = 0; + +/* Non-zero means the stuff being printed is inside of a function def. */ +static int inside_function_def; +static int skip_this_indent; +static int was_heredoc; + +/* The depth of the group commands that we are currently printing. This + includes the group command that is a function body. */ +static int group_command_nesting; + +/* A buffer to indicate the indirection level (PS4) when set -x is enabled. */ +static char indirection_string[100]; + +/* Print COMMAND (a command tree) on standard output. */ +void +print_command (command) + COMMAND *command; +{ + command_string_index = 0; + printf ("%s", make_command_string (command)); +} + +/* Make a string which is the printed representation of the command + tree in COMMAND. We return this string. However, the string is + not consed, so you have to do that yourself if you want it to + remain around. */ +char * +make_command_string (command) + COMMAND *command; +{ + command_string_index = was_heredoc = 0; + make_command_string_internal (command); + return (the_printed_command); +} + +/* The internal function. This is the real workhorse. */ +static void +make_command_string_internal (command) + COMMAND *command; +{ + if (command == 0) + cprintf (""); + else + { + if (skip_this_indent) + skip_this_indent--; + else + indent (indentation); + + if (command->flags & CMD_TIME_PIPELINE) + { + cprintf ("time "); + if (command->flags & CMD_TIME_POSIX) + cprintf ("-p "); + } + + if (command->flags & CMD_INVERT_RETURN) + cprintf ("! "); + + switch (command->type) + { + case cm_for: + print_for_command (command->value.For); + break; + +#if defined (ARITH_FOR_COMMAND) + case cm_arith_for: + print_arith_for_command (command->value.ArithFor); + break; +#endif + +#if defined (SELECT_COMMAND) + case cm_select: + print_select_command (command->value.Select); + break; +#endif + + case cm_case: + print_case_command (command->value.Case); + break; + + case cm_while: + print_while_command (command->value.While); + break; + + case cm_until: + print_until_command (command->value.While); + break; + + case cm_if: + print_if_command (command->value.If); + break; + +#if defined (DPAREN_ARITHMETIC) + case cm_arith: + print_arith_command (command->value.Arith->exp); + break; +#endif + +#if defined (COND_COMMAND) + case cm_cond: + print_cond_command (command->value.Cond); + break; +#endif + + case cm_simple: + print_simple_command (command->value.Simple); + break; + + case cm_connection: + + skip_this_indent++; + make_command_string_internal (command->value.Connection->first); + + switch (command->value.Connection->connector) + { + case '&': + case '|': + { + char c = command->value.Connection->connector; + cprintf (" %c", c); + if (c != '&' || command->value.Connection->second) + { + cprintf (" "); + skip_this_indent++; + } + } + break; + + case AND_AND: + cprintf (" && "); + if (command->value.Connection->second) + skip_this_indent++; + break; + + case OR_OR: + cprintf (" || "); + if (command->value.Connection->second) + skip_this_indent++; + break; + + case ';': + if (was_heredoc == 0) + cprintf (";"); + else + was_heredoc = 0; + + if (inside_function_def) + cprintf ("\n"); + else + { + cprintf (" "); + if (command->value.Connection->second) + skip_this_indent++; + } + break; + + default: + cprintf (_("print_command: bad connector `%d'"), + command->value.Connection->connector); + break; + } + + make_command_string_internal (command->value.Connection->second); + break; + + case cm_function_def: + print_function_def (command->value.Function_def); + break; + + case cm_group: + print_group_command (command->value.Group); + break; + + case cm_subshell: + cprintf ("( "); + skip_this_indent++; + make_command_string_internal (command->value.Subshell->command); + cprintf (" )"); + break; + + default: + command_error ("print_command", CMDERR_BADTYPE, command->type, 0); + break; + } + + + if (command->redirects) + { + cprintf (" "); + print_redirection_list (command->redirects); + } + } +} + +static void +_print_word_list (list, separator, pfunc) + WORD_LIST *list; + char *separator; + PFUNC *pfunc; +{ + WORD_LIST *w; + + for (w = list; w; w = w->next) + (*pfunc) ("%s%s", w->word->word, w->next ? separator : ""); +} + +void +print_word_list (list, separator) + WORD_LIST *list; + char *separator; +{ + _print_word_list (list, separator, xprintf); +} + +/* Return a string denoting what our indirection level is. */ + +char * +indirection_level_string () +{ + register int i, j; + char *ps4; + + indirection_string[0] = '\0'; + ps4 = get_string_value ("PS4"); + + if (ps4 == 0 || *ps4 == '\0') + return (indirection_string); + + change_flag ('x', FLAG_OFF); + ps4 = decode_prompt_string (ps4); + change_flag ('x', FLAG_ON); + + if (ps4 == 0 || *ps4 == '\0') + return (indirection_string); + + for (i = 0; *ps4 && i < indirection_level && i < 99; i++) + indirection_string[i] = *ps4; + + for (j = 1; *ps4 && ps4[j] && i < 99; i++, j++) + indirection_string[i] = ps4[j]; + + indirection_string[i] = '\0'; + free (ps4); + return (indirection_string); +} + +void +xtrace_print_assignment (name, value, assign_list, xflags) + char *name, *value; + int assign_list, xflags; +{ + char *nval; + + if (xflags) + fprintf (stderr, "%s", indirection_level_string ()); + + /* VALUE should not be NULL when this is called. */ + if (*value == '\0 || assign_list) + nval = value; + else if (sh_contains_shell_metas (value)) + nval = sh_single_quote (value); + else if (ansic_shouldquote (value)) + nval = ansic_quote (value, 0, (int *)0); + else + nval = value; + + if (assign_list) + fprintf (stderr, "%s=(%s)\n", name, nval); + else + fprintf (stderr, "%s=%s\n", name, nval); + + if (nval != value) + FREE (nval); + + fflush (stderr); +} + +/* A function to print the words of a simple command when set -x is on. */ +void +xtrace_print_word_list (list, xtflags) + WORD_LIST *list; + int xtflags; +{ + WORD_LIST *w; + char *t, *x; + + if (xtflags) + fprintf (stderr, "%s", indirection_level_string ()); + + for (w = list; w; w = w->next) + { + t = w->word->word; + if (t == 0 || *t == '\0') + fprintf (stderr, "''%s", w->next ? " " : ""); + else if (sh_contains_shell_metas (t)) + { + x = sh_single_quote (t); + fprintf (stderr, "%s%s", x, w->next ? " " : ""); + free (x); + } + else if (ansic_shouldquote (t)) + { + x = ansic_quote (t, 0, (int *)0); + fprintf (stderr, "%s%s", x, w->next ? " " : ""); + free (x); + } + else + fprintf (stderr, "%s%s", t, w->next ? " " : ""); + } + fprintf (stderr, "\n"); +} + +static void +command_print_word_list (list, separator) + WORD_LIST *list; + char *separator; +{ + _print_word_list (list, separator, cprintf); +} + +void +print_for_command_head (for_command) + FOR_COM *for_command; +{ + cprintf ("for %s in ", for_command->name->word); + command_print_word_list (for_command->map_list, " "); +} + +void +xtrace_print_for_command_head (for_command) + FOR_COM *for_command; +{ + fprintf (stderr, "%s", indirection_level_string ()); + fprintf (stderr, "for %s in ", for_command->name->word); + xtrace_print_word_list (for_command->map_list, 0); +} + +static void +print_for_command (for_command) + FOR_COM *for_command; +{ + print_for_command_head (for_command); + + cprintf (";"); + newline ("do\n"); + indentation += indentation_amount; + make_command_string_internal (for_command->action); + semicolon (); + indentation -= indentation_amount; + newline ("done"); +} + +#if defined (ARITH_FOR_COMMAND) +static void +print_arith_for_command (arith_for_command) + ARITH_FOR_COM *arith_for_command; +{ + cprintf ("for (("); + command_print_word_list (arith_for_command->init, " "); + cprintf (" ; "); + command_print_word_list (arith_for_command->test, " "); + cprintf (" ; "); + command_print_word_list (arith_for_command->step, " "); + cprintf ("))"); + newline ("do\n"); + indentation += indentation_amount; + make_command_string_internal (arith_for_command->action); + semicolon (); + indentation -= indentation_amount; + newline ("done"); +} +#endif /* ARITH_FOR_COMMAND */ + +#if defined (SELECT_COMMAND) +void +print_select_command_head (select_command) + SELECT_COM *select_command; +{ + cprintf ("select %s in ", select_command->name->word); + command_print_word_list (select_command->map_list, " "); +} + +void +xtrace_print_select_command_head (select_command) + SELECT_COM *select_command; +{ + fprintf (stderr, "%s", indirection_level_string ()); + fprintf (stderr, "select %s in ", select_command->name->word); + xtrace_print_word_list (select_command->map_list, 0); +} + +static void +print_select_command (select_command) + SELECT_COM *select_command; +{ + print_select_command_head (select_command); + + cprintf (";"); + newline ("do\n"); + indentation += indentation_amount; + make_command_string_internal (select_command->action); + semicolon (); + indentation -= indentation_amount; + newline ("done"); +} +#endif /* SELECT_COMMAND */ + +static void +print_group_command (group_command) + GROUP_COM *group_command; +{ + group_command_nesting++; + cprintf ("{ "); + + if (inside_function_def == 0) + skip_this_indent++; + else + { + /* This is a group command { ... } inside of a function + definition, and should be printed as a multiline group + command, using the current indentation. */ + cprintf ("\n"); + indentation += indentation_amount; + } + + make_command_string_internal (group_command->command); + + if (inside_function_def) + { + cprintf ("\n"); + indentation -= indentation_amount; + indent (indentation); + } + else + { + semicolon (); + cprintf (" "); + } + + cprintf ("}"); + + group_command_nesting--; +} + +void +print_case_command_head (case_command) + CASE_COM *case_command; +{ + cprintf ("case %s in ", case_command->word->word); +} + +void +xtrace_print_case_command_head (case_command) + CASE_COM *case_command; +{ + fprintf (stderr, "%s", indirection_level_string ()); + fprintf (stderr, "case %s in\n", case_command->word->word); +} + +static void +print_case_command (case_command) + CASE_COM *case_command; +{ + print_case_command_head (case_command); + + if (case_command->clauses) + print_case_clauses (case_command->clauses); + newline ("esac"); +} + +static void +print_case_clauses (clauses) + PATTERN_LIST *clauses; +{ + indentation += indentation_amount; + while (clauses) + { + newline (""); + command_print_word_list (clauses->patterns, " | "); + cprintf (")\n"); + indentation += indentation_amount; + make_command_string_internal (clauses->action); + indentation -= indentation_amount; + newline (";;"); + clauses = clauses->next; + } + indentation -= indentation_amount; +} + +static void +print_while_command (while_command) + WHILE_COM *while_command; +{ + print_until_or_while (while_command, "while"); +} + +static void +print_until_command (while_command) + WHILE_COM *while_command; +{ + print_until_or_while (while_command, "until"); +} + +static void +print_until_or_while (while_command, which) + WHILE_COM *while_command; + char *which; +{ + cprintf ("%s ", which); + skip_this_indent++; + make_command_string_internal (while_command->test); + semicolon (); + cprintf (" do\n"); /* was newline ("do\n"); */ + indentation += indentation_amount; + make_command_string_internal (while_command->action); + indentation -= indentation_amount; + semicolon (); + newline ("done"); +} + +static void +print_if_command (if_command) + IF_COM *if_command; +{ + cprintf ("if "); + skip_this_indent++; + make_command_string_internal (if_command->test); + semicolon (); + cprintf (" then\n"); + indentation += indentation_amount; + make_command_string_internal (if_command->true_case); + indentation -= indentation_amount; + + if (if_command->false_case) + { + semicolon (); + newline ("else\n"); + indentation += indentation_amount; + make_command_string_internal (if_command->false_case); + indentation -= indentation_amount; + } + semicolon (); + newline ("fi"); +} + +#if defined (DPAREN_ARITHMETIC) +void +print_arith_command (arith_cmd_list) + WORD_LIST *arith_cmd_list; +{ + cprintf ("(("); + command_print_word_list (arith_cmd_list, " "); + cprintf ("))"); +} +#endif + +#if defined (COND_COMMAND) +static void +print_cond_node (cond) + COND_COM *cond; +{ + if (cond->flags & CMD_INVERT_RETURN) + cprintf ("! "); + + if (cond->type == COND_EXPR) + { + cprintf ("( "); + print_cond_node (cond->left); + cprintf (" )"); + } + else if (cond->type == COND_AND) + { + print_cond_node (cond->left); + cprintf (" && "); + print_cond_node (cond->right); + } + else if (cond->type == COND_OR) + { + print_cond_node (cond->left); + cprintf (" || "); + print_cond_node (cond->right); + } + else if (cond->type == COND_UNARY) + { + cprintf ("%s", cond->op->word); + cprintf (" "); + print_cond_node (cond->left); + } + else if (cond->type == COND_BINARY) + { + print_cond_node (cond->left); + cprintf (" "); + cprintf ("%s", cond->op->word); + cprintf (" "); + print_cond_node (cond->right); + } + else if (cond->type == COND_TERM) + { + cprintf ("%s", cond->op->word); /* need to add quoting here */ + } +} + +void +print_cond_command (cond) + COND_COM *cond; +{ + cprintf ("[[ "); + print_cond_node (cond); + cprintf (" ]]"); +} + +#ifdef DEBUG +void +debug_print_cond_command (cond) + COND_COM *cond; +{ + fprintf (stderr, "DEBUG: "); + command_string_index = 0; + print_cond_command (cond); + fprintf (stderr, "%s\n", the_printed_command); +} +#endif + +void +xtrace_print_cond_term (type, invert, op, arg1, arg2) + int type, invert; + WORD_DESC *op; + char *arg1, *arg2; +{ + command_string_index = 0; + fprintf (stderr, "%s", indirection_level_string ()); + fprintf (stderr, "[[ "); + if (invert) + fprintf (stderr, "! "); + + if (type == COND_UNARY) + { + fprintf (stderr, "%s ", op->word); + fprintf (stderr, "%s", (arg1 && *arg1) ? arg1 : "''"); + } + else if (type == COND_BINARY) + { + fprintf (stderr, "%s", (arg1 && *arg1) ? arg1 : "''"); + fprintf (stderr, " %s ", op->word); + fprintf (stderr, "%s", (arg2 && *arg2) ? arg2 : "''"); + } + + fprintf (stderr, " ]]\n"); +} +#endif /* COND_COMMAND */ + +#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) +/* A function to print the words of an arithmetic command when set -x is on. */ +void +xtrace_print_arith_cmd (list) + WORD_LIST *list; +{ + WORD_LIST *w; + + fprintf (stderr, "%s", indirection_level_string ()); + fprintf (stderr, "(( "); + for (w = list; w; w = w->next) + fprintf (stderr, "%s%s", w->word->word, w->next ? " " : ""); + fprintf (stderr, " ))\n"); +} +#endif + +void +print_simple_command (simple_command) + SIMPLE_COM *simple_command; +{ + command_print_word_list (simple_command->words, " "); + + if (simple_command->redirects) + { + cprintf (" "); + print_redirection_list (simple_command->redirects); + } +} + +static void +print_redirection_list (redirects) + REDIRECT *redirects; +{ + REDIRECT *heredocs, *hdtail, *newredir; + + heredocs = (REDIRECT *)NULL; + hdtail = heredocs; + + was_heredoc = 0; + while (redirects) + { + /* Defer printing the here documents until we've printed the + rest of the redirections. */ + if (redirects->instruction == r_reading_until || redirects->instruction == r_deblank_reading_until) + { + newredir = copy_redirect (redirects); + newredir->next = (REDIRECT *)NULL; + if (heredocs) + { + hdtail->next = newredir; + hdtail = newredir; + } + else + hdtail = heredocs = newredir; + } + else if (redirects->instruction == r_duplicating_output_word && redirects->redirector == 1) + { + /* Temporarily translate it as the execution code does. */ + redirects->instruction = r_err_and_out; + print_redirection (redirects); + redirects->instruction = r_duplicating_output_word; + } + else + print_redirection (redirects); + + redirects = redirects->next; + if (redirects) + cprintf (" "); + } + + /* Now that we've printed all the other redirections (on one line), + print the here documents. */ + if (heredocs) + { + cprintf (" "); + for (hdtail = heredocs; hdtail; hdtail = hdtail->next) + { + print_redirection (hdtail); + cprintf ("\n"); + } + dispose_redirects (heredocs); + was_heredoc = 1; + } +} + +static void +print_redirection (redirect) + REDIRECT *redirect; +{ + int kill_leading, redirector, redir_fd; + WORD_DESC *redirectee; + + kill_leading = 0; + redirectee = redirect->redirectee.filename; + redirector = redirect->redirector; + redir_fd = redirect->redirectee.dest; + + switch (redirect->instruction) + { + case r_output_direction: + if (redirector != 1) + cprintf ("%d", redirector); + cprintf (">%s", redirectee->word); + break; + + case r_input_direction: + if (redirector != 0) + cprintf ("%d", redirector); + cprintf ("<%s", redirectee->word); + break; + + case r_inputa_direction: /* Redirection created by the shell. */ + cprintf ("&"); + break; + + case r_appending_to: + if (redirector != 1) + cprintf ("%d", redirector); + cprintf (">>%s", redirectee->word); + break; + + case r_deblank_reading_until: + kill_leading++; + /* ... */ + case r_reading_until: + if (redirector != 0) + cprintf ("%d", redirector); + /* If the here document delimiter is quoted, single-quote it. */ + if (redirect->redirectee.filename->flags & W_QUOTED) + { + char *x; + x = sh_single_quote (redirect->here_doc_eof); + cprintf ("<<%s%s\n", kill_leading? "-" : "", x); + free (x); + } + else + cprintf ("<<%s%s\n", kill_leading? "-" : "", redirect->here_doc_eof); + cprintf ("%s%s", + redirect->redirectee.filename->word, redirect->here_doc_eof); + break; + + case r_reading_string: + if (redirector != 0) + cprintf ("%d", redirector); + if (ansic_shouldquote (redirect->redirectee.filename->word)) + { + char *x; + x = ansic_quote (redirect->redirectee.filename->word, 0, (int *)0); + cprintf ("<<< %s", x); + free (x); + } + else + cprintf ("<<< %s", redirect->redirectee.filename->word); + break; + + case r_duplicating_input: + cprintf ("%d<&%d", redirector, redir_fd); + break; + + case r_duplicating_output: + cprintf ("%d>&%d", redirector, redir_fd); + break; + + case r_duplicating_input_word: + cprintf ("%d<&%s", redirector, redirectee->word); + break; + + case r_duplicating_output_word: + cprintf ("%d>&%s", redirector, redirectee->word); + break; + + case r_move_input: + cprintf ("%d<&%d-", redirector, redir_fd); + break; + + case r_move_output: + cprintf ("%d>&%d-", redirector, redir_fd); + break; + + case r_move_input_word: + cprintf ("%d<&%s-", redirector, redirectee->word); + break; + + case r_move_output_word: + cprintf ("%d>&%s-", redirector, redirectee->word); + break; + + case r_close_this: + cprintf ("%d>&-", redirector); + break; + + case r_err_and_out: + cprintf (">&%s", redirectee->word); + break; + + case r_input_output: + if (redirector != 1) + cprintf ("%d", redirector); + cprintf ("<>%s", redirectee->word); + break; + + case r_output_force: + if (redirector != 1) + cprintf ("%d", redirector); + cprintf (">|%s", redirectee->word); + break; + } +} + +static void +reset_locals () +{ + inside_function_def = 0; + indentation = 0; +} + +static void +print_function_def (func) + FUNCTION_DEF *func; +{ + COMMAND *cmdcopy; + REDIRECT *func_redirects; + + func_redirects = NULL; + cprintf ("function %s () \n", func->name->word); + add_unwind_protect (reset_locals, 0); + + indent (indentation); + cprintf ("{ \n"); + + inside_function_def++; + indentation += indentation_amount; + + cmdcopy = copy_command (func->command); + if (cmdcopy->type == cm_group) + { + func_redirects = cmdcopy->redirects; + cmdcopy->redirects = (REDIRECT *)NULL; + } + make_command_string_internal (cmdcopy->type == cm_group + ? cmdcopy->value.Group->command + : cmdcopy); + + remove_unwind_protect (); + indentation -= indentation_amount; + inside_function_def--; + + if (func_redirects) + { /* { */ + newline ("} "); + print_redirection_list (func_redirects); + cmdcopy->redirects = func_redirects; + } + else + newline ("}"); + + dispose_command (cmdcopy); +} + +/* Return the string representation of the named function. + NAME is the name of the function. + COMMAND is the function body. It should be a GROUP_COM. + MULTI_LINE is non-zero to pretty-print, or zero for all on one line. + */ +char * +named_function_string (name, command, multi_line) + char *name; + COMMAND *command; + int multi_line; +{ + char *result; + int old_indent, old_amount; + COMMAND *cmdcopy; + REDIRECT *func_redirects; + + old_indent = indentation; + old_amount = indentation_amount; + command_string_index = was_heredoc = 0; + + if (name && *name) + cprintf ("%s ", name); + + cprintf ("() "); + + if (multi_line == 0) + { + indentation = 1; + indentation_amount = 0; + } + else + { + cprintf ("\n"); + indentation += indentation_amount; + } + + inside_function_def++; + + cprintf (multi_line ? "{ \n" : "{ "); + + cmdcopy = copy_command (command); + /* Take any redirections specified in the function definition (which should + apply to the function as a whole) and save them for printing later. */ + func_redirects = (REDIRECT *)NULL; + if (cmdcopy->type == cm_group) + { + func_redirects = cmdcopy->redirects; + cmdcopy->redirects = (REDIRECT *)NULL; + } + make_command_string_internal (cmdcopy->type == cm_group + ? cmdcopy->value.Group->command + : cmdcopy); + + indentation = old_indent; + indentation_amount = old_amount; + inside_function_def--; + + if (func_redirects) + { /* { */ + newline ("} "); + print_redirection_list (func_redirects); + cmdcopy->redirects = func_redirects; + } + else + newline ("}"); + + result = the_printed_command; + + if (!multi_line) + { +#if 0 + register int i; + for (i = 0; result[i]; i++) + if (result[i] == '\n') + { + strcpy (result + i, result + i + 1); + --i; + } +#else + if (result[2] == '\n') /* XXX -- experimental */ + strcpy (result + 2, result + 3); +#endif + } + + dispose_command (cmdcopy); + + return (result); +} + +static void +newline (string) + char *string; +{ + cprintf ("\n"); + indent (indentation); + if (string && *string) + cprintf ("%s", string); +} + +static char *indentation_string; +static int indentation_size; + +static void +indent (amount) + int amount; +{ + register int i; + + RESIZE_MALLOCED_BUFFER (indentation_string, 0, amount, indentation_size, 16); + + for (i = 0; amount > 0; amount--) + indentation_string[i++] = ' '; + indentation_string[i] = '\0'; + cprintf (indentation_string); +} + +static void +semicolon () +{ + if (command_string_index > 0 && + (the_printed_command[command_string_index - 1] == '&' || + the_printed_command[command_string_index - 1] == '\n')) + return; + cprintf (";"); +} + +/* How to make the string. */ +static void +#if defined (PREFER_STDARG) +cprintf (const char *control, ...) +#else +cprintf (control, va_alist) + const char *control; + va_dcl +#endif +{ + register const char *s; + char char_arg[2], *argp, intbuf[INT_STRLEN_BOUND (int) + 1]; + int digit_arg, arg_len, c; + va_list args; + + SH_VA_START (args, control); + + arg_len = strlen (control); + the_printed_command_resize (arg_len + 1); + + char_arg[1] = '\0'; + s = control; + while (s && *s) + { + c = *s++; + argp = (char *)NULL; + if (c != '%' || !*s) + { + char_arg[0] = c; + argp = char_arg; + arg_len = 1; + } + else + { + c = *s++; + switch (c) + { + case '%': + char_arg[0] = c; + argp = char_arg; + arg_len = 1; + break; + + case 's': + argp = va_arg (args, char *); + arg_len = strlen (argp); + break; + + case 'd': + /* Represent an out-of-range file descriptor with an out-of-range + integer value. We can do this because the only use of `%d' in + the calls to cprintf is to output a file descriptor number for + a redirection. */ + digit_arg = va_arg (args, int); + if (digit_arg < 0) + { + sprintf (intbuf, "%u", (unsigned)-1); + argp = intbuf; + } + else + argp = inttostr (digit_arg, intbuf, sizeof (intbuf)); + arg_len = strlen (argp); + break; + + case 'c': + char_arg[0] = va_arg (args, int); + argp = char_arg; + arg_len = 1; + break; + + default: + programming_error (_("cprintf: `%c': invalid format character"), c); + /*NOTREACHED*/ + } + } + + if (argp && arg_len) + { + the_printed_command_resize (arg_len + 1); + FASTCOPY (argp, the_printed_command + command_string_index, arg_len); + command_string_index += arg_len; + } + } + + the_printed_command[command_string_index] = '\0'; +} + +/* Ensure that there is enough space to stuff LENGTH characters into + THE_PRINTED_COMMAND. */ +static void +the_printed_command_resize (length) + int length; +{ + if (the_printed_command == 0) + { + the_printed_command_size = (length + PRINTED_COMMAND_INITIAL_SIZE - 1) & ~(PRINTED_COMMAND_INITIAL_SIZE - 1); + the_printed_command = (char *)xmalloc (the_printed_command_size); + command_string_index = 0; + } + else if ((command_string_index + length) >= the_printed_command_size) + { + int new; + new = command_string_index + length + 1; + + /* Round up to the next multiple of PRINTED_COMMAND_GROW_SIZE. */ + new = (new + PRINTED_COMMAND_GROW_SIZE - 1) & ~(PRINTED_COMMAND_GROW_SIZE - 1); + the_printed_command_size = new; + + the_printed_command = (char *)xrealloc (the_printed_command, the_printed_command_size); + } +} + +#if defined (HAVE_VPRINTF) +/* ``If vprintf is available, you may assume that vfprintf and vsprintf are + also available.'' */ + +static void +#if defined (PREFER_STDARG) +xprintf (const char *format, ...) +#else +xprintf (format, va_alist) + const char *format; + va_dcl +#endif +{ + va_list args; + + SH_VA_START (args, format); + + vfprintf (stdout, format, args); + va_end (args); +} + +#else + +static void +xprintf (format, arg1, arg2, arg3, arg4, arg5) + const char *format; +{ + printf (format, arg1, arg2, arg3, arg4, arg5); +} + +#endif /* !HAVE_VPRINTF */ diff --git a/subst.c b/subst.c index 401c2ca..e33a1dd 100644 --- a/subst.c +++ b/subst.c @@ -1977,8 +1977,9 @@ do_assignment_internal (string, expand) SHELL_VAR *entry; #if defined (ARRAY_VARS) char *t; - int ni, assign_list = 0; + int ni; #endif + int assign_list = 0; offset = assignment (string, 0); name = savestring (string); @@ -2021,14 +2022,7 @@ do_assignment_internal (string, expand) } if (echo_command_at_execute) - { -#if defined (ARRAY_VARS) - if (assign_list) - fprintf (stderr, "%s%s=(%s)\n", indirection_level_string (), name, value); - else -#endif - fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value); - } + xtrace_print_assignment (name, value, assign_list, 1); #define ASSIGN_RETURN(r) do { FREE (value); free (name); return (r); } while (0) diff --git a/subst.c~ b/subst.c~ index 479df1a..4071350 100644 --- a/subst.c~ +++ b/subst.c~ @@ -259,7 +259,7 @@ static intmax_t parameter_brace_expand_length __P((char *)); static char *skiparith __P((char *, int)); static int verify_substring_values __P((char *, char *, int, intmax_t *, intmax_t *)); -static int get_var_and_type __P((char *, char *, SHELL_VAR **, char **)); +static int get_var_and_type __P((char *, char *, int, SHELL_VAR **, char **)); static char *mb_substring __P((char *, int, int)); static char *parameter_brace_substring __P((char *, char *, char *, int)); @@ -1977,8 +1977,9 @@ do_assignment_internal (string, expand) SHELL_VAR *entry; #if defined (ARRAY_VARS) char *t; - int ni, assign_list = 0; + int ni; #endif + int assign_list = 0; offset = assignment (string, 0); name = savestring (string); @@ -2022,12 +2023,14 @@ do_assignment_internal (string, expand) if (echo_command_at_execute) { -#if defined (ARRAY_VARS) +#if 0 if (assign_list) fprintf (stderr, "%s%s=(%s)\n", indirection_level_string (), name, value); else + fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value); +#else + xtrace_print_assignment (name, value, assign_list, 1); #endif - fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value); } #define ASSIGN_RETURN(r) do { FREE (value); free (name); return (r); } while (0) @@ -3530,7 +3533,7 @@ parameter_brace_remove_pattern (varname, value, patstr, rtype, quoted) this_command_name = varname; - vtype = get_var_and_type (varname, value, &v, &val); + vtype = get_var_and_type (varname, value, quoted, &v, &val); if (vtype == -1) return ((char *)NULL); @@ -4446,7 +4449,12 @@ parameter_brace_expand_word (name, var_is_special, quoted) if (legal_number (name, &arg_index)) { tt = get_dollar_var_value (arg_index); - temp = tt ? quote_escapes (tt) : (char *)NULL; + if (tt) + temp = (*tt && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (tt) + : quote_escapes (tt); + else + temp = (char *)NULL; FREE (tt); } else if (var_is_special) /* ${@} */ @@ -4465,7 +4473,9 @@ parameter_brace_expand_word (name, var_is_special, quoted) { temp = array_value (name, quoted, &atype); if (atype == 0 && temp) - temp = quote_escapes (temp); + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); } #endif else if (var = find_variable (name)) @@ -4479,7 +4489,9 @@ parameter_brace_expand_word (name, var_is_special, quoted) #endif if (temp) - temp = quote_escapes (temp); + temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))) + ? quote_string (temp) + : quote_escapes (temp); } else temp = (char *)NULL; @@ -4501,6 +4513,15 @@ parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, c char *temp, *t; t = parameter_brace_expand_word (name, var_is_special, quoted); + /* Have to dequote here if necessary */ + if (t) + { + temp = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + ? dequote_string (t) + : dequote_escapes (t); + free (t); + t = temp; + } chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at); if (t == 0) return (t); @@ -4895,8 +4916,9 @@ verify_substring_values (value, substr, vtype, e1p, e2p) characters in the value are quoted with CTLESC and takes appropriate steps. For convenience, *VALP is set to the dequoted VALUE. */ static int -get_var_and_type (varname, value, varp, valp) +get_var_and_type (varname, value, quoted, varp, valp) char *varname, *value; + int quoted; SHELL_VAR **varp; char **valp; { @@ -4943,7 +4965,21 @@ get_var_and_type (varname, value, varp, valp) } else #endif +#if 1 + { + if (value && vtype == VT_VARIABLE) + { + if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) + *valp = dequote_string (value); + else + *valp = dequote_escapes (value); + } + else + *valp = value; + } +#else *valp = (value && vtype == VT_VARIABLE) ? dequote_escapes (value) : value; +#endif return vtype; } @@ -5002,7 +5038,7 @@ parameter_brace_substring (varname, value, substr, quoted) this_command_name = varname; - vtype = get_var_and_type (varname, value, &v, &val); + vtype = get_var_and_type (varname, value, quoted, &v, &val); if (vtype == -1) return ((char *)NULL); @@ -5201,7 +5237,7 @@ parameter_brace_patsub (varname, value, patsub, quoted) this_command_name = varname; - vtype = get_var_and_type (varname, value, &v, &val); + vtype = get_var_and_type (varname, value, quoted, &v, &val); if (vtype == -1) return ((char *)NULL); @@ -5736,7 +5772,16 @@ param_expand (string, sindex, quoted, expanded_something, last_command_exit_value = EXECUTION_FAILURE; return (interactive_shell ? &expand_param_error : &expand_param_fatal); } +#if 1 + if (temp1) + temp = (*temp1 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp1) + : quote_escapes (temp1); + else + temp = (char *)NULL; +#else temp = temp1 ? quote_escapes (temp1) : (char *)NULL; +#endif break; /* $$ -- pid of the invoking shell. */ @@ -5827,13 +5872,10 @@ param_expand (string, sindex, quoted, expanded_something, string might need it (consider "\"$@\""), but we need some way to signal that the final split on the first character of $IFS should be done, even though QUOTED is 1. */ -if (list && list->next) - { if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) *quoted_dollar_at_p = 1; if (contains_dollar_at) *contains_dollar_at = 1; - } /* We want to separate the positional parameters with the first character of $IFS in case $IFS is something other than a space. @@ -5976,13 +6018,22 @@ comsub: { temp = array_reference (array_cell (var), 0); if (temp) - temp = quote_escapes (temp); + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : quote_escapes (temp); else if (unbound_vars_is_error) goto unbound_variable; } else #endif - temp = quote_escapes (value_cell (var)); + { + temp = value_cell (var); + + temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) + ? quote_string (temp) + : quote_escapes (temp); + } + free (temp1); goto return0; diff --git a/syntax.h b/syntax.h index 7af17ab..8bf1548 100644 --- a/syntax.h +++ b/syntax.h @@ -71,6 +71,8 @@ extern int sh_syntabsiz; #define shellbreak(c) (sh_syntaxtab[(unsigned char)(c)] & CSHBRK) #define shellquote(c) (sh_syntaxtab[(unsigned char)(c)] & CQUOTE) +#define shellxquote(c) (sh_syntaxtab[(unsigned char)(c)] & CXQUOTE) + #define issyntype(c, t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) != 0) #define notsyntype(c,t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) == 0) diff --git a/syntax.h~ b/syntax.h~ new file mode 100644 index 0000000..7af17ab --- /dev/null +++ b/syntax.h~ @@ -0,0 +1,100 @@ +/* syntax.h -- Syntax definitions for the shell */ + +/* Copyright (C) 2000 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 2, or (at your option) any later + version. + + Bash is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License along + with Bash; see the file COPYING. If not, write to the Free Software + Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#ifndef _SYNTAX_H_ +#define _SYNTAX_H_ + +/* Defines for use by mksyntax.c */ + +#define slashify_in_quotes "\\`$\"\n" +#define slashify_in_here_document "\\`$" + +#define shell_meta_chars "()<>;&|" +#define shell_break_chars "()<>;&| \t\n" + +#define shell_quote_chars "\"`'" + +#if defined (PROCESS_SUBSTITUTION) +# define shell_exp_chars "$<>" +#else +# define shell_exp_chars "$" +#endif + +#if defined (EXTENDED_GLOB) +# define ext_glob_chars "@*+?!" +#else +# define ext_glob_chars "" +#endif +#define shell_glob_chars "*?[]^" + +/* Defines shared by mksyntax.c and the rest of the shell code. */ + +/* Values for character flags in syntax tables */ + +#define CWORD 0x0000 /* nothing special; an ordinary character */ +#define CSHMETA 0x0001 /* shell meta character */ +#define CSHBRK 0x0002 /* shell break character */ +#define CBACKQ 0x0004 /* back quote */ +#define CQUOTE 0x0008 /* shell quote character */ +#define CSPECL 0x0010 /* special character that needs quoting */ +#define CEXP 0x0020 /* shell expansion character */ +#define CBSDQUOTE 0x0040 /* characters escaped by backslash in double quotes */ +#define CBSHDOC 0x0080 /* characters escaped by backslash in here doc */ +#define CGLOB 0x0100 /* globbing characters */ +#define CXGLOB 0x0200 /* extended globbing characters */ +#define CXQUOTE 0x0400 /* cquote + backslash */ +#define CSPECVAR 0x0800 /* single-character shell variable name */ +#define CSUBSTOP 0x1000 /* values of OP for ${word[:]OPstuff} */ + +/* Defines for use by the rest of the shell. */ +extern int sh_syntaxtab[]; +extern int sh_syntabsiz; + +#define shellmeta(c) (sh_syntaxtab[(unsigned char)(c)] & CSHMETA) +#define shellbreak(c) (sh_syntaxtab[(unsigned char)(c)] & CSHBRK) +#define shellquote(c) (sh_syntaxtab[(unsigned char)(c)] & CQUOTE) + +#define issyntype(c, t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) != 0) +#define notsyntype(c,t) ((sh_syntaxtab[(unsigned char)(c)] & (t)) == 0) + +#if defined (PROCESS_SUBSTITUTION) +# define shellexp(c) ((c) == '$' || (c) == '<' || (c) == '>') +#else +# define shellexp(c) ((c) == '$') +#endif + +#if defined (EXTENDED_GLOB) +# define PATTERN_CHAR(c) \ + ((c) == '@' || (c) == '*' || (c) == '+' || (c) == '?' || (c) == '!') +#else +# define PATTERN_CHAR(c) 0 +#endif + +#define GLOB_CHAR(c) \ + ((c) == '*' || (c) == '?' || (c) == '[' || (c) == ']' || (c) == '^') + +#define CTLESC '\001' +#define CTLNUL '\177' + +#if !defined (HAVE_ISBLANK) && !defined (isblank) +# define isblank(x) ((x) == ' ' || (x) == '\t') +#endif + +#endif /* _SYNTAX_H_ */ diff --git a/tests/RUN-ONE-TEST b/tests/RUN-ONE-TEST index 3efcf32..72ec06a 100755 --- a/tests/RUN-ONE-TEST +++ b/tests/RUN-ONE-TEST @@ -1,4 +1,4 @@ -BUILD_DIR=/usr/local/build/chet/bash/bash-current +BUILD_DIR=/usr/local/build/bash/bash-current THIS_SH=$BUILD_DIR/bash PATH=$PATH:$BUILD_DIR diff --git a/tests/errors.right b/tests/errors.right index b00101c..9054fd0 100644 --- a/tests/errors.right +++ b/tests/errors.right @@ -1,7 +1,7 @@ ./errors.tests: line 17: alias: -x: invalid option alias: usage: alias [-p] [name[=value] ... ] ./errors.tests: line 18: unalias: -x: invalid option -unalias: usage: unalias [-a] [name ...] +unalias: usage: unalias [-a] name [name ...] ./errors.tests: line 19: alias: hoowah: not found ./errors.tests: line 20: unalias: hoowah: not found ./errors.tests: line 23: `1': not a valid identifier diff --git a/tests/errors.right~ b/tests/errors.right~ new file mode 100644 index 0000000..b00101c --- /dev/null +++ b/tests/errors.right~ @@ -0,0 +1,100 @@ +./errors.tests: line 17: alias: -x: invalid option +alias: usage: alias [-p] [name[=value] ... ] +./errors.tests: line 18: unalias: -x: invalid option +unalias: usage: unalias [-a] [name ...] +./errors.tests: line 19: alias: hoowah: not found +./errors.tests: line 20: unalias: hoowah: not found +./errors.tests: line 23: `1': not a valid identifier +declare -fr func +./errors.tests: line 36: func: readonly function +./errors.tests: line 39: unset: -x: invalid option +unset: usage: unset [-f] [-v] [name ...] +./errors.tests: line 42: unset: func: cannot unset: readonly function +./errors.tests: line 45: declare: func: readonly function +./errors.tests: line 49: unset: XPATH: cannot unset: readonly variable +./errors.tests: line 52: unset: `/bin/sh': not a valid identifier +./errors.tests: line 55: unset: cannot simultaneously unset a function and a variable +./errors.tests: line 58: declare: -z: invalid option +declare: usage: declare [-afFirtx] [-p] [name[=value] ...] +./errors.tests: line 60: declare: `-z': not a valid identifier +./errors.tests: line 61: declare: `/bin/sh': not a valid identifier +./errors.tests: line 65: declare: cannot use `-f' to make functions +./errors.tests: line 68: exec: -i: invalid option +exec: usage: exec [-cl] [-a name] file [redirection ...] +./errors.tests: line 72: export: XPATH: not a function +./errors.tests: line 75: break: only meaningful in a `for', `while', or `until' loop +./errors.tests: line 76: continue: only meaningful in a `for', `while', or `until' loop +./errors.tests: line 79: shift: label: numeric argument required +./errors.tests: line 84: shift: too many arguments +./errors.tests: line 90: let: expression expected +./errors.tests: line 93: local: can only be used in a function +./errors.tests: line 96: logout: not login shell: use `exit' +./errors.tests: line 99: hash: notthere: not found +./errors.tests: line 102: hash: -v: invalid option +hash: usage: hash [-lr] [-p pathname] [-dt] [name ...] +./errors.tests: line 106: hash: hashing disabled +./errors.tests: line 109: export: `AA[4]': not a valid identifier +./errors.tests: line 110: readonly: `AA[4]': not a valid identifier +./errors.tests: line 113: [-2]: bad array subscript +./errors.tests: line 117: AA: readonly variable +./errors.tests: line 121: AA: readonly variable +./errors.tests: line 129: shift: 5: shift count out of range +./errors.tests: line 130: shift: -2: shift count out of range +./errors.tests: line 133: shopt: no_such_option: invalid shell option name +./errors.tests: line 134: shopt: no_such_option: invalid shell option name +./errors.tests: line 137: umask: 09: octal number out of range +./errors.tests: line 138: umask: `:': invalid symbolic mode character +./errors.tests: line 139: umask: `:': invalid symbolic mode operator +./errors.tests: line 142: umask: -i: invalid option +umask: usage: umask [-p] [-S] [mode] +./errors.tests: line 146: umask: `u': invalid symbolic mode character +./errors.tests: line 155: VAR: readonly variable +./errors.tests: line 158: declare: VAR: readonly variable +./errors.tests: line 159: declare: VAR: readonly variable +./errors.tests: line 161: declare: unset: not found +./errors.tests: line 164: VAR: readonly variable +./errors.tests: command substitution: line 168: syntax error: unexpected end of file +./errors.tests: command substitution: line 168: syntax error near unexpected token `done' +./errors.tests: command substitution: line 168: ` for z in 1 2 3; done ' +./errors.tests: line 171: cd: HOME not set +./errors.tests: line 172: cd: /tmp/xyz.bash: No such file or directory +./errors.tests: line 174: cd: OLDPWD not set +./errors.tests: line 175: cd: /bin/sh: Not a directory +./errors.tests: line 177: cd: /tmp/cd-notthere: No such file or directory +./errors.tests: line 180: .: filename argument required +.: usage: . filename [arguments] +./errors.tests: line 181: source: filename argument required +source: usage: source filename [arguments] +./errors.tests: line 184: .: -i: invalid option +.: usage: . filename [arguments] +./errors.tests: line 187: set: -q: invalid option +set: usage: set [--abefhkmnptuvxBCHP] [-o option] [arg ...] +./errors.tests: line 190: enable: sh: not a shell builtin +./errors.tests: line 190: enable: bash: not a shell builtin +./errors.tests: line 193: shopt: cannot set and unset shell options simultaneously +./errors.tests: line 196: read: var: invalid timeout specification +./errors.tests: line 199: read: `/bin/sh': not a valid identifier +./errors.tests: line 202: VAR: readonly variable +./errors.tests: line 205: readonly: -x: invalid option +readonly: usage: readonly [-af] [name[=value] ...] or readonly -p +./errors.tests: line 208: eval: -i: invalid option +eval: usage: eval [arg ...] +./errors.tests: line 209: command: -i: invalid option +command: usage: command [-pVv] command [arg ...] +./errors.tests: line 212: /bin/sh + 0: syntax error: operand expected (error token is "/bin/sh + 0") +./errors.tests: line 213: /bin/sh + 0: syntax error: operand expected (error token is "/bin/sh + 0") +./errors.tests: line 216: trap: NOSIG: invalid signal specification +./errors.tests: line 219: trap: -s: invalid option +trap: usage: trap [arg] [signal_spec ...] or trap -l +./errors.tests: line 225: return: can only `return' from a function or sourced script +./errors.tests: line 229: break: 0: loop count out of range +./errors.tests: line 233: continue: 0: loop count out of range +./errors.tests: line 238: builtin: bash: not a shell builtin +./errors.tests: line 242: bg: no job control +./errors.tests: line 243: fg: no job control +./errors.tests: line 246: kill: -s: option requires an argument +./errors.tests: line 248: kill: S: invalid signal specification +./errors.tests: line 250: kill: `': not a pid or valid job spec +kill: usage: kill [-s sigspec | -n signum | -sigspec] [pid | job]... or kill -l [sigspec] +./errors.tests: line 255: set: trackall: invalid option name +./errors.tests: line 262: `!!': not a valid identifier diff --git a/variables.c b/variables.c index c62c0d8..d99140b 100644 --- a/variables.c +++ b/variables.c @@ -2118,12 +2118,9 @@ assign_in_env (string) setifs (var); if (echo_command_at_execute) - { - /* The Korn shell prints the `+ ' in front of assignment statements, - so we do too. */ - fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value); - fflush (stderr); - } + /* The Korn shell prints the `+ ' in front of assignment statements, + so we do too. */ + xtrace_print_assignment (name, value, 0, 1); free (name); return 1; diff --git a/variables.c~ b/variables.c~ new file mode 100644 index 0000000..592602d --- /dev/null +++ b/variables.c~ @@ -0,0 +1,4092 @@ +/* variables.c -- Functions for hacking shell variables. */ + +/* Copyright (C) 1987-2004 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2, or (at your option) + any later version. + + Bash is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with Bash; see the file COPYING. If not, write to the Free + Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ + +#include "config.h" + +#include "bashtypes.h" +#include "posixstat.h" +#include "posixtime.h" + +#if defined (qnx) +# include +#endif + +#if defined (HAVE_UNISTD_H) +# include +#endif + +#include +#include "chartypes.h" +#include +#include "bashansi.h" +#include "bashintl.h" + +#include "shell.h" +#include "flags.h" +#include "execute_cmd.h" +#include "findcmd.h" +#include "mailcheck.h" +#include "input.h" +#include "hashcmd.h" +#include "pathexp.h" + +#include "builtins/getopt.h" +#include "builtins/common.h" + +#if defined (READLINE) +# include "bashline.h" +# include +#else +# include +#endif + +#if defined (HISTORY) +# include "bashhist.h" +# include +#endif /* HISTORY */ + +#if defined (PROGRAMMABLE_COMPLETION) +# include "pcomplete.h" +#endif + +#define TEMPENV_HASH_BUCKETS 4 /* must be power of two */ + +#define ifsname(s) ((s)[0] == 'I' && (s)[1] == 'F' && (s)[2] == 'S' && (s)[3] == '\0') + +/* Variables used here and defined in other files. */ +extern int posixly_correct; +extern int line_number; +extern int subshell_environment, indirection_level, subshell_level; +extern int build_version, patch_level; +extern int expanding_redir; +extern char *dist_version, *release_status; +extern char *shell_name; +extern char *primary_prompt, *secondary_prompt; +extern char *current_host_name; +extern sh_builtin_func_t *this_shell_builtin; +extern SHELL_VAR *this_shell_function; +extern char *the_printed_command_except_trap; +extern char *this_command_name; +extern char *command_execution_string; +extern time_t shell_start_time; + +#if defined (READLINE) +extern int perform_hostname_completion; +#endif + +/* The list of shell variables that the user has created at the global + scope, or that came from the environment. */ +VAR_CONTEXT *global_variables = (VAR_CONTEXT *)NULL; + +/* The current list of shell variables, including function scopes */ +VAR_CONTEXT *shell_variables = (VAR_CONTEXT *)NULL; + +/* The list of shell functions that the user has created, or that came from + the environment. */ +HASH_TABLE *shell_functions = (HASH_TABLE *)NULL; + +#if defined (DEBUGGER) +/* The table of shell function definitions that the user defined or that + came from the environment. */ +HASH_TABLE *shell_function_defs = (HASH_TABLE *)NULL; +#endif + +/* The current variable context. This is really a count of how deep into + executing functions we are. */ +int variable_context = 0; + +/* The set of shell assignments which are made only in the environment + for a single command. */ +HASH_TABLE *temporary_env = (HASH_TABLE *)NULL; + +/* Some funky variables which are known about specially. Here is where + "$*", "$1", and all the cruft is kept. */ +char *dollar_vars[10]; +WORD_LIST *rest_of_args = (WORD_LIST *)NULL; + +/* The value of $$. */ +pid_t dollar_dollar_pid; + +/* An array which is passed to commands as their environment. It is + manufactured from the union of the initial environment and the + shell variables that are marked for export. */ +char **export_env = (char **)NULL; +static int export_env_index; +static int export_env_size; + +/* Non-zero means that we have to remake EXPORT_ENV. */ +int array_needs_making = 1; + +/* The number of times BASH has been executed. This is set + by initialize_variables (). */ +int shell_level = 0; + +/* Some forward declarations. */ +static void set_machine_vars __P((void)); +static void set_home_var __P((void)); +static void set_shell_var __P((void)); +static char *get_bash_name __P((void)); +static void initialize_shell_level __P((void)); +static void uidset __P((void)); +#if defined (ARRAY_VARS) +static void make_vers_array __P((void)); +#endif + +static SHELL_VAR *null_assign __P((SHELL_VAR *, char *, arrayind_t)); +#if defined (ARRAY_VARS) +static SHELL_VAR *null_array_assign __P((SHELL_VAR *, char *, arrayind_t)); +#endif +static SHELL_VAR *get_self __P((SHELL_VAR *)); + +#if defined (ARRAY_VARS) +static SHELL_VAR *init_dynamic_array_var __P((char *, sh_var_value_func_t *, sh_var_assign_func_t *, int)); +#endif + +static SHELL_VAR *assign_seconds __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_seconds __P((SHELL_VAR *)); +static SHELL_VAR *init_seconds_var __P((void)); + +static int brand __P((void)); +static void sbrand __P((unsigned long)); /* set bash random number generator. */ +static SHELL_VAR *assign_random __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_random __P((SHELL_VAR *)); + +static SHELL_VAR *assign_lineno __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_lineno __P((SHELL_VAR *)); + +static SHELL_VAR *assign_subshell __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_subshell __P((SHELL_VAR *)); + +#if defined (HISTORY) +static SHELL_VAR *get_histcmd __P((SHELL_VAR *)); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR *assign_dirstack __P((SHELL_VAR *, char *, arrayind_t)); +static SHELL_VAR *get_dirstack __P((SHELL_VAR *)); +#endif + +#if defined (ARRAY_VARS) +static SHELL_VAR *get_groupset __P((SHELL_VAR *)); +#endif + +static SHELL_VAR *get_funcname __P((SHELL_VAR *)); +static SHELL_VAR *init_funcname_var __P((void)); + +static void initialize_dynamic_variables __P((void)); + +static SHELL_VAR *hash_lookup __P((const char *, HASH_TABLE *)); +static SHELL_VAR *new_shell_variable __P((const char *)); +static SHELL_VAR *make_new_variable __P((const char *, HASH_TABLE *)); +static SHELL_VAR *bind_variable_internal __P((const char *, char *, HASH_TABLE *, int)); + +static void free_variable_hash_data __P((PTR_T)); + +static VARLIST *vlist_alloc __P((int)); +static VARLIST *vlist_realloc __P((VARLIST *, int)); +static void vlist_add __P((VARLIST *, SHELL_VAR *, int)); + +static void flatten __P((HASH_TABLE *, sh_var_map_func_t *, VARLIST *, int)); + +static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **)); + +static SHELL_VAR **vapply __P((sh_var_map_func_t *)); +static SHELL_VAR **fapply __P((sh_var_map_func_t *)); + +static int visible_var __P((SHELL_VAR *)); +static int visible_and_exported __P((SHELL_VAR *)); +static int local_and_exported __P((SHELL_VAR *)); +static int variable_in_context __P((SHELL_VAR *)); +#if defined (ARRAY_VARS) +static int visible_array_vars __P((SHELL_VAR *)); +#endif + +static SHELL_VAR *bind_tempenv_variable __P((const char *, char *)); +static void push_temp_var __P((PTR_T)); +static void propagate_temp_var __P((PTR_T)); +static void dispose_temporary_env __P((sh_free_func_t *)); + +static inline char *mk_env_string __P((const char *, const char *)); +static char **make_env_array_from_var_list __P((SHELL_VAR **)); +static char **make_var_export_array __P((VAR_CONTEXT *)); +static char **make_func_export_array __P((void)); +static void add_temp_array_to_env __P((char **, int, int)); + +static int n_shell_variables __P((void)); +static int set_context __P((SHELL_VAR *)); + +static void push_func_var __P((PTR_T)); +static void push_exported_var __P((PTR_T)); + +static inline int find_special_var __P((const char *)); + +/* Initialize the shell variables from the current environment. + If PRIVMODE is nonzero, don't import functions from ENV or + parse $SHELLOPTS. */ +void +initialize_shell_variables (env, privmode) + char **env; + int privmode; +{ + char *name, *string, *temp_string; + int c, char_index, string_index, string_length; + SHELL_VAR *temp_var; + + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (0); + } + + if (shell_functions == 0) + shell_functions = hash_create (0); + +#if defined (DEBUGGER) + if (shell_function_defs == 0) + shell_function_defs = hash_create (0); +#endif + + for (string_index = 0; string = env[string_index++]; ) + { + char_index = 0; + name = string; + while ((c = *string++) && c != '=') + ; + if (string[-1] == '=') + char_index = string - name - 1; + + /* If there are weird things in the environment, like `=xxx' or a + string without an `=', just skip them. */ + if (char_index == 0) + continue; + + /* ASSERT(name[char_index] == '=') */ + name[char_index] = '\0'; + /* Now, name = env variable name, string = env variable value, and + char_index == strlen (name) */ + + /* If exported function, define it now. Don't import functions from + the environment in privileged mode. */ + if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4)) + { + string_length = strlen (string); + temp_string = (char *)xmalloc (3 + string_length + char_index); + + strcpy (temp_string, name); + temp_string[char_index] = ' '; + strcpy (temp_string + char_index + 1, string); + + parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST); + + /* Ancient backwards compatibility. Old versions of bash exported + functions like name()=() {...} */ + if (name[char_index - 1] == ')' && name[char_index - 2] == '(') + name[char_index - 2] = '\0'; + + if (temp_var = find_function (name)) + { + VSETATTR (temp_var, (att_exported|att_imported)); + array_needs_making = 1; + } + else + report_error (_("error importing function definition for `%s'"), name); + + /* ( */ + if (name[char_index - 1] == ')' && name[char_index - 2] == '\0') + name[char_index - 2] = '('; /* ) */ + } +#if defined (ARRAY_VARS) +# if 0 + /* Array variables may not yet be exported. */ + else if (*string == '(' && string[1] == '[' && xstrchr (string, ')')) + { + string_length = 1; + temp_string = extract_array_assignment_list (string, &string_length); + temp_var = assign_array_from_string (name, temp_string); + FREE (temp_string); + VSETATTR (temp_var, (att_exported | att_imported)); + array_needs_making = 1; + } +# endif +#endif + else + { + temp_var = bind_variable (name, string); + VSETATTR (temp_var, (att_exported | att_imported)); + array_needs_making = 1; + } + + name[char_index] = '='; + /* temp_var can be NULL if it was an exported function with a syntax + error (a different bug, but it still shouldn't dump core). */ + if (temp_var && function_p (temp_var) == 0) /* XXX not yet */ + { + CACHE_IMPORTSTR (temp_var, name); + } + } + + set_pwd (); + + /* Set up initial value of $_ */ + temp_var = bind_variable ("_", dollar_vars[0]); + + /* Remember this pid. */ + dollar_dollar_pid = getpid (); + + /* Now make our own defaults in case the vars that we think are + important are missing. */ + temp_var = set_if_not ("PATH", DEFAULT_PATH_VALUE); +#if 0 + set_auto_export (temp_var); /* XXX */ +#endif + + temp_var = set_if_not ("TERM", "dumb"); +#if 0 + set_auto_export (temp_var); /* XXX */ +#endif + +#if defined (qnx) + /* set node id -- don't import it from the environment */ + { + char node_name[22]; + qnx_nidtostr (getnid (), node_name, sizeof (node_name)); + temp_var = bind_variable ("NODE", node_name); + set_auto_export (temp_var); + } +#endif + + /* set up the prompts. */ + if (interactive_shell) + { +#if defined (PROMPT_STRING_DECODE) + set_if_not ("PS1", primary_prompt); +#else + if (current_user.uid == -1) + get_current_user_info (); + set_if_not ("PS1", current_user.euid == 0 ? "# " : primary_prompt); +#endif + set_if_not ("PS2", secondary_prompt); + } + set_if_not ("PS4", "+ "); + + /* Don't allow IFS to be imported from the environment. */ + temp_var = bind_variable ("IFS", " \t\n"); + setifs (temp_var); + + /* Magic machine types. Pretty convenient. */ + set_machine_vars (); + + /* Default MAILCHECK for interactive shells. Defer the creation of a + default MAILPATH until the startup files are read, because MAIL + names a mail file if MAILPATH is not set, and we should provide a + default only if neither is set. */ + if (interactive_shell) + set_if_not ("MAILCHECK", posixly_correct ? "600" : "60"); + + /* Do some things with shell level. */ + initialize_shell_level (); + + set_ppid (); + + /* Initialize the `getopts' stuff. */ + bind_variable ("OPTIND", "1"); + getopts_reset (0); + bind_variable ("OPTERR", "1"); + sh_opterr = 1; + + if (login_shell == 1) + set_home_var (); + + /* Get the full pathname to THIS shell, and set the BASH variable + to it. */ + name = get_bash_name (); + temp_var = bind_variable ("BASH", name); + free (name); + + /* Make the exported environment variable SHELL be the user's login + shell. Note that the `tset' command looks at this variable + to determine what style of commands to output; if it ends in "csh", + then C-shell commands are output, else Bourne shell commands. */ + set_shell_var (); + + /* Make a variable called BASH_VERSION which contains the version info. */ + bind_variable ("BASH_VERSION", shell_version_string ()); +#if defined (ARRAY_VARS) + make_vers_array (); +#endif + + if (command_execution_string) + bind_variable ("BASH_EXECUTION_STRING", command_execution_string); + + /* Find out if we're supposed to be in Posix.2 mode via an + environment variable. */ + temp_var = find_variable ("POSIXLY_CORRECT"); + if (!temp_var) + temp_var = find_variable ("POSIX_PEDANTIC"); + if (temp_var && imported_p (temp_var)) + sv_strict_posix (temp_var->name); + +#if defined (HISTORY) + /* Set history variables to defaults, and then do whatever we would + do if the variable had just been set. Do this only in the case + that we are remembering commands on the history list. */ + if (remember_on_history) + { + name = bash_tilde_expand (posixly_correct ? "~/.sh_history" : "~/.bash_history", 0); + + set_if_not ("HISTFILE", name); + free (name); + + set_if_not ("HISTSIZE", "500"); + sv_histsize ("HISTSIZE"); + } +#endif /* HISTORY */ + + /* Seed the random number generator. */ + sbrand (dollar_dollar_pid + shell_start_time); + + /* Handle some "special" variables that we may have inherited from a + parent shell. */ + if (interactive_shell) + { + temp_var = find_variable ("IGNOREEOF"); + if (!temp_var) + temp_var = find_variable ("ignoreeof"); + if (temp_var && imported_p (temp_var)) + sv_ignoreeof (temp_var->name); + } + +#if defined (HISTORY) + if (interactive_shell && remember_on_history) + { + sv_history_control ("HISTCONTROL"); + sv_histignore ("HISTIGNORE"); + } +#endif /* HISTORY */ + + /* + * 24 October 2001 + * + * I'm tired of the arguing and bug reports. Bash now leaves SSH_CLIENT + * and SSH2_CLIENT alone. I'm going to rely on the shell_level check in + * isnetconn() to avoid running the startup files more often than wanted. + * That will, of course, only work if the user's login shell is bash, so + * I've made that behavior conditional on SSH_SOURCE_BASHRC being defined + * in config-top.h. + */ +#if 0 + temp_var = find_variable ("SSH_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } + temp_var = find_variable ("SSH2_CLIENT"); + if (temp_var && imported_p (temp_var)) + { + VUNSETATTR (temp_var, att_exported); + array_needs_making = 1; + } +#endif + + /* Get the user's real and effective user ids. */ + uidset (); + + /* Initialize the dynamic variables, and seed their values. */ + initialize_dynamic_variables (); +} + +/* **************************************************************** */ +/* */ +/* Setting values for special shell variables */ +/* */ +/* **************************************************************** */ + +static void +set_machine_vars () +{ + SHELL_VAR *temp_var; + + temp_var = set_if_not ("HOSTTYPE", HOSTTYPE); + temp_var = set_if_not ("OSTYPE", OSTYPE); + temp_var = set_if_not ("MACHTYPE", MACHTYPE); + + temp_var = set_if_not ("HOSTNAME", current_host_name); +} + +/* Set $HOME to the information in the password file if we didn't get + it from the environment. */ + +/* This function is not static so the tilde and readline libraries can + use it. */ +char * +sh_get_home_dir () +{ + if (current_user.home_dir == 0) + get_current_user_info (); + return current_user.home_dir; +} + +static void +set_home_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("HOME"); + if (temp_var == 0) + temp_var = bind_variable ("HOME", sh_get_home_dir ()); +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +/* Set $SHELL to the user's login shell if it is not already set. Call + get_current_user_info if we haven't already fetched the shell. */ +static void +set_shell_var () +{ + SHELL_VAR *temp_var; + + temp_var = find_variable ("SHELL"); + if (temp_var == 0) + { + if (current_user.shell == 0) + get_current_user_info (); + temp_var = bind_variable ("SHELL", current_user.shell); + } +#if 0 + VSETATTR (temp_var, att_exported); +#endif +} + +static char * +get_bash_name () +{ + char *name; + + if ((login_shell == 1) && RELPATH(shell_name)) + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + else if (ABSPATH(shell_name)) + name = savestring (shell_name); + else if (shell_name[0] == '.' && shell_name[1] == '/') + { + /* Fast path for common case. */ + char *cdir; + int len; + + cdir = get_string_value ("PWD"); + if (cdir) + { + len = strlen (cdir); + name = (char *)xmalloc (len + strlen (shell_name) + 1); + strcpy (name, cdir); + strcpy (name + len, shell_name + 1); + } + else + name = savestring (shell_name); + } + else + { + char *tname; + int s; + + tname = find_user_command (shell_name); + + if (tname == 0) + { + /* Try the current directory. If there is not an executable + there, just punt and use the login shell. */ + s = file_status (shell_name); + if (s & FS_EXECABLE) + { + tname = make_absolute (shell_name, get_string_value ("PWD")); + if (*shell_name == '.') + { + name = sh_canonpath (tname, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); + if (name == 0) + name = tname; + else + free (tname); + } + else + name = tname; + } + else + { + if (current_user.shell == 0) + get_current_user_info (); + name = savestring (current_user.shell); + } + } + else + { + name = full_pathname (tname); + free (tname); + } + } + + return (name); +} + +void +adjust_shell_level (change) + int change; +{ + char new_level[5], *old_SHLVL; + intmax_t old_level; + SHELL_VAR *temp_var; + + old_SHLVL = get_string_value ("SHLVL"); + if (old_SHLVL == 0 || *old_SHLVL == '\0' || legal_number (old_SHLVL, &old_level) == 0) + old_level = 0; + + shell_level = old_level + change; + if (shell_level < 0) + shell_level = 0; + else if (shell_level > 1000) + { + internal_warning (_("shell level (%d) too high, resetting to 1"), shell_level); + shell_level = 1; + } + + /* We don't need the full generality of itos here. */ + if (shell_level < 10) + { + new_level[0] = shell_level + '0'; + new_level[1] = '\0'; + } + else if (shell_level < 100) + { + new_level[0] = (shell_level / 10) + '0'; + new_level[1] = (shell_level % 10) + '0'; + new_level[2] = '\0'; + } + else if (shell_level < 1000) + { + new_level[0] = (shell_level / 100) + '0'; + old_level = shell_level % 100; + new_level[1] = (old_level / 10) + '0'; + new_level[2] = (old_level % 10) + '0'; + new_level[3] = '\0'; + } + + temp_var = bind_variable ("SHLVL", new_level); + set_auto_export (temp_var); +} + +static void +initialize_shell_level () +{ + adjust_shell_level (1); +} + +/* If we got PWD from the environment, update our idea of the current + working directory. In any case, make sure that PWD exists before + checking it. It is possible for getcwd () to fail on shell startup, + and in that case, PWD would be undefined. If this is an interactive + login shell, see if $HOME is the current working directory, and if + that's not the same string as $PWD, set PWD=$HOME. */ + +void +set_pwd () +{ + SHELL_VAR *temp_var, *home_var; + char *temp_string, *home_string; + + home_var = find_variable ("HOME"); + home_string = home_var ? value_cell (home_var) : (char *)NULL; + + temp_var = find_variable ("PWD"); + if (temp_var && imported_p (temp_var) && + (temp_string = value_cell (temp_var)) && + same_file (temp_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + set_working_directory (temp_string); + else if (home_string && interactive_shell && login_shell && + same_file (home_string, ".", (struct stat *)NULL, (struct stat *)NULL)) + { + set_working_directory (home_string); + temp_var = bind_variable ("PWD", home_string); + set_auto_export (temp_var); + } + else + { + temp_string = get_working_directory ("shell-init"); + if (temp_string) + { + temp_var = bind_variable ("PWD", temp_string); + set_auto_export (temp_var); + free (temp_string); + } + } + + /* According to the Single Unix Specification, v2, $OLDPWD is an + `environment variable' and therefore should be auto-exported. + Make a dummy invisible variable for OLDPWD, and mark it as exported. */ + temp_var = bind_variable ("OLDPWD", (char *)NULL); + VSETATTR (temp_var, (att_exported | att_invisible)); +} + +/* Make a variable $PPID, which holds the pid of the shell's parent. */ +void +set_ppid () +{ + char namebuf[INT_STRLEN_BOUND(pid_t) + 1], *name; + SHELL_VAR *temp_var; + + name = inttostr (getppid (), namebuf, sizeof(namebuf)); + temp_var = find_variable ("PPID"); + if (temp_var) + VUNSETATTR (temp_var, (att_readonly | att_exported)); + temp_var = bind_variable ("PPID", name); + VSETATTR (temp_var, (att_readonly | att_integer)); +} + +static void +uidset () +{ + char buff[INT_STRLEN_BOUND(uid_t) + 1], *b; + register SHELL_VAR *v; + + b = inttostr (current_user.uid, buff, sizeof (buff)); + v = find_variable ("UID"); + if (v == 0) + { + v = bind_variable ("UID", b); + VSETATTR (v, (att_readonly | att_integer)); + } + + if (current_user.euid != current_user.uid) + b = inttostr (current_user.euid, buff, sizeof (buff)); + + v = find_variable ("EUID"); + if (v == 0) + { + v = bind_variable ("EUID", b); + VSETATTR (v, (att_readonly | att_integer)); + } +} + +#if defined (ARRAY_VARS) +static void +make_vers_array () +{ + SHELL_VAR *vv; + ARRAY *av; + char *s, d[32], b[INT_STRLEN_BOUND(int) + 1]; + + unbind_variable ("BASH_VERSINFO"); + + vv = make_new_array_variable ("BASH_VERSINFO"); + av = array_cell (vv); + strcpy (d, dist_version); + s = xstrchr (d, '.'); + if (s) + *s++ = '\0'; + array_insert (av, 0, d); + array_insert (av, 1, s); + s = inttostr (patch_level, b, sizeof (b)); + array_insert (av, 2, s); + s = inttostr (build_version, b, sizeof (b)); + array_insert (av, 3, s); + array_insert (av, 4, release_status); + array_insert (av, 5, MACHTYPE); + + VSETATTR (vv, att_readonly); +} +#endif /* ARRAY_VARS */ + +/* Set the environment variables $LINES and $COLUMNS in response to + a window size change. */ +void +sh_set_lines_and_columns (lines, cols) + int lines, cols; +{ + char val[INT_STRLEN_BOUND(int) + 1], *v; + + v = inttostr (lines, val, sizeof (val)); + bind_variable ("LINES", v); + + v = inttostr (cols, val, sizeof (val)); + bind_variable ("COLUMNS", v); +} + +/* **************************************************************** */ +/* */ +/* Printing variables and values */ +/* */ +/* **************************************************************** */ + +/* Print LIST (a list of shell variables) to stdout in such a way that + they can be read back in. */ +void +print_var_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + if (invisible_p (var) == 0) + print_assignment (var); +} + +/* Print LIST (a list of shell functions) to stdout in such a way that + they can be read back in. */ +void +print_func_list (list) + register SHELL_VAR **list; +{ + register int i; + register SHELL_VAR *var; + + for (i = 0; list && (var = list[i]); i++) + { + printf ("%s ", var->name); + print_var_function (var); + printf ("\n"); + } +} + +/* Print the value of a single SHELL_VAR. No newline is + output, but the variable is printed in such a way that + it can be read back in. */ +void +print_assignment (var) + SHELL_VAR *var; +{ + if (var_isset (var) == 0) + return; + + if (function_p (var)) + { + printf ("%s", var->name); + print_var_function (var); + printf ("\n"); + } +#if defined (ARRAY_VARS) + else if (array_p (var)) + print_array_assignment (var, 0); +#endif /* ARRAY_VARS */ + else + { + printf ("%s=", var->name); + print_var_value (var, 1); + printf ("\n"); + } +} + +/* Print the value cell of VAR, a shell variable. Do not print + the name, nor leading/trailing newline. If QUOTE is non-zero, + and the value contains shell metacharacters, quote the value + in such a way that it can be read back in. */ +void +print_var_value (var, quote) + SHELL_VAR *var; + int quote; +{ + char *t; + + if (var_isset (var) == 0) + return; + + if (quote && posixly_correct == 0 && ansic_shouldquote (value_cell (var))) + { + t = ansic_quote (value_cell (var), 0, (int *)0); + printf ("%s", t); + free (t); + } + else if (quote && sh_contains_shell_metas (value_cell (var))) + { + t = sh_single_quote (value_cell (var)); + printf ("%s", t); + free (t); + } + else + printf ("%s", value_cell (var)); +} + +/* Print the function cell of VAR, a shell variable. Do not + print the name, nor leading/trailing newline. */ +void +print_var_function (var) + SHELL_VAR *var; +{ + if (function_p (var) && var_isset (var)) + printf ("%s", named_function_string ((char *)NULL, function_cell(var), 1)); +} + +/* **************************************************************** */ +/* */ +/* Dynamic Variables */ +/* */ +/* **************************************************************** */ + +/* DYNAMIC VARIABLES + + These are variables whose values are generated anew each time they are + referenced. These are implemented using a pair of function pointers + in the struct variable: assign_func, which is called from bind_variable + and, if arrays are compiled into the shell, some of the functions in + arrayfunc.c, and dynamic_value, which is called from find_variable. + + assign_func is called from bind_variable_internal, if + bind_variable_internal discovers that the variable being assigned to + has such a function. The function is called as + SHELL_VAR *temp = (*(entry->assign_func)) (entry, value, ind) + and the (SHELL_VAR *)temp is returned as the value of bind_variable. It + is usually ENTRY (self). IND is an index for an array variable, and + unused otherwise. + + dynamic_value is called from find_variable_internal to return a `new' + value for the specified dynamic varible. If this function is NULL, + the variable is treated as a `normal' shell variable. If it is not, + however, then this function is called like this: + tempvar = (*(var->dynamic_value)) (var); + + Sometimes `tempvar' will replace the value of `var'. Other times, the + shell will simply use the string value. Pretty object-oriented, huh? + + Be warned, though: if you `unset' a special variable, it loses its + special meaning, even if you subsequently set it. + + The special assignment code would probably have been better put in + subst.c: do_assignment_internal, in the same style as + stupidly_hack_special_variables, but I wanted the changes as + localized as possible. */ + +#define INIT_DYNAMIC_VAR(var, val, gfunc, afunc) \ + do \ + { \ + v = bind_variable (var, (val)); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +#define INIT_DYNAMIC_ARRAY_VAR(var, gfunc, afunc) \ + do \ + { \ + v = make_new_array_variable (var); \ + v->dynamic_value = gfunc; \ + v->assign_func = afunc; \ + } \ + while (0) + +static SHELL_VAR * +null_assign (self, value, unused) + SHELL_VAR *self; + char *value; + arrayind_t unused; +{ + return (self); +} + +#if defined (ARRAY_VARS) +static SHELL_VAR * +null_array_assign (self, value, ind) + SHELL_VAR *self; + char *value; + arrayind_t ind; +{ + return (self); +} +#endif + +/* Degenerate `dynamic_value' function; just returns what's passed without + manipulation. */ +static SHELL_VAR * +get_self (self) + SHELL_VAR *self; +{ + return (self); +} + +#if defined (ARRAY_VARS) +/* A generic dynamic array variable initializer. Intialize array variable + NAME with dynamic value function GETFUNC and assignment function SETFUNC. */ +static SHELL_VAR * +init_dynamic_array_var (name, getfunc, setfunc, attrs) + char *name; + sh_var_value_func_t *getfunc; + sh_var_assign_func_t *setfunc; + int attrs; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v) + return (v); + INIT_DYNAMIC_ARRAY_VAR (name, getfunc, setfunc); + if (attrs) + VSETATTR (v, attrs); + return v; +} +#endif + + +/* The value of $SECONDS. This is the number of seconds since shell + invocation, or, the number of seconds since the last assignment + the + value of the last assignment. */ +static intmax_t seconds_value_assigned; + +static SHELL_VAR * +assign_seconds (self, value, unused) + SHELL_VAR *self; + char *value; + arrayind_t unused; +{ + if (legal_number (value, &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + shell_start_time = NOW; + return (self); +} + +static SHELL_VAR * +get_seconds (var) + SHELL_VAR *var; +{ + time_t time_since_start; + char *p; + + time_since_start = NOW - shell_start_time; + p = itos(seconds_value_assigned + time_since_start); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +init_seconds_var () +{ + SHELL_VAR *v; + + v = find_variable ("SECONDS"); + if (v) + { + if (legal_number (value_cell(v), &seconds_value_assigned) == 0) + seconds_value_assigned = 0; + } + INIT_DYNAMIC_VAR ("SECONDS", (v ? value_cell (v) : (char *)NULL), get_seconds, assign_seconds); + return v; +} + +/* The random number seed. You can change this by setting RANDOM. */ +static unsigned long rseed = 1; +static int last_random_value; + +/* A linear congruential random number generator based on the example + one in the ANSI C standard. This one isn't very good, but a more + complicated one is overkill. */ + +/* Returns a pseudo-random number between 0 and 32767. */ +static int +brand () +{ + rseed = rseed * 1103515245 + 12345; + return ((unsigned int)((rseed >> 16) & 32767)); /* was % 32768 */ +} + +/* Set the random number generator seed to SEED. */ +static void +sbrand (seed) + unsigned long seed; +{ + rseed = seed; + last_random_value = 0; +} + +static SHELL_VAR * +assign_random (self, value, unused) + SHELL_VAR *self; + char *value; + arrayind_t unused; +{ + sbrand (strtoul (value, (char **)NULL, 10)); + return (self); +} + +int +get_random_number () +{ + int rv; + + /* Reset for command and process substitution. */ + if (subshell_environment) + sbrand (rseed + getpid() + NOW); + + do + rv = brand (); + while (rv == last_random_value); + return rv; +} + +static SHELL_VAR * +get_random (var) + SHELL_VAR *var; +{ + int rv; + char *p; + + rv = get_random_number (); + last_random_value = rv; + p = itos (rv); + + FREE (value_cell (var)); + + VSETATTR (var, att_integer); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_lineno (var, value, unused) + SHELL_VAR *var; + char *value; + arrayind_t unused; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + line_number = new_value; + return var; +} + +/* Function which returns the current line number. */ +static SHELL_VAR * +get_lineno (var) + SHELL_VAR *var; +{ + char *p; + int ln; + + ln = executing_line_number (); + p = itos (ln); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +assign_subshell (var, value, unused) + SHELL_VAR *var; + char *value; + arrayind_t unused; +{ + intmax_t new_value; + + if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) + new_value = 0; + subshell_level = new_value; + return var; +} + +static SHELL_VAR * +get_subshell (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (subshell_level); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +static SHELL_VAR * +get_bash_command (var) + SHELL_VAR *var; +{ + char *p; + + p = savestring (the_printed_command_except_trap); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} + +#if defined (HISTORY) +static SHELL_VAR * +get_histcmd (var) + SHELL_VAR *var; +{ + char *p; + + p = itos (history_number ()); + FREE (value_cell (var)); + var_setvalue (var, p); + return (var); +} +#endif + +#if defined (READLINE) +/* When this function returns, VAR->value points to malloced memory. */ +static SHELL_VAR * +get_comp_wordbreaks (var) + SHELL_VAR *var; +{ + char *p; + + /* If we don't have anything yet, assign a default value. */ + if (rl_completer_word_break_characters == 0 && bash_readline_initialized == 0) + enable_hostname_completion (perform_hostname_completion); + +#if 0 + FREE (value_cell (var)); + p = savestring (rl_completer_word_break_characters); + + var_setvalue (var, p); +#else + var_setvalue (var, rl_completer_word_break_characters); +#endif + + return (var); +} + +/* When this function returns, rl_completer_word_break_characters points to + malloced memory. */ +static SHELL_VAR * +assign_comp_wordbreaks (self, value, unused) + SHELL_VAR *self; + char *value; + arrayind_t unused; +{ + if (rl_completer_word_break_characters && + rl_completer_word_break_characters != rl_basic_word_break_characters) + free (rl_completer_word_break_characters); + + rl_completer_word_break_characters = savestring (value); + return self; +} +#endif /* READLINE */ + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) +static SHELL_VAR * +assign_dirstack (self, value, ind) + SHELL_VAR *self; + char *value; + arrayind_t ind; +{ + set_dirstack_element (ind, 1, value); + return self; +} + +static SHELL_VAR * +get_dirstack (self) + SHELL_VAR *self; +{ + ARRAY *a; + WORD_LIST *l; + + l = get_directory_stack (); + a = array_from_word_list (l); + array_dispose (array_cell (self)); + dispose_words (l); + var_setarray (self, a); + return self; +} +#endif /* PUSHD AND POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) +/* We don't want to initialize the group set with a call to getgroups() + unless we're asked to, but we only want to do it once. */ +static SHELL_VAR * +get_groupset (self) + SHELL_VAR *self; +{ + register int i; + int ng; + ARRAY *a; + static char **group_set = (char **)NULL; + + if (group_set == 0) + { + group_set = get_group_list (&ng); + a = array_cell (self); + for (i = 0; i < ng; i++) + array_insert (a, i, group_set[i]); + } + return (self); +} +#endif /* ARRAY_VARS */ + +/* If ARRAY_VARS is not defined, this just returns the name of any + currently-executing function. If we have arrays, it's a call stack. */ +static SHELL_VAR * +get_funcname (self) + SHELL_VAR *self; +{ +#if ! defined (ARRAY_VARS) + char *t; + if (variable_context && this_shell_function) + { + FREE (value_cell (self)); + t = savestring (this_shell_function->name); + var_setvalue (self, t); + } +#endif + return (self); +} + +void +make_funcname_visible (on_or_off) + int on_or_off; +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v == 0 || v->dynamic_value == 0) + return; + + if (on_or_off) + VUNSETATTR (v, att_invisible); + else + VSETATTR (v, att_invisible); +} + +static SHELL_VAR * +init_funcname_var () +{ + SHELL_VAR *v; + + v = find_variable ("FUNCNAME"); + if (v) + return v; +#if defined (ARRAY_VARS) + INIT_DYNAMIC_ARRAY_VAR ("FUNCNAME", get_funcname, null_array_assign); +#else + INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign); +#endif + VSETATTR (v, att_invisible|att_noassign); + return v; +} + +static void +initialize_dynamic_variables () +{ + SHELL_VAR *v; + + v = init_seconds_var (); + + INIT_DYNAMIC_VAR ("BASH_COMMAND", (char *)NULL, get_bash_command, (sh_var_assign_func_t *)NULL); + INIT_DYNAMIC_VAR ("BASH_SUBSHELL", (char *)NULL, get_subshell, assign_subshell); + + INIT_DYNAMIC_VAR ("RANDOM", (char *)NULL, get_random, assign_random); + INIT_DYNAMIC_VAR ("LINENO", (char *)NULL, get_lineno, assign_lineno); + +#if defined (HISTORY) + INIT_DYNAMIC_VAR ("HISTCMD", (char *)NULL, get_histcmd, (sh_var_assign_func_t *)NULL); +#endif + +#if defined (READLINE) + INIT_DYNAMIC_VAR ("COMP_WORDBREAKS", (char *)NULL, get_comp_wordbreaks, assign_comp_wordbreaks); +#endif + +#if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) + v = init_dynamic_array_var ("DIRSTACK", get_dirstack, assign_dirstack, 0); +#endif /* PUSHD_AND_POPD && ARRAY_VARS */ + +#if defined (ARRAY_VARS) + v = init_dynamic_array_var ("GROUPS", get_groupset, null_array_assign, att_noassign); + +# if defined (DEBUGGER) + v = init_dynamic_array_var ("BASH_ARGC", get_self, null_array_assign, (att_invisible|att_noassign)); + v = init_dynamic_array_var ("BASH_ARGV", get_self, null_array_assign, (att_invisible|att_noassign)); +# endif /* DEBUGGER */ + v = init_dynamic_array_var ("BASH_SOURCE", get_self, null_array_assign, (att_invisible|att_noassign)); + v = init_dynamic_array_var ("BASH_LINENO", get_self, null_array_assign, (att_invisible|att_noassign)); +#endif + + v = init_funcname_var (); +} + +/* **************************************************************** */ +/* */ +/* Retrieving variables and values */ +/* */ +/* **************************************************************** */ + +/* How to get a pointer to the shell variable or function named NAME. + HASHED_VARS is a pointer to the hash table containing the list + of interest (either variables or functions). */ + +static SHELL_VAR * +hash_lookup (name, hashed_vars) + const char *name; + HASH_TABLE *hashed_vars; +{ + BUCKET_CONTENTS *bucket; + + bucket = hash_search (name, hashed_vars, 0); + return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL); +} + +SHELL_VAR * +var_lookup (name, vcontext) + const char *name; + VAR_CONTEXT *vcontext; +{ + VAR_CONTEXT *vc; + SHELL_VAR *v; + + v = (SHELL_VAR *)NULL; + for (vc = vcontext; vc; vc = vc->down) + if (v = hash_lookup (name, vc->table)) + break; + + return v; +} + +/* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero, + then also search the temporarily built list of exported variables. + The lookup order is: + temporary_env + shell_variables list +*/ + +SHELL_VAR * +find_variable_internal (name, force_tempenv) + const char *name; + int force_tempenv; +{ + SHELL_VAR *var; + int search_tempenv; + + var = (SHELL_VAR *)NULL; + + /* If explicitly requested, first look in the temporary environment for + the variable. This allows constructs such as "foo=x eval 'echo $foo'" + to get the `exported' value of $foo. This happens if we are executing + a function or builtin, or if we are looking up a variable in a + "subshell environment". */ + search_tempenv = force_tempenv || (expanding_redir == 0 && subshell_environment); + + if (search_tempenv && temporary_env) + var = hash_lookup (name, temporary_env); + + if (var == 0) + var = var_lookup (name, shell_variables); + + if (var == 0) + return ((SHELL_VAR *)NULL); + + return (var->dynamic_value ? (*(var->dynamic_value)) (var) : var); +} + +/* Look up the variable entry named NAME. Returns the entry or NULL. */ +SHELL_VAR * +find_variable (name) + const char *name; +{ + return (find_variable_internal (name, (expanding_redir == 0 && this_shell_builtin != 0))); +} + +/* Look up the function entry whose name matches STRING. + Returns the entry or NULL. */ +SHELL_VAR * +find_function (name) + const char *name; +{ + return (hash_lookup (name, shell_functions)); +} + +/* Find the function definition for the shell function named NAME. Returns + the entry or NULL. */ +FUNCTION_DEF * +find_function_def (name) + const char *name; +{ + return ((FUNCTION_DEF *)hash_lookup (name, shell_function_defs)); +} + +/* Return the value of VAR. VAR is assumed to have been the result of a + lookup without any subscript, if arrays are compiled into the shell. */ +char * +get_variable_value (var) + SHELL_VAR *var; +{ + if (var == 0) + return ((char *)NULL); +#if defined (ARRAY_VARS) + else if (array_p (var)) + return (array_reference (array_cell (var), 0)); +#endif + else + return (value_cell (var)); +} + +/* Return the string value of a variable. Return NULL if the variable + doesn't exist. Don't cons a new string. This is a potential memory + leak if the variable is found in the temporary environment. Since + functions and variables have separate name spaces, returns NULL if + var_name is a shell function only. */ +char * +get_string_value (var_name) + const char *var_name; +{ + SHELL_VAR *var; + + var = find_variable (var_name); + return ((var) ? get_variable_value (var) : (char *)NULL); +} + +/* This is present for use by the tilde and readline libraries. */ +char * +sh_get_env_value (v) + const char *v; +{ + return get_string_value (v); +} + +/* **************************************************************** */ +/* */ +/* Creating and setting variables */ +/* */ +/* **************************************************************** */ + +/* Set NAME to VALUE if NAME has no value. */ +SHELL_VAR * +set_if_not (name, value) + char *name, *value; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v == 0) + v = bind_variable_internal (name, value, global_variables->table, HASH_NOSRCH); + return (v); +} + +/* Create a local variable referenced by NAME. */ +SHELL_VAR * +make_local_variable (name) + const char *name; +{ + SHELL_VAR *new_var, *old_var; + VAR_CONTEXT *vc; + int was_tmpvar; + char *tmp_value; + + /* local foo; local foo; is a no-op. */ + old_var = find_variable (name); + if (old_var && local_p (old_var) && old_var->context == variable_context) + return (old_var); + + was_tmpvar = old_var && tempvar_p (old_var); + if (was_tmpvar) + tmp_value = value_cell (old_var); + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("make_local_variable: no function context at current scope")); + return ((SHELL_VAR *)NULL); + } + else if (vc->table == 0) + vc->table = hash_create (TEMPENV_HASH_BUCKETS); + + /* Since this is called only from the local/declare/typeset code, we can + call builtin_error here without worry (of course, it will also work + for anything that sets this_command_name). Variables with the `noassign' + attribute may not be made local. The test against old_var's context + level is to disallow local copies of readonly global variables (since I + believe that this could be a security hole). Readonly copies of calling + function local variables are OK. */ + if (old_var && (noassign_p (old_var) || + (readonly_p (old_var) && old_var->context == 0))) + { + if (readonly_p (old_var)) + sh_readonly (name); + return ((SHELL_VAR *)NULL); + } + + if (old_var == 0) + new_var = bind_variable_internal (name, "", vc->table, HASH_NOSRCH); + else + { + new_var = make_new_variable (name, vc->table); + + /* If we found this variable in one of the temporary environments, + inherit its value. Watch to see if this causes problems with + things like `x=4 local x'. */ + if (was_tmpvar) + var_setvalue (new_var, savestring (tmp_value)); + + new_var->attributes = exported_p (old_var) ? att_exported : 0; + } + + vc->flags |= VC_HASLOCAL; + + new_var->context = variable_context; + VSETATTR (new_var, att_local); + + if (ifsname (name)) + setifs (new_var); + + return (new_var); +} + +#if defined (ARRAY_VARS) +SHELL_VAR * +make_local_array_variable (name) + char *name; +{ + SHELL_VAR *var; + ARRAY *array; + + var = make_local_variable (name); + if (var == 0) + return var; + array = array_create (); + + FREE (value_cell(var)); + var_setarray (var, array); + VSETATTR (var, att_array); + return var; +} +#endif /* ARRAY_VARS */ + +/* Create a new shell variable with name NAME. */ +static SHELL_VAR * +new_shell_variable (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + entry->name = savestring (name); + var_setvalue (entry, (char *)NULL); + CLEAR_EXPORTSTR (entry); + + entry->dynamic_value = (sh_var_value_func_t *)NULL; + entry->assign_func = (sh_var_assign_func_t *)NULL; + + entry->attributes = 0; + + /* Always assume variables are to be made at toplevel! + make_local_variable has the responsibilty of changing the + variable context. */ + entry->context = 0; + + return (entry); +} + +/* Create a new shell variable with name NAME and add it to the hash table + TABLE. */ +static SHELL_VAR * +make_new_variable (name, table) + const char *name; + HASH_TABLE *table; +{ + SHELL_VAR *entry; + BUCKET_CONTENTS *elt; + + entry = new_shell_variable (name); + + /* Make sure we have a shell_variables hash table to add to. */ + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (0); + } + + elt = hash_insert (savestring (name), table, HASH_NOSRCH); + elt->data = (PTR_T)entry; + + return entry; +} + +#if defined (ARRAY_VARS) +SHELL_VAR * +make_new_array_variable (name) + char *name; +{ + SHELL_VAR *entry; + ARRAY *array; + + entry = make_new_variable (name, global_variables->table); + array = array_create (); + var_setarray (entry, array); + VSETATTR (entry, att_array); + return entry; +} +#endif + +char * +make_variable_value (var, value) + SHELL_VAR *var; + char *value; +{ + char *retval; + intmax_t lval; + int expok; + + /* If this variable has had its type set to integer (via `declare -i'), + then do expression evaluation on it and store the result. The + functions in expr.c (evalexp()) and bind_int_variable() are responsible + for turning off the integer flag if they don't want further + evaluation done. */ + if (integer_p (var)) + { + lval = evalexp (value, &expok); + if (expok == 0) + jump_to_top_level (DISCARD); + retval = itos (lval); + } + else if (value) + { + if (*value) + retval = savestring (value); + else + { + retval = (char *)xmalloc (1); + retval[0] = '\0'; + } + } + else + retval = (char *)NULL; + + return retval; +} + +/* Bind a variable NAME to VALUE in the HASH_TABLE TABLE, which may be the + temporary environment (but usually is not). */ +static SHELL_VAR * +bind_variable_internal (name, value, table, hflags) + const char *name; + char *value; + HASH_TABLE *table; + int hflags; +{ + char *newval; + SHELL_VAR *entry; + + entry = (hflags & HASH_NOSRCH) ? (SHELL_VAR *)NULL : hash_lookup (name, table); + + if (entry == 0) + { + entry = make_new_variable (name, table); + var_setvalue (entry, make_variable_value (entry, value)); + } + else if (entry->assign_func) /* array vars have assign functions now */ + { + INVALIDATE_EXPORTSTR (entry); + return ((*(entry->assign_func)) (entry, value, -1)); + } + else + { + if (readonly_p (entry) || noassign_p (entry)) + { + if (readonly_p (entry)) + err_readonly (name); + return (entry); + } + + /* Variables which are bound are visible. */ + VUNSETATTR (entry, att_invisible); + + newval = make_variable_value (entry, value); + + /* Invalidate any cached export string */ + INVALIDATE_EXPORTSTR (entry); + +#if defined (ARRAY_VARS) + /* XXX -- this bears looking at again -- XXX */ + /* If an existing array variable x is being assigned to with x=b or + `read x' or something of that nature, silently convert it to + x[0]=b or `read x[0]'. */ + if (array_p (entry)) + { + array_insert (array_cell (entry), 0, newval); + free (newval); + } + else +#endif + { + FREE (value_cell (entry)); + var_setvalue (entry, newval); + } + } + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + if (exported_p (entry)) + array_needs_making = 1; + + return (entry); +} + +/* Bind a variable NAME to VALUE. This conses up the name + and value strings. If we have a temporary environment, we bind there + first, then we bind into shell_variables. */ + +SHELL_VAR * +bind_variable (name, value) + const char *name; + char *value; +{ + SHELL_VAR *v; + VAR_CONTEXT *vc; + + if (shell_variables == 0) + { + shell_variables = global_variables = new_var_context ((char *)NULL, 0); + shell_variables->scope = 0; + shell_variables->table = hash_create (0); + } + + /* If we have a temporary environment, look there first for the variable, + and, if found, modify the value there before modifying it in the + shell_variables table. This allows sourced scripts to modify values + given to them in a temporary environment while modifying the variable + value that the caller sees. */ + if (temporary_env) + bind_tempenv_variable (name, value); + + /* XXX -- handle local variables here. */ + for (vc = shell_variables; vc; vc = vc->down) + { + if (vc_isfuncenv (vc) || vc_isbltnenv (vc)) + { + v = hash_lookup (name, vc->table); + if (v) + return (bind_variable_internal (name, value, vc->table, 0)); + } + } + return (bind_variable_internal (name, value, global_variables->table, 0)); +} + +/* Make VAR, a simple shell variable, have value VALUE. Once assigned a + value, variables are no longer invisible. This is a duplicate of part + of the internals of bind_variable. If the variable is exported, or + all modified variables should be exported, mark the variable for export + and note that the export environment needs to be recreated. */ +SHELL_VAR * +bind_variable_value (var, value) + SHELL_VAR *var; + char *value; +{ + char *t; + + VUNSETATTR (var, att_invisible); + + t = make_variable_value (var, value); + FREE (value_cell (var)); + var_setvalue (var, t); + + INVALIDATE_EXPORTSTR (var); + + if (mark_modified_vars) + VSETATTR (var, att_exported); + + if (exported_p (var)) + array_needs_making = 1; + + return (var); +} + +/* Bind/create a shell variable with the name LHS to the RHS. + This creates or modifies a variable such that it is an integer. + + This used to be in expr.c, but it is here so that all of the + variable binding stuff is localized. Since we don't want any + recursive evaluation from bind_variable() (possible without this code, + since bind_variable() calls the evaluator for variables with the integer + attribute set), we temporarily turn off the integer attribute for each + variable we set here, then turn it back on after binding as necessary. */ + +SHELL_VAR * +bind_int_variable (lhs, rhs) + char *lhs, *rhs; +{ + register SHELL_VAR *v; + char *t; + int isint, isarr; + + isint = isarr = 0; +#if defined (ARRAY_VARS) +# if 0 + if (t = xstrchr (lhs, '[')) /*]*/ +# else + if (valid_array_reference (lhs)) +# endif + { + isarr = 1; + v = array_variable_part (lhs, (char **)0, (int *)0); + } + else +#endif + v = find_variable (lhs); + + if (v) + { + isint = integer_p (v); + VUNSETATTR (v, att_integer); + } + +#if defined (ARRAY_VARS) + if (isarr) + v = assign_array_element (lhs, rhs); + else +#endif + v = bind_variable (lhs, rhs); + + if (isint) + VSETATTR (v, att_integer); + + return (v); +} + +SHELL_VAR * +bind_var_to_int (var, val) + char *var; + intmax_t val; +{ + char ibuf[INT_STRLEN_BOUND (intmax_t) + 1], *p; + + p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0); + return (bind_int_variable (var, p)); +} + +/* Do a function binding to a variable. You pass the name and + the command to bind to. This conses the name and command. */ +SHELL_VAR * +bind_function (name, value) + const char *name; + COMMAND *value; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry == 0) + { + BUCKET_CONTENTS *elt; + + elt = hash_insert (savestring (name), shell_functions, HASH_NOSRCH); + entry = new_shell_variable (name); + elt->data = (PTR_T)entry; + } + else + INVALIDATE_EXPORTSTR (entry); + + if (var_isset (entry)) + dispose_command (function_cell (entry)); + + if (value) + var_setfunc (entry, copy_command (value)); + else + var_setfunc (entry, 0); + + VSETATTR (entry, att_function); + + if (mark_modified_vars) + VSETATTR (entry, att_exported); + + VUNSETATTR (entry, att_invisible); /* Just to be sure */ + + if (exported_p (entry)) + array_needs_making = 1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + return (entry); +} + +/* Bind a function definition, which includes source file and line number + information in addition to the command, into the FUNCTION_DEF hash table.*/ +void +bind_function_def (name, value) + const char *name; + FUNCTION_DEF *value; +{ + FUNCTION_DEF *entry; + BUCKET_CONTENTS *elt; + COMMAND *cmd; + + entry = find_function_def (name); + if (entry) + { + dispose_function_def_contents (entry); + entry = copy_function_def_contents (value, entry); + } + else + { + cmd = value->command; + value->command = 0; + entry = copy_function_def (value); + value->command = cmd; + + elt = hash_insert (savestring (name), shell_function_defs, HASH_NOSRCH); + elt->data = (PTR_T *)entry; + } +} + +/* Add STRING, which is of the form foo=bar, to the temporary environment + HASH_TABLE (temporary_env). The functions in execute_cmd.c are + responsible for moving the main temporary env to one of the other + temporary environments. The expansion code in subst.c calls this. */ +int +assign_in_env (string) + const char *string; +{ + int offset; + char *name, *temp, *value; + SHELL_VAR *var; + + offset = assignment (string, 0); + name = savestring (string); + value = (char *)NULL; + + if (name[offset] == '=') + { + name[offset] = 0; + + var = find_variable (name); + if (var && (readonly_p (var) || noassign_p (var))) + { + if (readonly_p (var)) + err_readonly (name); + free (name); + return (0); + } + + temp = name + offset + 1; + temp = (xstrchr (temp, '~') != 0) ? bash_tilde_expand (temp, 1) : savestring (temp); + + value = expand_string_unsplit_to_string (temp, 0); + free (temp); + } + + if (temporary_env == 0) + temporary_env = hash_create (TEMPENV_HASH_BUCKETS); + + var = hash_lookup (name, temporary_env); + if (var == 0) + var = make_new_variable (name, temporary_env); + else + FREE (value_cell (var)); + + if (value == 0) + { + value = (char *)xmalloc (1); /* like do_assignment_internal */ + value[0] = '\0'; + } + + var_setvalue (var, value); + var->attributes |= (att_exported|att_tempvar); + var->context = variable_context; /* XXX */ + + INVALIDATE_EXPORTSTR (var); + var->exportstr = mk_env_string (name, value); + + array_needs_making = 1; + + if (ifsname (name)) + setifs (var); + + if (echo_command_at_execute) + { + /* The Korn shell prints the `+ ' in front of assignment statements, + so we do too. */ +#if 0 + fprintf (stderr, "%s%s=%s\n", indirection_level_string (), name, value); + fflush (stderr); +#else + xtrace_print_assignment (name, value, 0, 1); +#endif + } + + free (name); + return 1; +} + +/* **************************************************************** */ +/* */ +/* Copying variables */ +/* */ +/* **************************************************************** */ + +#ifdef INCLUDE_UNUSED +/* Copy VAR to a new data structure and return that structure. */ +SHELL_VAR * +copy_variable (var) + SHELL_VAR *var; +{ + SHELL_VAR *copy = (SHELL_VAR *)NULL; + + if (var) + { + copy = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); + + copy->attributes = var->attributes; + copy->name = savestring (var->name); + + if (function_p (var)) + var_setfunc (copy, copy_command (function_cell (var))); +#if defined (ARRAY_VARS) + else if (array_p (var)) + var_setarray (copy, dup_array (array_cell (var))); +#endif + else if (value_cell (var)) + var_setvalue (copy, savestring (value_cell (var))); + else + var_setvalue (copy, (char *)NULL); + + copy->dynamic_value = var->dynamic_value; + copy->assign_func = var->assign_func; + + copy->exportstr = COPY_EXPORTSTR (var); + + copy->context = var->context; + } + return (copy); +} +#endif + +/* **************************************************************** */ +/* */ +/* Deleting and unsetting variables */ +/* */ +/* **************************************************************** */ + +/* Dispose of the information attached to VAR. */ +void +dispose_variable (var) + SHELL_VAR *var; +{ + if (var == 0) + return; + + if (function_p (var)) + dispose_command (function_cell (var)); +#if defined (ARRAY_VARS) + else if (array_p (var)) + array_dispose (array_cell (var)); +#endif + else + FREE (value_cell (var)); + + FREE_EXPORTSTR (var); + + free (var->name); + + if (exported_p (var)) + array_needs_making = 1; + + free (var); +} + +/* Unset the shell variable referenced by NAME. */ +int +unbind_variable (name) + const char *name; +{ + return makunbound (name, shell_variables); +} + +/* Unset the shell function named NAME. */ +int +unbind_func (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + SHELL_VAR *func; + + elt = hash_remove (name, shell_functions, 0); + + if (elt == 0) + return -1; + +#if defined (PROGRAMMABLE_COMPLETION) + set_itemlist_dirty (&it_functions); +#endif + + func = (SHELL_VAR *)elt->data; + if (func) + { + if (exported_p (func)) + array_needs_making++; + dispose_variable (func); + } + + free (elt->key); + free (elt); + + return 0; +} + +int +unbind_function_def (name) + const char *name; +{ + BUCKET_CONTENTS *elt; + FUNCTION_DEF *funcdef; + + elt = hash_remove (name, shell_function_defs, 0); + + if (elt == 0) + return -1; + + funcdef = (FUNCTION_DEF *)elt->data; + if (funcdef) + dispose_function_def (funcdef); + + free (elt->key); + free (elt); + + return 0; +} + +/* Make the variable associated with NAME go away. HASH_LIST is the + hash table from which this variable should be deleted (either + shell_variables or shell_functions). + Returns non-zero if the variable couldn't be found. */ +int +makunbound (name, vc) + const char *name; + VAR_CONTEXT *vc; +{ + BUCKET_CONTENTS *elt, *new_elt; + SHELL_VAR *old_var; + VAR_CONTEXT *v; + char *t; + + for (elt = (BUCKET_CONTENTS *)NULL, v = vc; v; v = v->down) + if (elt = hash_remove (name, v->table, 0)) + break; + + if (elt == 0) + return (-1); + + old_var = (SHELL_VAR *)elt->data; + + if (old_var && exported_p (old_var)) + array_needs_making++; + + /* If we're unsetting a local variable and we're still executing inside + the function, just mark the variable as invisible. The function + eventually called by pop_var_context() will clean it up later. This + must be done so that if the variable is subsequently assigned a new + value inside the function, the `local' attribute is still present. + We also need to add it back into the correct hash table. */ + if (old_var && local_p (old_var) && variable_context == old_var->context) + { + /* Reset the attributes. Preserve the export attribute if the variable + came from a temporary environment. Make sure it stays local, and + make it invisible. */ + old_var->attributes = (exported_p (old_var) && tempvar_p (old_var)) ? att_exported : 0; + VSETATTR (old_var, att_local); + VSETATTR (old_var, att_invisible); + FREE (value_cell (old_var)); + var_setvalue (old_var, (char *)NULL); + INVALIDATE_EXPORTSTR (old_var); + + new_elt = hash_insert (savestring (old_var->name), v->table, 0); + new_elt->data = (PTR_T)old_var; + stupidly_hack_special_variables (old_var->name); + + free (elt->key); + free (elt); + return (0); + } + + /* Have to save a copy of name here, because it might refer to + old_var->name. If so, stupidly_hack_special_variables will + reference freed memory. */ + t = savestring (name); + + free (elt->key); + free (elt); + + dispose_variable (old_var); + stupidly_hack_special_variables (t); + free (t); + + return (0); +} + +/* Get rid of all of the variables in the current context. */ +void +kill_all_local_variables () +{ + VAR_CONTEXT *vc; + + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + if (vc == 0) + return; /* XXX */ + + if (vc->table && vc_haslocals (vc)) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + vc->table = (HASH_TABLE *)NULL; +} + +static void +free_variable_hash_data (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + dispose_variable (var); +} + +/* Delete the entire contents of the hash table. */ +void +delete_all_variables (hashed_vars) + HASH_TABLE *hashed_vars; +{ + hash_flush (hashed_vars, free_variable_hash_data); +} + +/* **************************************************************** */ +/* */ +/* Setting variable attributes */ +/* */ +/* **************************************************************** */ + +#define FIND_OR_MAKE_VARIABLE(name, entry) \ + do \ + { \ + entry = find_variable (name); \ + if (!entry) \ + { \ + entry = bind_variable (name, ""); \ + if (!no_invisible_vars) entry->attributes |= att_invisible; \ + } \ + } \ + while (0) + +/* Make the variable associated with NAME be readonly. + If NAME does not exist yet, create it. */ +void +set_var_read_only (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + VSETATTR (entry, att_readonly); +} + +#ifdef INCLUDE_UNUSED +/* Make the function associated with NAME be readonly. + If NAME does not exist, we just punt, like auto_export code below. */ +void +set_func_read_only (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + VSETATTR (entry, att_readonly); +} + +/* Make the variable associated with NAME be auto-exported. + If NAME does not exist yet, create it. */ +void +set_var_auto_export (name) + char *name; +{ + SHELL_VAR *entry; + + FIND_OR_MAKE_VARIABLE (name, entry); + set_auto_export (entry); +} + +/* Make the function associated with NAME be auto-exported. */ +void +set_func_auto_export (name) + const char *name; +{ + SHELL_VAR *entry; + + entry = find_function (name); + if (entry) + set_auto_export (entry); +} +#endif + +/* **************************************************************** */ +/* */ +/* Creating lists of variables */ +/* */ +/* **************************************************************** */ + +static VARLIST * +vlist_alloc (nentries) + int nentries; +{ + VARLIST *vlist; + + vlist = (VARLIST *)xmalloc (sizeof (VARLIST)); + vlist->list = (SHELL_VAR **)xmalloc ((nentries + 1) * sizeof (SHELL_VAR *)); + vlist->list_size = nentries; + vlist->list_len = 0; + vlist->list[0] = (SHELL_VAR *)NULL; + + return vlist; +} + +static VARLIST * +vlist_realloc (vlist, n) + VARLIST *vlist; + int n; +{ + if (vlist == 0) + return (vlist = vlist_alloc (n)); + if (n > vlist->list_size) + { + vlist->list_size = n; + vlist->list = (SHELL_VAR **)xrealloc (vlist->list, (vlist->list_size + 1) * sizeof (SHELL_VAR *)); + } + return vlist; +} + +static void +vlist_add (vlist, var, flags) + VARLIST *vlist; + SHELL_VAR *var; + int flags; +{ + register int i; + + for (i = 0; i < vlist->list_len; i++) + if (STREQ (var->name, vlist->list[i]->name)) + break; + if (i < vlist->list_len) + return; + + if (i >= vlist->list_size) + vlist = vlist_realloc (vlist, vlist->list_size + 16); + + vlist->list[vlist->list_len++] = var; + vlist->list[vlist->list_len] = (SHELL_VAR *)NULL; +} + +/* Map FUNCTION over the variables in VAR_HASH_TABLE. Return an array of the + variables for which FUNCTION returns a non-zero value. A NULL value + for FUNCTION means to use all variables. */ +SHELL_VAR ** +map_over (function, vc) + sh_var_map_func_t *function; + VAR_CONTEXT *vc; +{ + VAR_CONTEXT *v; + VARLIST *vlist; + SHELL_VAR **ret; + int nentries; + + for (nentries = 0, v = vc; v; v = v->down) + nentries += HASH_ENTRIES (v->table); + + if (nentries == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (nentries); + + for (v = vc; v; v = v->down) + flatten (v->table, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +SHELL_VAR ** +map_over_funcs (function) + sh_var_map_func_t *function; +{ + VARLIST *vlist; + SHELL_VAR **ret; + + if (shell_functions == 0 || HASH_ENTRIES (shell_functions) == 0) + return ((SHELL_VAR **)NULL); + + vlist = vlist_alloc (HASH_ENTRIES (shell_functions)); + + flatten (shell_functions, function, vlist, 0); + + ret = vlist->list; + free (vlist); + return ret; +} + +/* Flatten VAR_HASH_TABLE, applying FUNC to each member and adding those + elements for which FUNC succeeds to VLIST->list. FLAGS is reserved + for future use. Only unique names are added to VLIST. If FUNC is + NULL, each variable in VAR_HASH_TABLE is added to VLIST. If VLIST is + NULL, FUNC is applied to each SHELL_VAR in VAR_HASH_TABLE. If VLIST + and FUNC are both NULL, nothing happens. */ +static void +flatten (var_hash_table, func, vlist, flags) + HASH_TABLE *var_hash_table; + sh_var_map_func_t *func; + VARLIST *vlist; + int flags; +{ + register int i; + register BUCKET_CONTENTS *tlist; + int r; + SHELL_VAR *var; + + if (var_hash_table == 0 || (HASH_ENTRIES (var_hash_table) == 0) || (vlist == 0 && func == 0)) + return; + + for (i = 0; i < var_hash_table->nbuckets; i++) + { + for (tlist = hash_items (i, var_hash_table); tlist; tlist = tlist->next) + { + var = (SHELL_VAR *)tlist->data; + + r = func ? (*func) (var) : 1; + if (r && vlist) + vlist_add (vlist, var, flags); + } + } +} + +void +sort_variables (array) + SHELL_VAR **array; +{ + qsort (array, strvec_len ((char **)array), sizeof (SHELL_VAR *), (QSFUNC *)qsort_var_comp); +} + +static int +qsort_var_comp (var1, var2) + SHELL_VAR **var1, **var2; +{ + int result; + + if ((result = (*var1)->name[0] - (*var2)->name[0]) == 0) + result = strcmp ((*var1)->name, (*var2)->name); + + return (result); +} + +/* Apply FUNC to each variable in SHELL_VARIABLES, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +vapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over (func, shell_variables); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Apply FUNC to each variable in SHELL_FUNCTIONS, adding each one for + which FUNC succeeds to an array of SHELL_VAR *s. Returns the array. */ +static SHELL_VAR ** +fapply (func) + sh_var_map_func_t *func; +{ + SHELL_VAR **list; + + list = map_over_funcs (func); + if (list /* && posixly_correct */) + sort_variables (list); + return (list); +} + +/* Create a NULL terminated array of all the shell variables. */ +SHELL_VAR ** +all_shell_variables () +{ + return (vapply ((sh_var_map_func_t *)NULL)); +} + +/* Create a NULL terminated array of all the shell functions. */ +SHELL_VAR ** +all_shell_functions () +{ + return (fapply ((sh_var_map_func_t *)NULL)); +} + +static int +visible_var (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0); +} + +SHELL_VAR ** +all_visible_functions () +{ + return (fapply (visible_var)); +} + +SHELL_VAR ** +all_visible_variables () +{ + return (vapply (visible_var)); +} + +/* Return non-zero if the variable VAR is visible and exported. Array + variables cannot be exported. */ +static int +visible_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && exported_p (var)); +} + +/* Return non-zero if VAR is a local variable in the current context and + is exported. */ +static int +local_and_exported (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context && exported_p (var)); +} + +SHELL_VAR ** +all_exported_variables () +{ + return (vapply (visible_and_exported)); +} + +SHELL_VAR ** +local_exported_variables () +{ + return (vapply (local_and_exported)); +} + +static int +variable_in_context (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && local_p (var) && var->context == variable_context); +} + +SHELL_VAR ** +all_local_variables () +{ + VARLIST *vlist; + SHELL_VAR **ret; + VAR_CONTEXT *vc; + + vc = shell_variables; + for (vc = shell_variables; vc; vc = vc->down) + if (vc_isfuncenv (vc) && vc->scope == variable_context) + break; + + if (vc == 0) + { + internal_error (_("all_local_variables: no function context at current scope")); + return (SHELL_VAR **)NULL; + } + if (vc->table == 0 || HASH_ENTRIES (vc->table) == 0 || vc_haslocals (vc) == 0) + return (SHELL_VAR **)NULL; + + vlist = vlist_alloc (HASH_ENTRIES (vc->table)); + + flatten (vc->table, variable_in_context, vlist, 0); + + ret = vlist->list; + free (vlist); + if (ret) + sort_variables (ret); + return ret; +} + +#if defined (ARRAY_VARS) +/* Return non-zero if the variable VAR is visible and an array. */ +static int +visible_array_vars (var) + SHELL_VAR *var; +{ + return (invisible_p (var) == 0 && array_p (var)); +} + +SHELL_VAR ** +all_array_variables () +{ + return (vapply (visible_array_vars)); +} +#endif /* ARRAY_VARS */ + +char ** +all_variables_matching_prefix (prefix) + const char *prefix; +{ + SHELL_VAR **varlist; + char **rlist; + int vind, rind, plen; + + plen = STRLEN (prefix); + varlist = all_visible_variables (); + for (vind = 0; varlist && varlist[vind]; vind++) + ; + if (varlist == 0 || vind == 0) + return ((char **)NULL); + rlist = strvec_create (vind + 1); + for (vind = rind = 0; varlist[vind]; vind++) + { + if (plen == 0 || STREQN (prefix, varlist[vind]->name, plen)) + rlist[rind++] = savestring (varlist[vind]->name); + } + rlist[rind] = (char *)0; + free (varlist); + + return rlist; +} + +/* **************************************************************** */ +/* */ +/* Managing temporary variable scopes */ +/* */ +/* **************************************************************** */ + +/* Make variable NAME have VALUE in the temporary environment. */ +static SHELL_VAR * +bind_tempenv_variable (name, value) + const char *name; + char *value; +{ + SHELL_VAR *var; + + var = temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL; + + if (var) + { + FREE (value_cell (var)); + var_setvalue (var, savestring (value)); + INVALIDATE_EXPORTSTR (var); + } + + return (var); +} + +/* Find a variable in the temporary environment that is named NAME. + Return the SHELL_VAR *, or NULL if not found. */ +SHELL_VAR * +find_tempenv_variable (name) + const char *name; +{ + return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL); +} + +/* Push the variable described by (SHELL_VAR *)DATA down to the next + variable context from the temporary environment. */ +static void +push_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + HASH_TABLE *binding_table; + + var = (SHELL_VAR *)data; + + binding_table = shell_variables->table; + if (binding_table == 0) + { + if (shell_variables == global_variables) + /* shouldn't happen */ + binding_table = shell_variables->table = global_variables->table = hash_create (0); + else + binding_table = shell_variables->table = hash_create (TEMPENV_HASH_BUCKETS); + } + + v = bind_variable_internal (var->name, value_cell (var), binding_table, 0); + + /* XXX - should we set the context here? It shouldn't matter because of how + assign_in_env works, but might want to check. */ + if (binding_table == global_variables->table) /* XXX */ + var->attributes &= ~(att_tempvar|att_propagate); + else + { + var->attributes |= att_propagate; + if (binding_table == shell_variables->table) + shell_variables->flags |= VC_HASTMPVAR; + } + v->attributes |= var->attributes; + + dispose_variable (var); +} + +static void +propagate_temp_var (data) + PTR_T data; +{ + SHELL_VAR *var; + + var = (SHELL_VAR *)data; + if (tempvar_p (var) && (var->attributes & att_propagate)) + push_temp_var (data); + else + dispose_variable (var); +} + +/* Free the storage used in the hash table for temporary + environment variables. PUSHF is a function to be called + to free each hash table entry. It takes care of pushing variables + to previous scopes if appropriate. */ +static void +dispose_temporary_env (pushf) + sh_free_func_t *pushf; +{ + hash_flush (temporary_env, pushf); + hash_dispose (temporary_env); + temporary_env = (HASH_TABLE *)NULL; + + array_needs_making = 1; + + sv_ifs ("IFS"); /* XXX here for now */ +} + +void +dispose_used_env_vars () +{ + if (temporary_env) + dispose_temporary_env (propagate_temp_var); +} + +/* Take all of the shell variables in the temporary environment HASH_TABLE + and make shell variables from them at the current variable context. */ +void +merge_temporary_env () +{ + if (temporary_env) + dispose_temporary_env (push_temp_var); +} + +/* **************************************************************** */ +/* */ +/* Creating and manipulating the environment */ +/* */ +/* **************************************************************** */ + +static inline char * +mk_env_string (name, value) + const char *name, *value; +{ + int name_len, value_len; + char *p; + + name_len = strlen (name); + value_len = STRLEN (value); + p = (char *)xmalloc (2 + name_len + value_len); + strcpy (p, name); + p[name_len] = '='; + if (value && *value) + strcpy (p + name_len + 1, value); + else + p[name_len + 1] = '\0'; + return (p); +} + +#ifdef DEBUG +/* Debugging */ +static int +valid_exportstr (v) + SHELL_VAR *v; +{ + char *s; + + s = v->exportstr; + if (legal_variable_starter ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + for (s = v->exportstr + 1; s && *s; s++) + { + if (*s == '=') + break; + if (legal_variable_char ((unsigned char)*s) == 0) + { + internal_error (_("invalid character %d in exportstr for %s"), *s, v->name); + return (0); + } + } + if (*s != '=') + { + internal_error (_("no `=' in exportstr for %s"), v->name); + return (0); + } + return (1); +} +#endif + +static char ** +make_env_array_from_var_list (vars) + SHELL_VAR **vars; +{ + register int i, list_index; + register SHELL_VAR *var; + char **list, *value; + + list = strvec_create ((1 + strvec_len ((char **)vars))); + +#define USE_EXPORTSTR (value == var->exportstr) + + for (i = 0, list_index = 0; var = vars[i]; i++) + { +#if defined (__CYGWIN__) + /* We don't use the exportstr stuff on Cygwin at all. */ + INVALIDATE_EXPORTSTR (var); +#endif + if (var->exportstr) + value = var->exportstr; + else if (function_p (var)) + value = named_function_string ((char *)NULL, function_cell (var), 0); +#if defined (ARRAY_VARS) + else if (array_p (var)) +# if 0 + value = array_to_assignment_string (array_cell (var)); +# else + continue; /* XXX array vars cannot yet be exported */ +# endif +#endif + else + value = value_cell (var); + + if (value) + { + /* Gee, I'd like to get away with not using savestring() if we're + using the cached exportstr... */ + list[list_index] = USE_EXPORTSTR ? savestring (value) + : mk_env_string (var->name, value); + + if (USE_EXPORTSTR == 0) + SAVE_EXPORTSTR (var, list[list_index]); + + list_index++; +#undef USE_EXPORTSTR + +#if 0 /* not yet */ +#if defined (ARRAY_VARS) + if (array_p (var)) + free (value); +#endif +#endif + } + } + + list[list_index] = (char *)NULL; + return (list); +} + +/* Make an array of assignment statements from the hash table + HASHED_VARS which contains SHELL_VARs. Only visible, exported + variables are eligible. */ +static char ** +make_var_export_array (vcxt) + VAR_CONTEXT *vcxt; +{ + char **list; + SHELL_VAR **vars; + + vars = map_over (visible_and_exported, vcxt); + + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +static char ** +make_func_export_array () +{ + char **list; + SHELL_VAR **vars; + + vars = map_over_funcs (visible_and_exported); + if (vars == 0) + return (char **)NULL; + + list = make_env_array_from_var_list (vars); + + free (vars); + return (list); +} + +/* Add ENVSTR to the end of the exported environment, EXPORT_ENV. */ +#define add_to_export_env(envstr,do_alloc) \ +do \ + { \ + if (export_env_index >= (export_env_size - 1)) \ + { \ + export_env_size += 16; \ + export_env = strvec_resize (export_env, export_env_size); \ + } \ + export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \ + export_env[export_env_index] = (char *)NULL; \ + } while (0) + +/* Add ASSIGN to EXPORT_ENV, or supercede a previous assignment in the + array with the same left-hand side. Return the new EXPORT_ENV. */ +char ** +add_or_supercede_exported_var (assign, do_alloc) + char *assign; + int do_alloc; +{ + register int i; + int equal_offset; + + equal_offset = assignment (assign, 0); + if (equal_offset == 0) + return (export_env); + + /* If this is a function, then only supersede the function definition. + We do this by including the `=() {' in the comparison, like + initialize_shell_variables does. */ + if (assign[equal_offset + 1] == '(' && + strncmp (assign + equal_offset + 2, ") {", 3) == 0) /* } */ + equal_offset += 4; + + for (i = 0; i < export_env_index; i++) + { + if (STREQN (assign, export_env[i], equal_offset + 1)) + { + free (export_env[i]); + export_env[i] = do_alloc ? savestring (assign) : assign; + return (export_env); + } + } + add_to_export_env (assign, do_alloc); + return (export_env); +} + +static void +add_temp_array_to_env (temp_array, do_alloc, do_supercede) + char **temp_array; + int do_alloc, do_supercede; +{ + register int i; + + if (temp_array == 0) + return; + + for (i = 0; temp_array[i]; i++) + { + if (do_supercede) + export_env = add_or_supercede_exported_var (temp_array[i], do_alloc); + else + add_to_export_env (temp_array[i], do_alloc); + } + + free (temp_array); +} + +/* Make the environment array for the command about to be executed, if the + array needs making. Otherwise, do nothing. If a shell action could + change the array that commands receive for their environment, then the + code should `array_needs_making++'. + + The order to add to the array is: + temporary_env + list of var contexts whose head is shell_variables + shell_functions + + This is the shell variable lookup order. We add only new variable + names at each step, which allows local variables and variables in + the temporary environments to shadow variables in the global (or + any previous) scope. +*/ + +static int +n_shell_variables () +{ + VAR_CONTEXT *vc; + int n; + + for (n = 0, vc = shell_variables; vc; vc = vc->down) + n += HASH_ENTRIES (vc->table); + return n; +} + +void +maybe_make_export_env () +{ + register char **temp_array; + int new_size; + VAR_CONTEXT *tcxt; + + if (array_needs_making) + { + if (export_env) + strvec_flush (export_env); + + /* Make a guess based on how many shell variables and functions we + have. Since there will always be array variables, and array + variables are not (yet) exported, this will always be big enough + for the exported variables and functions. */ + new_size = n_shell_variables () + HASH_ENTRIES (shell_functions) + 1 + + HASH_ENTRIES (temporary_env); + if (new_size > export_env_size) + { + export_env_size = new_size; + export_env = strvec_resize (export_env, export_env_size); + } + export_env[export_env_index = 0] = (char *)NULL; + + /* Make a dummy variable context from the temporary_env, stick it on + the front of shell_variables, call make_var_export_array on the + whole thing to flatten it, and convert the list of SHELL_VAR *s + to the form needed by the environment. */ + if (temporary_env) + { + tcxt = new_var_context ((char *)NULL, 0); + tcxt->table = temporary_env; + tcxt->down = shell_variables; + } + else + tcxt = shell_variables; + + temp_array = make_var_export_array (tcxt); + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + if (tcxt != shell_variables) + free (tcxt); + +#if defined (RESTRICTED_SHELL) + /* Restricted shells may not export shell functions. */ + temp_array = restricted ? (char **)0 : make_func_export_array (); +#else + temp_array = make_func_export_array (); +#endif + if (temp_array) + add_temp_array_to_env (temp_array, 0, 0); + + array_needs_making = 0; + } +} + +/* This is an efficiency hack. PWD and OLDPWD are auto-exported, so + we will need to remake the exported environment every time we + change directories. `_' is always put into the environment for + every external command, so without special treatment it will always + cause the environment to be remade. + + If there is no other reason to make the exported environment, we can + just update the variables in place and mark the exported environment + as no longer needing a remake. */ +void +update_export_env_inplace (env_prefix, preflen, value) + char *env_prefix; + int preflen; + char *value; +{ + char *evar; + + evar = (char *)xmalloc (STRLEN (value) + preflen + 1); + strcpy (evar, env_prefix); + if (value) + strcpy (evar + preflen, value); + export_env = add_or_supercede_exported_var (evar, 0); +} + +/* We always put _ in the environment as the name of this command. */ +void +put_command_name_into_env (command_name) + char *command_name; +{ + update_export_env_inplace ("_=", 2, command_name); +} + +#if 0 /* UNUSED -- it caused too many problems */ +void +put_gnu_argv_flags_into_env (pid, flags_string) + intmax_t pid; + char *flags_string; +{ + char *dummy, *pbuf; + int l, fl; + + pbuf = itos (pid); + l = strlen (pbuf); + + fl = strlen (flags_string); + + dummy = (char *)xmalloc (l + fl + 30); + dummy[0] = '_'; + strcpy (dummy + 1, pbuf); + strcpy (dummy + 1 + l, "_GNU_nonoption_argv_flags_"); + dummy[l + 27] = '='; + strcpy (dummy + l + 28, flags_string); + + free (pbuf); + + export_env = add_or_supercede_exported_var (dummy, 0); +} +#endif + +/* **************************************************************** */ +/* */ +/* Managing variable contexts */ +/* */ +/* **************************************************************** */ + +/* Allocate and return a new variable context with NAME and FLAGS. + NAME can be NULL. */ + +VAR_CONTEXT * +new_var_context (name, flags) + char *name; + int flags; +{ + VAR_CONTEXT *vc; + + vc = (VAR_CONTEXT *)xmalloc (sizeof (VAR_CONTEXT)); + vc->name = name ? savestring (name) : (char *)NULL; + vc->scope = variable_context; + vc->flags = flags; + + vc->up = vc->down = (VAR_CONTEXT *)NULL; + vc->table = (HASH_TABLE *)NULL; + + return vc; +} + +/* Free a variable context and its data, including the hash table. Dispose + all of the variables. */ +void +dispose_var_context (vc) + VAR_CONTEXT *vc; +{ + FREE (vc->name); + + if (vc->table) + { + delete_all_variables (vc->table); + hash_dispose (vc->table); + } + + free (vc); +} + +/* Set VAR's scope level to the current variable context. */ +static int +set_context (var) + SHELL_VAR *var; +{ + return (var->context = variable_context); +} + +/* Make a new variable context with NAME and FLAGS and a HASH_TABLE of + temporary variables, and push it onto shell_variables. This is + for shell functions. */ +VAR_CONTEXT * +push_var_context (name, flags, tempvars) + char *name; + int flags; + HASH_TABLE *tempvars; +{ + VAR_CONTEXT *vc; + + vc = new_var_context (name, flags); + vc->table = tempvars; + if (tempvars) + { + /* Have to do this because the temp environment was created before + variable_context was incremented. */ + flatten (tempvars, set_context, (VARLIST *)NULL, 0); + vc->flags |= VC_HASTMPVAR; + } + vc->down = shell_variables; + shell_variables->up = vc; + + return (shell_variables = vc); +} + +static void +push_func_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + if (tempvar_p (var) && (posixly_correct || (var->attributes & att_propagate))) + { + /* XXX - should we set v->context here? */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0); + if (shell_variables == global_variables) + var->attributes &= ~(att_tempvar|att_propagate); + else + shell_variables->flags |= VC_HASTMPVAR; + v->attributes |= var->attributes; + } + + dispose_variable (var); +} + +/* Pop the top context off of VCXT and dispose of it, returning the rest of + the stack. */ +void +pop_var_context () +{ + VAR_CONTEXT *ret, *vcxt; + + vcxt = shell_variables; + if (vc_isfuncenv (vcxt) == 0) + { + internal_error (_("pop_var_context: head of shell_variables not a function context")); + return; + } + + if (ret = vcxt->down) + { + ret->up = (VAR_CONTEXT *)NULL; + shell_variables = ret; + if (vcxt->table) + hash_flush (vcxt->table, push_func_var); + dispose_var_context (vcxt); + } + else + internal_error (_("pop_var_context: no global_variables context")); +} + +/* Delete the HASH_TABLEs for all variable contexts beginning at VCXT, and + all of the VAR_CONTEXTs except GLOBAL_VARIABLES. */ +void +delete_all_contexts (vcxt) + VAR_CONTEXT *vcxt; +{ + VAR_CONTEXT *v, *t; + + for (v = vcxt; v != global_variables; v = t) + { + t = v->down; + dispose_var_context (v); + } + + delete_all_variables (global_variables->table); + shell_variables = global_variables; +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping temporary variable scopes */ +/* */ +/* **************************************************************** */ + +VAR_CONTEXT * +push_scope (flags, tmpvars) + int flags; + HASH_TABLE *tmpvars; +{ + return (push_var_context ((char *)NULL, flags, tmpvars)); +} + +static void +push_exported_var (data) + PTR_T data; +{ + SHELL_VAR *var, *v; + + var = (SHELL_VAR *)data; + + /* If a temp var had its export attribute set, or it's marked to be + propagated, bind it in the previous scope before disposing it. */ + if (exported_p (var) || (var->attributes & att_propagate)) + { + var->attributes &= ~att_tempvar; /* XXX */ + v = bind_variable_internal (var->name, value_cell (var), shell_variables->table, 0); + if (shell_variables == global_variables) + var->attributes &= ~att_propagate; + v->attributes |= var->attributes; + } + + dispose_variable (var); +} + +void +pop_scope (is_special) + int is_special; +{ + VAR_CONTEXT *vcxt, *ret; + + vcxt = shell_variables; + if (vc_istempscope (vcxt) == 0) + { + internal_error (_("pop_scope: head of shell_variables not a temporary environment scope")); + return; + } + + ret = vcxt->down; + if (ret) + ret->up = (VAR_CONTEXT *)NULL; + + shell_variables = ret; + + /* Now we can take care of merging variables in VCXT into set of scopes + whose head is RET (shell_variables). */ + FREE (vcxt->name); + if (vcxt->table) + { + if (is_special) + hash_flush (vcxt->table, push_func_var); + else + hash_flush (vcxt->table, push_exported_var); + hash_dispose (vcxt->table); + } + free (vcxt); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* **************************************************************** */ +/* */ +/* Pushing and Popping function contexts */ +/* */ +/* **************************************************************** */ + +static WORD_LIST **dollar_arg_stack = (WORD_LIST **)NULL; +static int dollar_arg_stack_slots; +static int dollar_arg_stack_index; + +/* XXX - we might want to consider pushing and popping the `getopts' state + when we modify the positional parameters. */ +void +push_context (name, is_subshell, tempvars) + char *name; /* function name */ + int is_subshell; + HASH_TABLE *tempvars; +{ + if (is_subshell == 0) + push_dollar_vars (); + variable_context++; + push_var_context (name, VC_FUNCENV, tempvars); +} + +/* Only called when subshell == 0, so we don't need to check, and can + unconditionally pop the dollar vars off the stack. */ +void +pop_context () +{ + pop_dollar_vars (); + variable_context--; + pop_var_context (); + + sv_ifs ("IFS"); /* XXX here for now */ +} + +/* Save the existing positional parameters on a stack. */ +void +push_dollar_vars () +{ + if (dollar_arg_stack_index + 2 > dollar_arg_stack_slots) + { + dollar_arg_stack = (WORD_LIST **) + xrealloc (dollar_arg_stack, (dollar_arg_stack_slots += 10) + * sizeof (WORD_LIST **)); + } + dollar_arg_stack[dollar_arg_stack_index++] = list_rest_of_args (); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; +} + +/* Restore the positional parameters from our stack. */ +void +pop_dollar_vars () +{ + if (!dollar_arg_stack || dollar_arg_stack_index == 0) + return; + + remember_args (dollar_arg_stack[--dollar_arg_stack_index], 1); + dispose_words (dollar_arg_stack[dollar_arg_stack_index]); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; + set_dollar_vars_unchanged (); +} + +void +dispose_saved_dollar_vars () +{ + if (!dollar_arg_stack || dollar_arg_stack_index == 0) + return; + + dispose_words (dollar_arg_stack[dollar_arg_stack_index]); + dollar_arg_stack[dollar_arg_stack_index] = (WORD_LIST *)NULL; +} + +/* Manipulate the special BASH_ARGV and BASH_ARGC variables. */ + +void +push_args (list) + WORD_LIST *list; +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + WORD_LIST *l; + arrayind_t i; + char *t; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + for (l = list, i = 0; l; l = l->next, i++) + array_push (bash_argv_a, l->word->word); + + t = itos (i); + array_push (bash_argc_a, t); + free (t); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/* Remove arguments from BASH_ARGV array. Pop top element off BASH_ARGC + array and use that value as the count of elements to remove from + BASH_ARGV. */ +void +pop_args () +{ +#if defined (ARRAY_VARS) && defined (DEBUGGER) + SHELL_VAR *bash_argv_v, *bash_argc_v; + ARRAY *bash_argv_a, *bash_argc_a; + ARRAY_ELEMENT *ce; + intmax_t i; + + GET_ARRAY_FROM_VAR ("BASH_ARGV", bash_argv_v, bash_argv_a); + GET_ARRAY_FROM_VAR ("BASH_ARGC", bash_argc_v, bash_argc_a); + + ce = array_shift (bash_argc_a, 1, 0); + if (ce == 0 || legal_number (element_value (ce), &i) == 0) + i = 0; + + for ( ; i > 0; i--) + array_pop (bash_argv_a); + array_dispose_element (ce); +#endif /* ARRAY_VARS && DEBUGGER */ +} + +/************************************************* + * * + * Functions to manage special variables * + * * + *************************************************/ + +/* Extern declarations for variables this code has to manage. */ +extern int eof_encountered, eof_encountered_limit, ignoreeof; + +#if defined (READLINE) +extern int no_line_editing; +extern int hostname_list_initialized; +#endif + +/* An alist of name.function for each special variable. Most of the + functions don't do much, and in fact, this would be faster with a + switch statement, but by the end of this file, I am sick of switch + statements. */ + +#define SET_INT_VAR(name, intvar) intvar = find_variable (name) != 0 + +/* This table will be sorted with qsort() the first time it's accessed. */ +struct name_and_function { + char *name; + sh_sv_func_t *function; +}; + +static struct name_and_function special_vars[] = { + { "GLOBIGNORE", sv_globignore }, + +#if defined (HISTORY) + { "HISTCONTROL", sv_history_control }, + { "HISTFILESIZE", sv_histsize }, + { "HISTIGNORE", sv_histignore }, + { "HISTSIZE", sv_histsize }, + { "HISTTIMEFORMAT", sv_histtimefmt }, +#endif + +#if defined (READLINE) + { "HOSTFILE", sv_hostfile }, +#endif + + { "IFS", sv_ifs }, + { "IGNOREEOF", sv_ignoreeof }, + + { "LANG", sv_locale }, + { "LC_ALL", sv_locale }, + { "LC_COLLATE", sv_locale }, + { "LC_CTYPE", sv_locale }, + { "LC_MESSAGES", sv_locale }, + { "LC_NUMERIC", sv_locale }, + + { "MAIL", sv_mail }, + { "MAILCHECK", sv_mail }, + { "MAILPATH", sv_mail }, + + { "OPTERR", sv_opterr }, + { "OPTIND", sv_optind }, + + { "PATH", sv_path }, + { "POSIXLY_CORRECT", sv_strict_posix }, + +#if defined (READLINE) + { "TERM", sv_terminal }, + { "TERMCAP", sv_terminal }, + { "TERMINFO", sv_terminal }, +#endif /* READLINE */ + + { "TEXTDOMAIN", sv_locale }, + { "TEXTDOMAINDIR", sv_locale }, + +#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) + { "TZ", sv_tz }, +#endif + +#if defined (HISTORY) && defined (BANG_HISTORY) + { "histchars", sv_histchars }, +#endif /* HISTORY && BANG_HISTORY */ + + { "ignoreeof", sv_ignoreeof }, + + { (char *)0, (sh_sv_func_t *)0 } +}; + +#define N_SPECIAL_VARS (sizeof (special_vars) / sizeof (special_vars[0]) - 1) + +static int +sv_compare (sv1, sv2) + struct name_and_function *sv1, *sv2; +{ + int r; + + if ((r = sv1->name[0] - sv2->name[0]) == 0) + r = strcmp (sv1->name, sv2->name); + return r; +} + +static inline int +find_special_var (name) + const char *name; +{ + register int i, r; + + for (i = 0; special_vars[i].name; i++) + { + r = special_vars[i].name[0] - name[0]; + if (r == 0) + r = strcmp (special_vars[i].name, name); + if (r == 0) + return i; + else if (r > 0) + /* Can't match any of rest of elements in sorted list. Take this out + if it causes problems in certain environments. */ + break; + } + return -1; +} + +/* The variable in NAME has just had its state changed. Check to see if it + is one of the special ones where something special happens. */ +void +stupidly_hack_special_variables (name) + char *name; +{ + static int sv_sorted = 0; + int i; + + if (sv_sorted == 0) /* shouldn't need, but it's fairly cheap. */ + { + qsort (special_vars, N_SPECIAL_VARS, sizeof (special_vars[0]), + (QSFUNC *)sv_compare); + sv_sorted = 1; + } + + i = find_special_var (name); + if (i != -1) + (*(special_vars[i].function)) (name); +} + +void +sv_ifs (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable ("IFS"); + setifs (v); +} + +/* What to do just after the PATH variable has changed. */ +void +sv_path (name) + char *name; +{ + /* hash -r */ + phash_flush (); +} + +/* What to do just after one of the MAILxxxx variables has changed. NAME + is the name of the variable. This is called with NAME set to one of + MAIL, MAILCHECK, or MAILPATH. */ +void +sv_mail (name) + char *name; +{ + /* If the time interval for checking the files has changed, then + reset the mail timer. Otherwise, one of the pathname vars + to the users mailbox has changed, so rebuild the array of + filenames. */ + if (name[4] == 'C') /* if (strcmp (name, "MAILCHECK") == 0) */ + reset_mail_timer (); + else + { + free_mail_files (); + remember_mail_dates (); + } +} + +/* What to do when GLOBIGNORE changes. */ +void +sv_globignore (name) + char *name; +{ + setup_glob_ignore (name); +} + +#if defined (READLINE) +/* What to do just after one of the TERMxxx variables has changed. + If we are an interactive shell, then try to reset the terminal + information in readline. */ +void +sv_terminal (name) + char *name; +{ + if (interactive_shell && no_line_editing == 0) + rl_reset_terminal (get_string_value ("TERM")); +} + +void +sv_hostfile (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + if (v == 0) + clear_hostname_list (); + else + hostname_list_initialized = 0; +} +#endif /* READLINE */ + +#if defined (HISTORY) +/* What to do after the HISTSIZE or HISTFILESIZE variables change. + If there is a value for this HISTSIZE (and it is numeric), then stifle + the history. Otherwise, if there is NO value for this variable, + unstifle the history. If name is HISTFILESIZE, and its value is + numeric, truncate the history file to hold no more than that many + lines. */ +void +sv_histsize (name) + char *name; +{ + char *temp; + intmax_t num; + + temp = get_string_value (name); + + if (temp && *temp) + { + if (legal_number (temp, &num)) + { + if (name[4] == 'S') + { + stifle_history (num); + num = where_history (); + if (history_lines_this_session > num) + history_lines_this_session = num; + } + else + { + history_truncate_file (get_string_value ("HISTFILE"), (int)num); + if (num <= history_lines_in_file) + history_lines_in_file = num; + } + } + } + else if (name[4] == 'S') + unstifle_history (); +} + +/* What to do after the HISTIGNORE variable changes. */ +void +sv_histignore (name) + char *name; +{ + setup_history_ignore (name); +} + +/* What to do after the HISTCONTROL variable changes. */ +void +sv_history_control (name) + char *name; +{ + char *temp; + char *val; + int tptr; + + history_control = 0; + temp = get_string_value (name); + + if (temp == 0 || *temp == 0) + return; + + tptr = 0; + while (val = extract_colon_unit (temp, &tptr)) + { + if (STREQ (val, "ignorespace")) + history_control |= HC_IGNSPACE; + else if (STREQ (val, "ignoredups")) + history_control |= HC_IGNDUPS; + else if (STREQ (val, "ignoreboth")) + history_control |= HC_IGNBOTH; + else if (STREQ (val, "erasedups")) + history_control |= HC_ERASEDUPS; + + free (val); + } +} + +#if defined (BANG_HISTORY) +/* Setting/unsetting of the history expansion character. */ +void +sv_histchars (name) + char *name; +{ + char *temp; + + temp = get_string_value (name); + if (temp) + { + history_expansion_char = *temp; + if (temp[0] && temp[1]) + { + history_subst_char = temp[1]; + if (temp[2]) + history_comment_char = temp[2]; + } + } + else + { + history_expansion_char = '!'; + history_subst_char = '^'; + history_comment_char = '#'; + } +} +#endif /* BANG_HISTORY */ + +void +sv_histtimefmt (name) + char *name; +{ + SHELL_VAR *v; + + v = find_variable (name); + history_write_timestamps = (v != 0); +} +#endif /* HISTORY */ + +#if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) +void +sv_tz (name) + char *name; +{ + tzset (); +} +#endif + +/* If the variable exists, then the value of it can be the number + of times we actually ignore the EOF. The default is small, + (smaller than csh, anyway). */ +void +sv_ignoreeof (name) + char *name; +{ + SHELL_VAR *tmp_var; + char *temp; + + eof_encountered = 0; + + tmp_var = find_variable (name); + ignoreeof = tmp_var != 0; + temp = tmp_var ? value_cell (tmp_var) : (char *)NULL; + if (temp) + eof_encountered_limit = (*temp && all_digits (temp)) ? atoi (temp) : 10; + set_shellopts (); /* make sure `ignoreeof' is/is not in $SHELLOPTS */ +} + +void +sv_optind (name) + char *name; +{ + char *tt; + int s; + + tt = get_string_value ("OPTIND"); + if (tt && *tt) + { + s = atoi (tt); + + /* According to POSIX, setting OPTIND=1 resets the internal state + of getopt (). */ + if (s < 0 || s == 1) + s = 0; + } + else + s = 0; + getopts_reset (s); +} + +void +sv_opterr (name) + char *name; +{ + char *tt; + + tt = get_string_value ("OPTERR"); + sh_opterr = (tt && *tt) ? atoi (tt) : 1; +} + +void +sv_strict_posix (name) + char *name; +{ + SET_INT_VAR (name, posixly_correct); + posix_initialize (posixly_correct); +#if defined (READLINE) + if (interactive_shell) + posix_readline_initialize (posixly_correct); +#endif /* READLINE */ + set_shellopts (); /* make sure `posix' is/is not in $SHELLOPTS */ +} + +void +sv_locale (name) + char *name; +{ + char *v; + + v = get_string_value (name); + if (name[0] == 'L' && name[1] == 'A') /* LANG */ + set_lang (name, v); + else + set_locale_var (name, v); /* LC_*, TEXTDOMAIN* */ +} + +#if defined (ARRAY_VARS) +void +set_pipestatus_array (ps, nproc) + int *ps; + int nproc; +{ + SHELL_VAR *v; + ARRAY *a; + ARRAY_ELEMENT *ae; + register int i; + char *t, tbuf[INT_STRLEN_BOUND(int) + 1]; + + v = find_variable ("PIPESTATUS"); + if (v == 0) + v = make_new_array_variable ("PIPESTATUS"); + if (array_p (v) == 0) + return; /* Do nothing if not an array variable. */ + a = array_cell (v); + + if (a == 0 || array_num_elements (a) == 0) + { + for (i = 0; i < nproc; i++) /* was ps[i] != -1, not i < nproc */ + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + return; + } + + /* Fast case */ + if (array_num_elements (a) == nproc && nproc == 1) + { + ae = element_forw (a->head); + free (element_value (ae)); + ae->value = itos (ps[0]); + } + else if (array_num_elements (a) <= nproc) + { + /* modify in array_num_elements members in place, then add */ + ae = a->head; + for (i = 0; i < array_num_elements (a); i++) + { + ae = element_forw (ae); + free (element_value (ae)); + ae->value = itos (ps[i]); + } + /* add any more */ + for ( ; i < nproc; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } + else + { + /* deleting elements. it's faster to rebuild the array. */ + array_flush (a); + for (i = 0; ps[i] != -1; i++) + { + t = inttostr (ps[i], tbuf, sizeof (tbuf)); + array_insert (a, i, t); + } + } +} +#endif + +void +set_pipestatus_from_exit (s) + int s; +{ +#if defined (ARRAY_VARS) + static int v[2] = { 0, -1 }; + + v[0] = s; + set_pipestatus_array (v, 1); +#endif +} -- 2.7.4