/* shell.c -- GNU's idea of the POSIX shell specification. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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. Birthdate: Sunday, January 10th, 1988. Initial author: Brian Fox */ #define INSTALL_DEBUG_MODE #include "config.h" #include "bashtypes.h" #ifndef _MINIX # include #endif #include "posixstat.h" #include "posixtime.h" #include "bashansi.h" #include #include #include #include "filecntl.h" #include #if defined (HAVE_UNISTD_H) # include #endif #define NEED_SH_SETLINEBUF_DECL /* used in externs.h */ #include "shell.h" #include "flags.h" #include "trap.h" #include "mailcheck.h" #include "builtins.h" #include "builtins/common.h" #if defined (JOB_CONTROL) #include "jobs.h" #endif /* JOB_CONTROL */ #include "input.h" #include "execute_cmd.h" #include "findcmd.h" #if defined (HISTORY) # include "bashhist.h" # include #endif #include #include #if defined (__OPENNT) # include #endif #if !defined (HAVE_GETPW_DECLS) extern struct passwd *getpwuid (); #endif /* !HAVE_GETPW_DECLS */ #if !defined (errno) extern int errno; #endif #if defined (NO_MAIN_ENV_ARG) extern char **environ; /* used if no third argument to main() */ #endif extern char *dist_version, *release_status; extern int patch_level, build_version; extern int shell_level; extern int subshell_environment; extern int last_command_exit_value; extern int line_number; extern char *primary_prompt, *secondary_prompt; extern int expand_aliases; extern char *this_command_name; extern int array_needs_making; /* Non-zero means that this shell has already been run; i.e. you should call shell_reinitialize () if you need to start afresh. */ int shell_initialized = 0; COMMAND *global_command = (COMMAND *)NULL; /* Information about the current user. */ struct user_info current_user = { (uid_t)-1, (uid_t)-1, (gid_t)-1, (gid_t)-1, (char *)NULL, (char *)NULL, (char *)NULL }; /* The current host's name. */ char *current_host_name = (char *)NULL; /* Non-zero means that this shell is a login shell. Specifically: 0 = not login shell. 1 = login shell from getty (or equivalent fake out) -1 = login shell from "--login" flag. -2 = both from getty, and from flag. */ int login_shell = 0; /* Non-zero means that at this moment, the shell is interactive. In general, this means that the shell is at this moment reading input from the keyboard. */ int interactive = 0; /* Non-zero means that the shell was started as an interactive shell. */ int interactive_shell = 0; /* Non-zero means to send a SIGHUP to all jobs when an interactive login shell exits. */ int hup_on_exit = 0; /* Tells what state the shell was in when it started: 0 = non-interactive shell script 1 = interactive 2 = -c command This is a superset of the information provided by interactive_shell. */ int startup_state = 0; /* Special debugging helper. */ int debugging_login_shell = 0; /* The environment that the shell passes to other commands. */ char **shell_environment; /* Non-zero when we are executing a top-level command. */ int executing = 0; /* The number of commands executed so far. */ int current_command_number = 1; /* Non-zero is the recursion depth for commands. */ int indirection_level = 0; /* The name of this shell, as taken from argv[0]. */ char *shell_name = (char *)NULL; /* time in seconds when the shell was started */ time_t shell_start_time; /* Are we running in an emacs shell window? */ int running_under_emacs; /* The name of the .(shell)rc file. */ static char *bashrc_file = "~/.bashrc"; /* Non-zero means to act more like the Bourne shell on startup. */ static int act_like_sh; /* Non-zero if this shell is being run by `su'. */ static int su_shell; /* Non-zero if we have already expanded and sourced $ENV. */ static int sourced_env; /* Is this shell running setuid? */ static int running_setuid; /* Values for the long-winded argument names. */ static int debugging; /* Do debugging things. */ static int no_rc; /* Don't execute ~/.bashrc */ static int no_profile; /* Don't execute .profile */ static int do_version; /* Display interesting version info. */ static int make_login_shell; /* Make this shell be a `-bash' shell. */ static int want_initial_help; /* --help option */ int no_line_editing = 0; /* Don't do fancy line editing. */ int posixly_correct = 0; /* Non-zero means posix.2 superset. */ int dump_translatable_strings; /* Dump strings in $"...", don't execute. */ int dump_po_strings; /* Dump strings in $"..." in po format */ int wordexp_only = 0; /* Do word expansion only */ /* Some long-winded argument names. These are obviously new. */ #define Int 1 #define Charp 2 struct { char *name; int type; int *int_value; char **char_value; } long_args[] = { { "debug", Int, &debugging, (char **)0x0 }, { "dump-po-strings", Int, &dump_po_strings, (char **)0x0 }, { "dump-strings", Int, &dump_translatable_strings, (char **)0x0 }, { "help", Int, &want_initial_help, (char **)0x0 }, { "init-file", Charp, (int *)0x0, &bashrc_file }, { "login", Int, &make_login_shell, (char **)0x0 }, { "noediting", Int, &no_line_editing, (char **)0x0 }, { "noprofile", Int, &no_profile, (char **)0x0 }, { "norc", Int, &no_rc, (char **)0x0 }, { "posix", Int, &posixly_correct, (char **)0x0 }, { "rcfile", Charp, (int *)0x0, &bashrc_file }, #if defined (RESTRICTED_SHELL) { "restricted", Int, &restricted, (char **)0x0 }, #endif { "verbose", Int, &echo_input_at_read, (char **)0x0 }, { "version", Int, &do_version, (char **)0x0 }, { "wordexp", Int, &wordexp_only, (char **)0x0 }, { (char *)0x0, Int, (int *)0x0, (char **)0x0 } }; /* These are extern so execute_simple_command can set them, and then longjmp back to main to execute a shell script, instead of calling main () again and resulting in indefinite, possibly fatal, stack growth. */ procenv_t subshell_top_level; int subshell_argc; char **subshell_argv; char **subshell_envp; #if defined (BUFFERED_INPUT) /* The file descriptor from which the shell is reading input. */ int default_buffered_input = -1; #endif /* The following two variables are not static so they can show up in $-. */ int read_from_stdin; /* -s flag supplied */ int want_pending_command; /* -c flag supplied */ static int shell_reinitialized = 0; static char *local_pending_command; static FILE *default_input; static STRING_INT_ALIST *shopt_alist; static int shopt_ind = 0, shopt_len = 0; static int parse_long_options __P((char **, int, int)); static int parse_shell_options __P((char **, int, int)); static int bind_args __P((char **, int, int, int)); static void add_shopt_to_alist __P((char *, int)); static void run_shopt_alist __P((void)); static void execute_env_file __P((char *)); static void run_startup_files __P((void)); static int open_shell_script __P((char *)); static void set_bash_input __P((void)); static int run_one_command __P((char *)); static int run_wordexp __P((char *)); static int uidget __P((void)); static int isnetconn __P((int)); static void init_interactive __P((void)); static void init_noninteractive __P((void)); static void set_shell_name __P((char *)); static void shell_initialize __P((void)); static void shell_reinitialize __P((void)); static void show_shell_usage __P((FILE *, int)); #ifdef __CYGWIN__ static void _cygwin32_check_tmp () { struct stat sb; if (stat ("/tmp", &sb) < 0) internal_warning ("could not find /tmp, please create!"); else { if (S_ISDIR (sb.st_mode) == 0) internal_warning ("/tmp must be a valid directory name"); } } #endif /* __CYGWIN__ */ #if defined (NO_MAIN_ENV_ARG) /* systems without third argument to main() */ int main (argc, argv) int argc; char **argv; #else /* !NO_MAIN_ENV_ARG */ int main (argc, argv, env) int argc; char **argv, **env; #endif /* !NO_MAIN_ENV_ARG */ { register int i; int code, old_errexit_flag; #if defined (RESTRICTED_SHELL) int saverst; #endif volatile int locally_skip_execution; volatile int arg_index, top_level_arg_index; #ifdef __OPENNT char **env; env = environ; #endif /* __OPENNT */ USE_VAR(argc); USE_VAR(argv); USE_VAR(env); USE_VAR(code); USE_VAR(old_errexit_flag); #if defined (RESTRICTED_SHELL) USE_VAR(saverst); #endif /* Catch early SIGINTs. */ code = setjmp (top_level); if (code) exit (2); #if defined (USING_BASH_MALLOC) && defined (DEBUG) # if 0 /* memory tracing */ malloc_set_trace(1); # endif # if 0 malloc_set_register (1); # endif #endif check_dev_tty (); #ifdef __CYGWIN__ _cygwin32_check_tmp (); #endif /* __CYGWIN__ */ /* Wait forever if we are debugging a login shell. */ while (debugging_login_shell); set_default_locale (); running_setuid = uidget (); if (getenv ("POSIXLY_CORRECT") || getenv ("POSIX_PEDANTIC")) posixly_correct = 1; #if defined (USE_GNU_MALLOC_LIBRARY) mcheck (programming_error, (void (*) ())0); #endif /* USE_GNU_MALLOC_LIBRARY */ if (setjmp (subshell_top_level)) { argc = subshell_argc; argv = subshell_argv; env = subshell_envp; sourced_env = 0; } shell_reinitialized = 0; /* Initialize `local' variables for all `invocations' of main (). */ arg_index = 1; local_pending_command = (char *)NULL; want_pending_command = locally_skip_execution = read_from_stdin = 0; default_input = stdin; #if defined (BUFFERED_INPUT) default_buffered_input = -1; #endif /* Fix for the `infinite process creation' bug when running shell scripts from startup files on System V. */ login_shell = make_login_shell = 0; /* If this shell has already been run, then reinitialize it to a vanilla state. */ if (shell_initialized || shell_name) { /* Make sure that we do not infinitely recurse as a login shell. */ if (*shell_name == '-') shell_name++; shell_reinitialize (); if (setjmp (top_level)) exit (2); } shell_environment = env; set_shell_name (argv[0]); shell_start_time = NOW; /* NOW now defined in general.h */ /* Parse argument flags from the input line. */ /* Find full word arguments first. */ arg_index = parse_long_options (argv, arg_index, argc); if (want_initial_help) { show_shell_usage (stdout, 1); exit (EXECUTION_SUCCESS); } if (do_version) { show_shell_version (1); exit (EXECUTION_SUCCESS); } /* If user supplied the "--login" flag, then set and invert LOGIN_SHELL. */ if (make_login_shell) { login_shell++; login_shell = -login_shell; } set_login_shell (login_shell != 0); /* All done with full word options; do standard shell option parsing.*/ this_command_name = shell_name; /* for error reporting */ arg_index = parse_shell_options (argv, arg_index, argc); if (dump_po_strings) dump_translatable_strings = 1; if (dump_translatable_strings) read_but_dont_execute = 1; if (running_setuid && privileged_mode == 0) disable_priv_mode (); /* Need to get the argument to a -c option processed in the above loop. The next arg is a command to execute, and the following args are $0...$n respectively. */ if (want_pending_command) { local_pending_command = argv[arg_index]; if (local_pending_command == 0) { report_error ("option `-c' requires an argument"); exit (EX_USAGE); } arg_index++; } this_command_name = (char *)NULL; /* First, let the outside world know about our interactive status. A shell is interactive if the `-i' flag was given, or if all of the following conditions are met: no -c command no arguments remaining or the -s flag given standard input is a terminal standard output is a terminal Refer to Posix.2, the description of the `sh' utility. */ if (forced_interactive || /* -i flag */ (!local_pending_command && /* No -c command and ... */ wordexp_only == 0 && /* No --wordexp and ... */ ((arg_index == argc) || /* no remaining args or... */ read_from_stdin) && /* -s flag with args, and */ isatty (fileno (stdin)) && /* Input is a terminal and */ isatty (fileno (stdout)))) /* output is a terminal. */ init_interactive (); else init_noninteractive (); #define CLOSE_FDS_AT_LOGIN #if defined (CLOSE_FDS_AT_LOGIN) /* * Some systems have the bad habit of starting login shells with lots of open * file descriptors. For instance, most systems that have picked up the * pre-4.0 Sun YP code leave a file descriptor open each time you call one * of the getpw* functions, and it's set to be open across execs. That * means one for login, one for xterm, one for shelltool, etc. */ if (login_shell && interactive_shell) { for (i = 3; i < 20; i++) close (i); } #endif /* CLOSE_FDS_AT_LOGIN */ /* If we're in a strict Posix.2 mode, turn on interactive comments and other Posix.2 things. */ if (posixly_correct) { bind_variable ("POSIXLY_CORRECT", "y"); sv_strict_posix ("POSIXLY_CORRECT"); } /* Now we run the shopt_alist and process the options. */ if (shopt_alist) run_shopt_alist (); /* From here on in, the shell must be a normal functioning shell. Variables from the environment are expected to be set, etc. */ shell_initialize (); set_default_locale_vars (); if (interactive_shell) { char *term; term = getenv ("TERM"); no_line_editing |= term && (STREQ (term, "emacs")); term = getenv ("EMACS"); running_under_emacs = term ? ((strmatch ("*term*", term, 0) == 0) ? 2 : 1) : 0; } top_level_arg_index = arg_index; old_errexit_flag = exit_immediately_on_error; /* Give this shell a place to longjmp to before executing the startup files. This allows users to press C-c to abort the lengthy startup. */ code = setjmp (top_level); if (code) { if (code == EXITPROG) exit_shell (last_command_exit_value); else { #if defined (JOB_CONTROL) /* Reset job control, since run_startup_files turned it off. */ set_job_control (interactive_shell); #endif /* Reset value of `set -e', since it's turned off before running the startup files. */ exit_immediately_on_error += old_errexit_flag; locally_skip_execution++; } } arg_index = top_level_arg_index; /* Execute the start-up scripts. */ if (interactive_shell == 0) { makunbound ("PS1", shell_variables); makunbound ("PS2", shell_variables); interactive = 0; expand_aliases = posixly_correct; } else { change_flag ('i', FLAG_ON); interactive = 1; } #if defined (RESTRICTED_SHELL) /* Set restricted_shell based on whether the basename of $0 indicates that the shell should be restricted or if the `-r' option was supplied at startup. */ restricted_shell = shell_is_restricted (shell_name); /* If the `-r' option is supplied at invocation, make sure that the shell is not in restricted mode when running the startup files. */ saverst = restricted; restricted = 0; #endif /* The startup files are run with `set -e' temporarily disabled. */ if (locally_skip_execution == 0 && running_setuid == 0) { old_errexit_flag = exit_immediately_on_error; exit_immediately_on_error = 0; run_startup_files (); exit_immediately_on_error += old_errexit_flag; } /* If we are invoked as `sh', turn on Posix mode. */ if (act_like_sh) { bind_variable ("POSIXLY_CORRECT", "y"); sv_strict_posix ("POSIXLY_CORRECT"); } #if defined (RESTRICTED_SHELL) /* Turn on the restrictions after executing the startup files. This means that `bash -r' or `set -r' invoked from a startup file will turn on the restrictions after the startup files are executed. */ restricted = saverst || restricted; if (shell_reinitialized == 0) maybe_make_restricted (shell_name); #endif /* RESTRICTED_SHELL */ if (wordexp_only) { startup_state = 3; last_command_exit_value = run_wordexp (argv[arg_index]); exit_shell (last_command_exit_value); } if (local_pending_command) { arg_index = bind_args (argv, arg_index, argc, 0); startup_state = 2; #if defined (ONESHOT) executing = 1; run_one_command (local_pending_command); exit_shell (last_command_exit_value); #else /* ONESHOT */ with_input_from_string (local_pending_command, "-c"); goto read_and_execute; #endif /* !ONESHOT */ } /* Get possible input filename and set up default_buffered_input or default_input as appropriate. */ if (arg_index != argc && read_from_stdin == 0) { open_shell_script (argv[arg_index]); arg_index++; } else if (interactive == 0) /* In this mode, bash is reading a script from stdin, which is a pipe or redirected file. */ #if defined (BUFFERED_INPUT) default_buffered_input = fileno (stdin); /* == 0 */ #else setbuf (default_input, (char *)NULL); #endif /* !BUFFERED_INPUT */ set_bash_input (); /* Bind remaining args to $1 ... $n */ arg_index = bind_args (argv, arg_index, argc, 1); /* Do the things that should be done only for interactive shells. */ if (interactive_shell) { /* Set up for checking for presence of mail. */ remember_mail_dates (); reset_mail_timer (); #if defined (HISTORY) /* Initialize the interactive history stuff. */ bash_initialize_history (); if (shell_initialized == 0) load_history (); #endif /* HISTORY */ /* Initialize terminal state for interactive shells after the .bash_profile and .bashrc are interpreted. */ get_tty_state (); } #if !defined (ONESHOT) read_and_execute: #endif /* !ONESHOT */ shell_initialized = 1; /* Read commands until exit condition. */ reader_loop (); exit_shell (last_command_exit_value); } static int parse_long_options (argv, arg_start, arg_end) char **argv; int arg_start, arg_end; { int arg_index, longarg, i; char *arg_string; arg_index = arg_start; while ((arg_index != arg_end) && (arg_string = argv[arg_index]) && (*arg_string == '-')) { longarg = 0; /* Make --login equivalent to -login. */ if (arg_string[1] == '-' && arg_string[2]) { longarg = 1; arg_string++; } for (i = 0; long_args[i].name; i++) { if (STREQ (arg_string + 1, long_args[i].name)) { if (long_args[i].type == Int) *long_args[i].int_value = 1; else if (argv[++arg_index] == 0) { report_error ("option `%s' requires an argument", long_args[i].name); exit (EX_USAGE); } else *long_args[i].char_value = argv[arg_index]; break; } } if (long_args[i].name == 0) { if (longarg) { report_error ("%s: unrecognized option", argv[arg_index]); show_shell_usage (stderr, 0); exit (EX_USAGE); } break; /* No such argument. Maybe flag arg. */ } arg_index++; } return (arg_index); } static int parse_shell_options (argv, arg_start, arg_end) char **argv; int arg_start, arg_end; { int arg_index; int arg_character, on_or_off, next_arg, i; char *o_option, *arg_string; arg_index = arg_start; while (arg_index != arg_end && (arg_string = argv[arg_index]) && (*arg_string == '-' || *arg_string == '+')) { /* There are flag arguments, so parse them. */ next_arg = arg_index + 1; /* A single `-' signals the end of options. From the 4.3 BSD sh. An option `--' means the same thing; this is the standard getopt(3) meaning. */ if (arg_string[0] == '-' && (arg_string[1] == '\0' || (arg_string[1] == '-' && arg_string[2] == '\0'))) return (next_arg); i = 1; on_or_off = arg_string[0]; while (arg_character = arg_string[i++]) { switch (arg_character) { case 'c': want_pending_command = 1; break; case 's': read_from_stdin = 1; break; case 'o': o_option = argv[next_arg]; if (o_option == 0) { list_minus_o_opts (-1, (on_or_off == '-') ? 0 : 1); break; } if (set_minus_o_option (on_or_off, o_option) != EXECUTION_SUCCESS) exit (EX_USAGE); next_arg++; break; case 'O': /* Since some of these can be overridden by the normal interactive/non-interactive shell initialization or initializing posix mode, we save the options and process them after initialization. */ o_option = argv[next_arg]; if (o_option == 0) { shopt_listopt (o_option, (on_or_off == '-') ? 0 : 1); break; } add_shopt_to_alist (o_option, on_or_off); next_arg++; break; case 'D': dump_translatable_strings = 1; break; default: if (change_flag (arg_character, on_or_off) == FLAG_ERROR) { report_error ("%c%c: unrecognized option", on_or_off, arg_character); show_shell_usage (stderr, 0); exit (EX_USAGE); } } } /* Can't do just a simple increment anymore -- what about "bash -abouo emacs ignoreeof -hP"? */ arg_index = next_arg; } return (arg_index); } /* Exit the shell with status S. */ void exit_shell (s) int s; { /* Do trap[0] if defined. Allow it to override the exit status passed to us. */ if (signal_is_trapped (0)) s = run_exit_trap (); #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ #if defined (HISTORY) if (interactive_shell) maybe_save_shell_history (); #endif /* HISTORY */ #if defined (JOB_CONTROL) /* If the user has run `shopt -s huponexit', hangup all jobs when we exit an interactive login shell. ksh does this unconditionally. */ if (interactive_shell && login_shell && hup_on_exit) hangup_all_jobs (); /* If this shell is interactive, terminate all stopped jobs and restore the original terminal process group. Don't do this if we're in a subshell and calling exit_shell after, for example, a failed word expansion. */ if (subshell_environment == 0) end_job_control (); #endif /* JOB_CONTROL */ /* Always return the exit status of the last command to our parent. */ exit (s); } #ifdef INCLUDE_UNUSED /* A wrapper for exit that (optionally) can do other things, like malloc statistics tracing. */ void sh_exit (s) int s; { exit (s); } #endif /* Source the bash startup files. If POSIXLY_CORRECT is non-zero, we obey the Posix.2 startup file rules: $ENV is expanded, and if the file it names exists, that file is sourced. The Posix.2 rules are in effect for interactive shells only. (section 4.56.5.3) */ /* Execute ~/.bashrc for most shells. Never execute it if ACT_LIKE_SH is set, or if NO_RC is set. If the executable file "/usr/gnu/src/bash/foo" contains: #!/usr/gnu/bin/bash echo hello then: COMMAND EXECUTE BASHRC -------------------------------- bash -c foo NO bash foo NO foo NO rsh machine ls YES (for rsh, which calls `bash -c') rsh machine foo YES (for shell started by rsh) NO (for foo!) echo ls | bash NO login NO bash YES */ static void execute_env_file (env_file) char *env_file; { char *fn; if (env_file && *env_file) { fn = expand_string_unsplit_to_string (env_file, Q_DOUBLE_QUOTES); if (fn && *fn) maybe_execute_file (fn, 1); FREE (fn); } } static void run_startup_files () { #if defined (JOB_CONTROL) int old_job_control; #endif int sourced_login, run_by_ssh; /* get the rshd/sshd case out of the way first. */ if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 && act_like_sh == 0 && local_pending_command) { #ifdef SSH_SOURCE_BASHRC run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) || (find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0); #else run_by_ssh = 0; #endif /* If we were run by sshd or we think we were run by rshd, execute ~/.bashrc if we are a top-level shell. */ if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2) { #ifdef SYS_BASHRC # if defined (__OPENNT) maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); # else maybe_execute_file (SYS_BASHRC, 1); # endif #endif maybe_execute_file (bashrc_file, 1); return; } } #if defined (JOB_CONTROL) /* Startup files should be run without job control enabled. */ old_job_control = interactive_shell ? set_job_control (0) : 0; #endif sourced_login = 0; /* A shell begun with the --login flag that is not in posix mode runs the login shell startup files, no matter whether or not it is interactive. If NON_INTERACTIVE_LOGIN_SHELLS is defined, run the startup files if argv[0][0] == '-' as well. */ #if defined (NON_INTERACTIVE_LOGIN_SHELLS) if (login_shell && posixly_correct == 0) #else if (login_shell < 0 && posixly_correct == 0) #endif { /* We don't execute .bashrc for login shells. */ no_rc++; /* Execute /etc/profile and one of the personal login shell initialization files. */ if (no_profile == 0) { maybe_execute_file (SYS_PROFILE, 1); if (act_like_sh) /* sh */ maybe_execute_file ("~/.profile", 1); else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ maybe_execute_file ("~/.profile", 1); } sourced_login = 1; } /* A non-interactive shell not named `sh' and not in posix mode reads and executes commands from $BASH_ENV. If `su' starts a shell with `-c cmd' and `-su' as the name of the shell, we want to read the startup files. No other non-interactive shells read any startup files. */ if (interactive_shell == 0 && !(su_shell && login_shell)) { if (posixly_correct == 0 && act_like_sh == 0 && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("BASH_ENV")); return; } /* Interactive shell or `-su' shell. */ if (posixly_correct == 0) /* bash, sh */ { if (login_shell && sourced_login++ == 0) { /* We don't execute .bashrc for login shells. */ no_rc++; /* Execute /etc/profile and one of the personal login shell initialization files. */ if (no_profile == 0) { maybe_execute_file (SYS_PROFILE, 1); if (act_like_sh) /* sh */ maybe_execute_file ("~/.profile", 1); else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ maybe_execute_file ("~/.profile", 1); } } /* bash */ if (act_like_sh == 0 && no_rc == 0) { #ifdef SYS_BASHRC # if defined (__OPENNT) maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); # else maybe_execute_file (SYS_BASHRC, 1); # endif #endif maybe_execute_file (bashrc_file, 1); } /* sh */ else if (act_like_sh && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("ENV")); } else /* bash --posix, sh --posix */ { /* bash and sh */ if (interactive_shell && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("ENV")); } #if defined (JOB_CONTROL) set_job_control (old_job_control); #endif } #if defined (RESTRICTED_SHELL) /* Return 1 if the shell should be a restricted one based on NAME or the value of `restricted'. Don't actually do anything, just return a boolean value. */ int shell_is_restricted (name) char *name; { char *temp; if (restricted) return 1; temp = base_pathname (name); return (STREQ (temp, RESTRICTED_SHELL_NAME)); } /* Perhaps make this shell a `restricted' one, based on NAME. If the basename of NAME is "rbash", then this shell is restricted. The name of the restricted shell is a configurable option, see config.h. In a restricted shell, PATH, SHELL, ENV, and BASH_ENV are read-only and non-unsettable. Do this also if `restricted' is already set to 1; maybe the shell was started with -r. */ int maybe_make_restricted (name) char *name; { char *temp; temp = base_pathname (shell_name); if (restricted || (STREQ (temp, RESTRICTED_SHELL_NAME))) { set_var_read_only ("PATH"); set_var_read_only ("SHELL"); set_var_read_only ("ENV"); set_var_read_only ("BASH_ENV"); restricted = 1; } return (restricted); } #endif /* RESTRICTED_SHELL */ /* Fetch the current set of uids and gids and return 1 if we're running setuid or setgid. */ static int uidget () { uid_t u; u = getuid (); if (current_user.uid != u) { FREE (current_user.user_name); FREE (current_user.shell); FREE (current_user.home_dir); current_user.user_name = current_user.shell = current_user.home_dir = (char *)NULL; } current_user.uid = u; current_user.gid = getgid (); current_user.euid = geteuid (); current_user.egid = getegid (); /* See whether or not we are running setuid or setgid. */ return (current_user.uid != current_user.euid) || (current_user.gid != current_user.egid); } void disable_priv_mode () { setuid (current_user.uid); setgid (current_user.gid); current_user.euid = current_user.uid; current_user.egid = current_user.gid; } static int run_wordexp (words) char *words; { int code, nw, nb; WORD_LIST *wl, *result; code = setjmp (top_level); if (code != NOT_JUMPED) { switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: return last_command_exit_value = 127; case EXITPROG: return last_command_exit_value; case DISCARD: return last_command_exit_value = 1; default: command_error ("run_wordexp", CMDERR_BADJUMP, code, 0); } } /* Run it through the parser to get a list of words and expand them */ if (words && *words) { with_input_from_string (words, "--wordexp"); if (parse_command () != 0) return (126); if (global_command == 0) { printf ("0\n0\n"); return (0); } if (global_command->type != cm_simple) return (126); wl = global_command->value.Simple->words; result = wl ? expand_words_no_vars (wl) : (WORD_LIST *)0; } else result = (WORD_LIST *)0; last_command_exit_value = 0; if (result == 0) { printf ("0\n0\n"); return (0); } /* Count up the number of words and bytes, and print them. Don't count the trailing NUL byte. */ for (nw = nb = 0, wl = result; wl; wl = wl->next) { nw++; nb += strlen (wl->word->word); } printf ("%u\n%u\n", nw, nb); /* Print each word on a separate line. This will have to be changed when the interface to glibc is completed. */ for (wl = result; wl; wl = wl->next) printf ("%s\n", wl->word->word); return (0); } #if defined (ONESHOT) /* Run one command, given as the argument to the -c option. Tell parse_and_execute not to fork for a simple command. */ static int run_one_command (command) char *command; { int code; code = setjmp (top_level); if (code != NOT_JUMPED) { #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: return last_command_exit_value = 127; case EXITPROG: return last_command_exit_value; case DISCARD: return last_command_exit_value = 1; default: command_error ("run_one_command", CMDERR_BADJUMP, code, 0); } } return (parse_and_execute (savestring (command), "-c", SEVAL_NOHIST)); } #endif /* ONESHOT */ static int bind_args (argv, arg_start, arg_end, start_index) char **argv; int arg_start, arg_end, start_index; { register int i; WORD_LIST *args; for (i = arg_start, args = (WORD_LIST *)NULL; i != arg_end; i++) args = make_word_list (make_word (argv[i]), args); if (args) { args = REVERSE_LIST (args, WORD_LIST *); if (start_index == 0) /* bind to $0...$n for sh -c command */ { /* Posix.2 4.56.3 says that the first argument after sh -c command becomes $0, and the rest of the arguments become $1...$n */ shell_name = savestring (args->word->word); FREE (dollar_vars[0]); dollar_vars[0] = savestring (args->word->word); remember_args (args->next, 1); } else /* bind to $1...$n for shell script */ remember_args (args, 1); dispose_words (args); } return (i); } void unbind_args () { remember_args ((WORD_LIST *)NULL, 1); } static int open_shell_script (script_name) char *script_name; { int fd, e, fd_is_tty; char *filename, *path_filename; char sample[80]; int sample_len; struct stat sb; free (dollar_vars[0]); dollar_vars[0] = savestring (script_name); filename = savestring (script_name); fd = open (filename, O_RDONLY); if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0)) { e = errno; /* If it's not in the current directory, try looking through PATH for it. */ path_filename = find_path_file (script_name); if (path_filename) { free (filename); filename = path_filename; fd = open (filename, O_RDONLY); } else errno = e; } if (fd < 0) { e = errno; file_error (filename); exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT); } #ifdef HAVE_DEV_FD fd_is_tty = isatty (fd); #else fd_is_tty = 0; #endif /* Only do this with non-tty file descriptors we can seek on. */ if (fd_is_tty == 0 && (lseek (fd, 0L, 1) != -1)) { /* Check to see if the `file' in `bash file' is a binary file according to the same tests done by execute_simple_command (), and report an error and exit if it is. */ sample_len = read (fd, sample, sizeof (sample)); if (sample_len < 0) { e = errno; if ((fstat (fd, &sb) == 0) && S_ISDIR (sb.st_mode)) internal_error ("%s: is a directory", filename); else { errno = e; file_error (filename); } exit (EX_NOEXEC); } else if (sample_len > 0 && (check_binary_file (sample, sample_len))) { internal_error ("%s: cannot execute binary file", filename); exit (EX_BINARY_FILE); } /* Now rewind the file back to the beginning. */ lseek (fd, 0L, 0); } /* Open the script. But try to move the file descriptor to a randomly large one, in the hopes that any descriptors used by the script will not match with ours. */ fd = move_to_high_fd (fd, 0, -1); #if defined (__CYGWIN__) && defined (O_TEXT) setmode (fd, O_TEXT); #endif #if defined (BUFFERED_INPUT) default_buffered_input = fd; SET_CLOSE_ON_EXEC (default_buffered_input); #else /* !BUFFERED_INPUT */ default_input = fdopen (fd, "r"); if (default_input == 0) { file_error (filename); exit (EX_NOTFOUND); } SET_CLOSE_ON_EXEC (fd); if (fileno (default_input) != fd) SET_CLOSE_ON_EXEC (fileno (default_input)); #endif /* !BUFFERED_INPUT */ /* Just about the only way for this code to be executed is if something like `bash -i /dev/stdin' is executed. */ if (interactive_shell && fd_is_tty) { dup2 (fd, 0); close (fd); fd = 0; #if defined (BUFFERED_INPUT) default_buffered_input = 0; #else fclose (default_input); default_input = stdin; #endif } else if (forced_interactive && fd_is_tty == 0) /* But if a script is called with something like `bash -i scriptname', we need to do a non-interactive setup here, since we didn't do it before. */ init_noninteractive (); free (filename); return (fd); } /* Initialize the input routines for the parser. */ static void set_bash_input () { /* Make sure the fd from which we are reading input is not in no-delay mode. */ #if defined (BUFFERED_INPUT) if (interactive == 0) sh_unset_nodelay_mode (default_buffered_input); else #endif /* !BUFFERED_INPUT */ sh_unset_nodelay_mode (fileno (stdin)); /* with_input_from_stdin really means `with_input_from_readline' */ if (interactive && no_line_editing == 0) with_input_from_stdin (); else #if defined (BUFFERED_INPUT) { if (interactive == 0) with_input_from_buffered_stream (default_buffered_input, dollar_vars[0]); else with_input_from_stream (default_input, dollar_vars[0]); } #else /* !BUFFERED_INPUT */ with_input_from_stream (default_input, dollar_vars[0]); #endif /* !BUFFERED_INPUT */ } /* Close the current shell script input source and forget about it. This is extern so execute_cmd.c:initialize_subshell() can call it. If CHECK_ZERO is non-zero, we close default_buffered_input even if it's the standard input (fd 0). */ void unset_bash_input (check_zero) int check_zero; { #if defined (BUFFERED_INPUT) if ((check_zero && default_buffered_input >= 0) || (check_zero == 0 && default_buffered_input > 0)) { close_buffered_fd (default_buffered_input); default_buffered_input = bash_input.location.buffered_fd = -1; } #else /* !BUFFERED_INPUT */ if (default_input) { fclose (default_input); default_input = (FILE *)NULL; } #endif /* !BUFFERED_INPUT */ } #if !defined (PROGRAM) # define PROGRAM "bash" #endif static void set_shell_name (argv0) char *argv0; { /* Here's a hack. If the name of this shell is "sh", then don't do any startup files; just try to be more like /bin/sh. */ shell_name = base_pathname (argv0); if (*shell_name == '-') shell_name++; if (shell_name[0] == 's' && shell_name[1] == 'h' && shell_name[2] == '\0') act_like_sh++; if (shell_name[0] == 's' && shell_name[1] == 'u' && shell_name[2] == '\0') su_shell++; shell_name = argv0; FREE (dollar_vars[0]); dollar_vars[0] = savestring (shell_name); if (*shell_name == '-') { shell_name++; login_shell++; } /* A program may start an interactive shell with "execl ("/bin/bash", "-", NULL)". If so, default the name of this shell to our name. */ if (!shell_name || !*shell_name || (shell_name[0] == '-' && !shell_name[1])) shell_name = PROGRAM; } static void init_interactive () { interactive_shell = startup_state = interactive = 1; expand_aliases = 1; } static void init_noninteractive () { #if defined (HISTORY) bash_history_reinit (0); #endif /* HISTORY */ interactive_shell = startup_state = interactive = 0; expand_aliases = 0; no_line_editing = 1; #if defined (JOB_CONTROL) set_job_control (0); #endif /* JOB_CONTROL */ } void get_current_user_info () { struct passwd *entry; /* Don't fetch this more than once. */ if (current_user.user_name == 0) { entry = getpwuid (current_user.uid); if (entry) { current_user.user_name = savestring (entry->pw_name); current_user.shell = (entry->pw_shell && entry->pw_shell[0]) ? savestring (entry->pw_shell) : savestring ("/bin/sh"); current_user.home_dir = savestring (entry->pw_dir); } else { current_user.user_name = savestring ("I have no name!"); current_user.shell = savestring ("/bin/sh"); current_user.home_dir = savestring ("/"); } endpwent (); } } /* Do whatever is necessary to initialize the shell. Put new initializations in here. */ static void shell_initialize () { char hostname[256]; /* Line buffer output for stderr and stdout. */ if (shell_initialized == 0) { sh_setlinebuf (stderr); sh_setlinebuf (stdout); } /* Sort the array of shell builtins so that the binary search in find_shell_builtin () works correctly. */ initialize_shell_builtins (); /* Initialize the trap signal handlers before installing our own signal handlers. traps.c:restore_original_signals () is responsible for restoring the original default signal handlers. That function is called when we make a new child. */ initialize_traps (); initialize_signals (); /* It's highly unlikely that this will change. */ if (current_host_name == 0) { /* Initialize current_host_name. */ if (gethostname (hostname, 255) < 0) current_host_name = "??host??"; else current_host_name = savestring (hostname); } /* Initialize the stuff in current_user that comes from the password file. We don't need to do this right away if the shell is not interactive. */ if (interactive_shell) get_current_user_info (); /* Initialize our interface to the tilde expander. */ tilde_initialize (); /* Initialize internal and environment variables. Don't import shell functions from the environment if we are running in privileged or restricted mode or if the shell is running setuid. */ #if defined (RESTRICTED_SHELL) initialize_shell_variables (shell_environment, privileged_mode||restricted||running_setuid); #else initialize_shell_variables (shell_environment, privileged_mode||running_setuid); #endif #if 0 /* Initialize filename hash tables. */ initialize_filename_hashing (); #endif /* Initialize the data structures for storing and running jobs. */ initialize_job_control (0); /* Initialize input streams to null. */ initialize_bash_input (); /* Initialize the shell options. Don't import the shell options from the environment variable $SHELLOPTS if we are running in privileged or restricted mode or if the shell is running setuid. */ #if defined (RESTRICTED_SHELL) initialize_shell_options (privileged_mode||restricted||running_setuid); #else initialize_shell_options (privileged_mode||running_setuid); #endif } /* Function called by main () when it appears that the shell has already had some initialization performed. This is supposed to reset the world back to a pristine state, as if we had been exec'ed. */ static void shell_reinitialize () { /* The default shell prompts. */ primary_prompt = PPROMPT; secondary_prompt = SPROMPT; /* Things that get 1. */ current_command_number = 1; /* We have decided that the ~/.bashrc file should not be executed for the invocation of each shell script. If the variable $ENV (or $BASH_ENV) is set, its value is used as the name of a file to source. */ no_rc = no_profile = 1; /* Things that get 0. */ login_shell = make_login_shell = interactive = executing = 0; debugging = do_version = line_number = last_command_exit_value = 0; forced_interactive = interactive_shell = subshell_environment = 0; expand_aliases = 0; #if defined (HISTORY) bash_history_reinit (0); #endif /* HISTORY */ #if defined (RESTRICTED_SHELL) restricted = 0; #endif /* RESTRICTED_SHELL */ /* Ensure that the default startup file is used. (Except that we don't execute this file for reinitialized shells). */ bashrc_file = "~/.bashrc"; /* Delete all variables and functions. They will be reinitialized when the environment is parsed. */ delete_all_variables (shell_variables); delete_all_variables (shell_functions); shell_reinitialized = 1; } static void show_shell_usage (fp, extra) FILE *fp; int extra; { int i; char *set_opts, *s, *t; if (extra) fprintf (fp, "GNU bash, version %s-(%s)\n", shell_version_string (), MACHTYPE); fprintf (fp, "Usage:\t%s [GNU long option] [option] ...\n\t%s [GNU long option] [option] script-file ...\n", shell_name, shell_name); fputs ("GNU long options:\n", fp); for (i = 0; long_args[i].name; i++) fprintf (fp, "\t--%s\n", long_args[i].name); fputs ("Shell options:\n", fp); fputs ("\t-irsD or -c command or -O shopt_option\t\t(invocation only)\n", fp); for (i = 0, set_opts = 0; shell_builtins[i].name; i++) if (STREQ (shell_builtins[i].name, "set")) set_opts = savestring (shell_builtins[i].short_doc); if (set_opts) { s = strchr (set_opts, '['); if (s == 0) s = set_opts; while (*++s == '-') ; t = strchr (s, ']'); if (t) *t = '\0'; fprintf (fp, "\t-%s or -o option\n", s); free (set_opts); } if (extra) { fprintf (fp, "Type `%s -c \"help set\"' for more information about shell options.\n", shell_name); fprintf (fp, "Type `%s -c help' for more information about shell builtin commands.\n", shell_name); fprintf (fp, "Use the `bashbug' command to report bugs.\n"); } } static void add_shopt_to_alist (opt, on_or_off) char *opt; int on_or_off; { if (shopt_ind >= shopt_len) { shopt_len += 8; shopt_alist = (STRING_INT_ALIST *)xrealloc (shopt_alist, shopt_len * sizeof (shopt_alist[0])); } shopt_alist[shopt_ind].word = opt; shopt_alist[shopt_ind].token = on_or_off; shopt_ind++; } static void run_shopt_alist () { register int i; for (i = 0; i < shopt_ind; i++) if (shopt_setopt (shopt_alist[i].word, (shopt_alist[i].token == '-')) != EXECUTION_SUCCESS) exit (EX_USAGE); free (shopt_alist); shopt_alist = 0; shopt_ind = shopt_len = 0; } /* The second and subsequent conditions must match those used to decide whether or not to call getpeername() in isnetconn(). */ #if defined (HAVE_SYS_SOCKET_H) && defined (HAVE_GETPEERNAME) && !defined (SVR4_2) # include #endif /* Is FD a socket or network connection? */ static int isnetconn (fd) int fd; { #if defined (HAVE_GETPEERNAME) && !defined (SVR4_2) && !defined (__BEOS__) int rv; socklen_t l; struct sockaddr sa; l = sizeof(sa); rv = getpeername(fd, &sa, &l); /* Solaris 2.5 getpeername() returns EINVAL if the fd is not a socket. */ return ((rv < 0 && (errno == ENOTSOCK || errno == EINVAL)) ? 0 : 1); #else /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ # if defined (SVR4) || defined (SVR4_2) /* Sockets on SVR4 and SVR4.2 are character special (streams) devices. */ struct stat sb; if (isatty (fd)) return (0); if (fstat (fd, &sb) < 0) return (0); # if defined (S_ISFIFO) if (S_ISFIFO (sb.st_mode)) return (0); # endif /* S_ISFIFO */ return (S_ISCHR (sb.st_mode)); # else /* !SVR4 && !SVR4_2 */ # if defined (S_ISSOCK) && !defined (__BEOS__) struct stat sb; if (fstat (fd, &sb) < 0) return (0); return (S_ISSOCK (sb.st_mode)); # else /* !S_ISSOCK || __BEOS__ */ return (0); # endif /* !S_ISSOCK || __BEOS__ */ # endif /* !SVR4 && !SVR4_2 */ #endif /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ } /* alias.h -- structure definitions. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 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 (_ALIAS_H_) #define _ALIAS_H_ #include "stdc.h" #include "hashlib.h" typedef struct alias { char *name; char *value; char flags; } alias_t; /* Values for `flags' member of struct alias. */ #define AL_EXPANDNEXT 0x1 #define AL_BEINGEXPANDED 0x2 /* The list of known aliases. */ extern HASH_TABLE *aliases; extern void initialize_aliases __P((void)); /* Scan the list of aliases looking for one with NAME. Return NULL if the alias doesn't exist, else a pointer to the alias. */ extern alias_t *find_alias __P((char *)); /* Return the value of the alias for NAME, or NULL if there is none. */ extern char *get_alias_value __P((char *)); /* Make a new alias from NAME and VALUE. If NAME can be found, then replace its value. */ extern void add_alias __P((char *, char *)); /* Remove the alias with name NAME from the alias list. Returns the index of the removed alias, or -1 if the alias didn't exist. */ extern int remove_alias __P((char *)); /* Remove all aliases. */ extern void delete_all_aliases __P((void)); /* Return an array of all defined aliases. */ extern alias_t **all_aliases __P((void)); /* Expand a single word for aliases. */ extern char *alias_expand_word __P((char *)); /* Return a new line, with any aliases expanded. */ extern char *alias_expand __P((char *)); #endif /* _ALIAS_H_ */ /* array.h -- definitions for the interface exported by array.c that allows the rest of the shell to manipulate array variables. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 _ARRAY_H_ #define _ARRAY_H_ #include "stdc.h" typedef long arrayind_t; enum atype {array_indexed, array_assoc}; typedef struct array { enum atype type; arrayind_t max_index, num_elements, max_size; struct array_element *head; } ARRAY; typedef struct array_element { arrayind_t ind; char *value; struct array_element *next, *prev; } ARRAY_ELEMENT; typedef int sh_ae_map_func_t __P((ARRAY_ELEMENT *)); char *array_reference __P((ARRAY *, arrayind_t)); extern int array_add_element __P((ARRAY *, arrayind_t, char *)); extern ARRAY_ELEMENT *array_delete_element __P((ARRAY *, arrayind_t)); extern ARRAY_ELEMENT *new_array_element __P((arrayind_t, char *)); extern void destroy_array_element __P((ARRAY_ELEMENT *)); extern ARRAY *new_array __P((void)); extern void empty_array __P((ARRAY *)); extern void dispose_array __P((ARRAY *)); extern ARRAY *dup_array __P((ARRAY *)); extern ARRAY *dup_array_subrange __P((ARRAY *, ARRAY_ELEMENT *, ARRAY_ELEMENT *)); extern ARRAY_ELEMENT *copy_array_element __P((ARRAY_ELEMENT *)); extern WORD_LIST *array_to_word_list __P((ARRAY *)); extern ARRAY *word_list_to_array __P((WORD_LIST *)); extern ARRAY *assign_word_list __P((ARRAY *, WORD_LIST *)); extern char **array_to_argv __P((ARRAY *)); extern char *array_to_assignment_string __P((ARRAY *)); extern char *quoted_array_assignment_string __P((ARRAY *)); extern char *array_to_string __P((ARRAY *, char *, int)); extern ARRAY *string_to_array __P((char *, char *)); extern char *array_subrange __P((ARRAY *, arrayind_t, arrayind_t, int)); extern char *array_pat_subst __P((ARRAY *, char *, char *, int)); extern ARRAY *array_quote __P((ARRAY *)); #define array_num_elements(a) ((a)->num_elements) #define array_max_index(a) ((a)->max_index) #define array_head(a) ((a)->head) #define array_empty(a) ((a)->num_elements == 0) #define element_value(ae) ((ae)->value) #define element_index(ae) ((ae)->ind) #define element_forw(ae) ((ae)->next) #define element_back(ae) ((ae)->prev) #define ALL_ELEMENT_SUB(c) ((c) == '@' || (c) == '*') #endif /* _ARRAY_H_ */ /* arrayfunc.h -- declarations for miscellaneous array functions in arrayfunc.c */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_ARRAYFUNC_H_) #define _ARRAYFUNC_H_ /* Must include variables.h before including this file. */ #if defined (ARRAY_VARS) extern SHELL_VAR *convert_var_to_array __P((SHELL_VAR *)); extern SHELL_VAR *bind_array_variable __P((char *, arrayind_t, char *)); extern SHELL_VAR *assign_array_element __P((char *, char *)); extern SHELL_VAR *find_or_make_array_variable __P((char *, int)); extern SHELL_VAR *assign_array_from_string __P((char *, char *)); extern SHELL_VAR *assign_array_var_from_word_list __P((SHELL_VAR *, WORD_LIST *)); extern SHELL_VAR *assign_array_var_from_string __P((SHELL_VAR *, char *)); extern int unbind_array_element __P((SHELL_VAR *, char *)); extern int skipsubscript __P((const char *, int)); extern void print_array_assignment __P((SHELL_VAR *, int)); extern arrayind_t array_expand_index __P((char *, int)); extern int valid_array_reference __P((char *)); extern char *array_value __P((char *, int)); extern char *get_array_value __P((char *, int)); extern char *array_variable_name __P((char *, char **, int *)); extern SHELL_VAR *array_variable_part __P((char *, char **, int *)); #endif #endif /* !_ARRAYFUNC_H_ */ /* bashansi.h -- Typically included information required by picky compilers. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_BASHANSI_H_) #define _BASHANSI_H_ #if defined (HAVE_STRING_H) # if ! defined (STDC_HEADERS) && defined (HAVE_MEMORY_H) # include # endif # include #endif /* !HAVE_STRING_H */ #if defined (HAVE_STRINGS_H) # include #endif /* !HAVE_STRINGS_H */ #if defined (HAVE_STDLIB_H) # include #else # include "ansi_stdlib.h" #endif /* !HAVE_STDLIB_H */ #endif /* !_BASHANSI_H_ */ /* bashhist.h -- interface to the bash history functions in bashhist.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_BASHHIST_H_) #define _BASHHIST_H_ #include "stdc.h" extern int remember_on_history; extern int history_lines_this_session; extern int history_lines_in_file; extern int history_expansion; extern int history_control; extern int command_oriented_history; extern int hist_last_line_added; # if defined (BANG_HISTORY) extern int history_expansion_inhibited; # endif /* BANG_HISTORY */ extern void bash_initialize_history __P((void)); extern void bash_history_reinit __P((int)); extern void bash_history_disable __P((void)); extern void bash_history_enable __P((void)); extern void load_history __P((void)); extern void save_history __P((void)); extern int maybe_append_history __P((char *)); extern int maybe_save_shell_history __P((void)); extern char *pre_process_line __P((char *, int, int)); extern void maybe_add_history __P((char *)); extern void bash_add_history __P((char *)); extern int history_number __P((void)); extern void setup_history_ignore __P((char *)); extern char *last_history_line __P((void)); #endif /* _BASHHIST_H_ */ /* bashintl.h -- Internationalization stuff Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_BASHINTL_H_) #define _BASHINTL_H_ /* Include this *after* config.h */ #if defined (HAVE_LIBINTL_H) # include #endif #if defined (HAVE_LOCALE_H) # include #endif #if defined (HAVE_SETLOCALE) && !defined (LC_ALL) # undef HAVE_SETLOCALE #endif #if !defined (HAVE_SETLOCALE) # define setlocale(cat, loc) #endif #if !defined (HAVE_TEXTDOMAIN) # define textdomain(dom) #endif #if !defined (HAVE_BINDTEXTDOMAIN) # define bindtextdomain(dom, dir) #endif #endif /* !_BASHINTL_H_ */ /* bashjmp.h -- wrapper for setjmp.h with necessary bash definitions. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 _BASHJMP_H_ #define _BASHJMP_H_ #include "posixjmp.h" extern procenv_t top_level; extern procenv_t subshell_top_level; extern procenv_t return_catch; /* used by `return' builtin */ #define SHFUNC_RETURN() longjmp (return_catch, 1) #define COPY_PROCENV(old, save) \ xbcopy ((char *)old, (char *)save, sizeof (procenv_t)); /* Values for the second argument to longjmp/siglongjmp. */ #define NOT_JUMPED 0 /* Not returning from a longjmp. */ #define FORCE_EOF 1 /* We want to stop parsing. */ #define DISCARD 2 /* Discard current command. */ #define EXITPROG 3 /* Unconditionally exit the program now. */ #endif /* _BASHJMP_H_ */ /* bashline.h -- interface to the bash readline functions in bashline.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_BASHLINE_H_) #define _BASHLINE_H_ #include "stdc.h" extern int bash_readline_initialized; extern void posix_readline_initialize __P((int)); extern int enable_hostname_completion __P((int)); extern void initialize_readline __P((void)); extern void bashline_reinitialize __P((void)); extern int bash_re_edit __P((char *)); extern int bind_keyseq_to_unix_command __P((char *)); /* Used by programmable completion code. */ extern char *command_word_completion_function __P((const char *, int)); extern char *bash_groupname_completion_function __P((const char *, int)); extern char **get_hostname_list __P((void)); extern void clear_hostname_list __P((void)); extern char **bash_directory_completion_matches __P((const char *)); #endif /* _BASHLINE_H_ */ /* bashtypes.h -- with special handling for crays. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_BASHTYPES_H_) # define _BASHTYPES_H_ #if defined (CRAY) # define word __word #endif #include #if defined (CRAY) # undef word #endif #endif /* _BASHTYPES_H_ */ /* builtins.h -- What a builtin looks like, and where to find them. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "command.h" #include "general.h" #if defined (ALIAS) #include "alias.h" #endif /* Flags describing various things about a builtin. */ #define BUILTIN_ENABLED 0x1 /* This builtin is enabled. */ #define BUILTIN_DELETED 0x2 /* This has been deleted with enable -d. */ #define STATIC_BUILTIN 0x4 /* This builtin is not dynamically loaded. */ #define SPECIAL_BUILTIN 0x8 /* This is a Posix `special' builtin. */ #define ASSIGNMENT_BUILTIN 0x10 /* This builtin takes assignment statements. */ /* The thing that we build the array of builtins out of. */ struct builtin { char *name; /* The name that the user types. */ sh_builtin_func_t *function; /* The address of the invoked function. */ int flags; /* One of the #defines above. */ char **long_doc; /* NULL terminated array of strings. */ char *short_doc; /* Short version of documenation. */ char *handle; /* for future use */ }; /* Found in builtins.c, created by builtins/mkbuiltins. */ extern int num_shell_builtins; /* Number of shell builtins. */ extern struct builtin static_shell_builtins[]; extern struct builtin *shell_builtins; extern struct builtin *current_builtin; /* command.h -- The structures used internally to represent commands, and the extern declarations of the functions used to create them. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_COMMAND_H_) #define _COMMAND_H_ #include "stdc.h" /* Instructions describing what kind of thing to do for a redirection. */ enum r_instruction { r_output_direction, r_input_direction, r_inputa_direction, r_appending_to, r_reading_until, r_duplicating_input, r_duplicating_output, r_deblank_reading_until, r_close_this, r_err_and_out, r_input_output, r_output_force, r_duplicating_input_word, r_duplicating_output_word }; /* Redirection errors. */ #define AMBIGUOUS_REDIRECT -1 #define NOCLOBBER_REDIRECT -2 #define RESTRICTED_REDIRECT -3 /* can only happen in restricted shells. */ #define HEREDOC_REDIRECT -4 /* here-doc temp file can't be created */ #define CLOBBERING_REDIRECT(ri) \ (ri == r_output_direction || ri == r_err_and_out) #define OUTPUT_REDIRECT(ri) \ (ri == r_output_direction || ri == r_input_output || ri == r_err_and_out) #define INPUT_REDIRECT(ri) \ (ri == r_input_direction || ri == r_inputa_direction || ri == r_input_output) #define WRITE_REDIRECT(ri) \ (ri == r_output_direction || \ ri == r_input_output || \ ri == r_err_and_out || \ ri == r_appending_to || \ ri == r_output_force) /* Command Types: */ enum command_type { cm_for, cm_case, cm_while, cm_if, cm_simple, cm_select, cm_connection, cm_function_def, cm_until, cm_group, cm_arith, cm_cond, cm_arith_for, cm_subshell }; /* Possible values for the `flags' field of a WORD_DESC. */ #define W_HASDOLLAR 0x01 /* Dollar sign present. */ #define W_QUOTED 0x02 /* Some form of quote character is present. */ #define W_ASSIGNMENT 0x04 /* This word is a variable assignment. */ #define W_GLOBEXP 0x08 /* This word is the result of a glob expansion. */ #define W_NOSPLIT 0x10 /* Do not perform word splitting on this word. */ #define W_NOGLOB 0x20 /* Do not perform globbing on this word. */ #define W_NOSPLIT2 0x40 /* Don't split word except for $@ expansion. */ /* Possible values for subshell_environment */ #define SUBSHELL_ASYNC 0x01 /* subshell caused by `command &' */ #define SUBSHELL_PAREN 0x02 /* subshell caused by ( ... ) */ #define SUBSHELL_COMSUB 0x04 /* subshell caused by `command` or $(command) */ #define SUBSHELL_FORK 0x08 /* subshell caused by executing a disk command */ #define SUBSHELL_PIPE 0x10 /* subshell from a pipeline element */ /* A structure which represents a word. */ typedef struct word_desc { char *word; /* Zero terminated string. */ int flags; /* Flags associated with this word. */ } WORD_DESC; /* A linked list of words. */ typedef struct word_list { struct word_list *next; WORD_DESC *word; } WORD_LIST; /* **************************************************************** */ /* */ /* Shell Command Structs */ /* */ /* **************************************************************** */ /* What a redirection descriptor looks like. If the redirection instruction is ri_duplicating_input or ri_duplicating_output, use DEST, otherwise use the file in FILENAME. Out-of-range descriptors are identified by a negative DEST. */ typedef union { int dest; /* Place to redirect REDIRECTOR to, or ... */ WORD_DESC *filename; /* filename to redirect to. */ } REDIRECTEE; /* Structure describing a redirection. If REDIRECTOR is negative, the parser (or translator in redir.c) encountered an out-of-range file descriptor. */ typedef struct redirect { struct redirect *next; /* Next element, or NULL. */ int redirector; /* Descriptor to be redirected. */ int flags; /* Flag value for `open'. */ enum r_instruction instruction; /* What to do with the information. */ REDIRECTEE redirectee; /* File descriptor or filename */ char *here_doc_eof; /* The word that appeared in <flags. */ #define CMD_WANT_SUBSHELL 0x01 /* User wants a subshell: ( command ) */ #define CMD_FORCE_SUBSHELL 0x02 /* Shell needs to force a subshell. */ #define CMD_INVERT_RETURN 0x04 /* Invert the exit value. */ #define CMD_IGNORE_RETURN 0x08 /* Ignore the exit value. For set -e. */ #define CMD_NO_FUNCTIONS 0x10 /* Ignore functions during command lookup. */ #define CMD_INHIBIT_EXPANSION 0x20 /* Do not expand the command words. */ #define CMD_NO_FORK 0x40 /* Don't fork; just call execve */ #define CMD_TIME_PIPELINE 0x80 /* Time a pipeline */ #define CMD_TIME_POSIX 0x100 /* time -p; use POSIX.2 time output spec. */ #define CMD_AMPERSAND 0x200 /* command & */ #define CMD_STDIN_REDIR 0x400 /* async command needs implicit " /* System-wide .bashrc file for interactive shells. */ /* #define SYS_BASHRC "/etc/bash.bashrc" */ /* System-wide .bash_logout for login shells. */ /* #define SYS_BASH_LOGOUT "/etc/bash.bash_logout" */ /* Define this to make non-interactive shells begun with argv[0][0] == '-' run the startup files when not in posix mode. */ /* #define NON_INTERACTIVE_LOGIN_SHELLS */ /* Define this if you want bash to try to check whether it's being run by sshd and source the .bashrc if so (like the rshd behavior). */ /* #define SSH_SOURCE_BASHRC */ /* conftypes.h -- defines for build and host system. */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_CONFTYPES_H_) #define _CONFTYPES_H_ /* Placeholder for future modifications if cross-compiling or building a `fat' binary, e.g. on Apple Rhapsody. These values are used in multiple files, so they appear here. */ #if !defined (RHAPSODY) # define HOSTTYPE CONF_HOSTTYPE # define OSTYPE CONF_OSTYPE # define MACHTYPE CONF_MACHTYPE #else /* RHAPSODY */ # if defined(__powerpc__) || defined(__ppc__) # define HOSTTYPE "powerpc" # elif defined(__i386__) # define HOSTTYPE "i386" # else # define HOSTTYPE CONF_HOSTTYPE # endif # define OSTYPE CONF_OSTYPE # define VENDOR CONF_VENDOR # define MACHTYPE HOSTTYPE "-" VENDOR "-" OSTYPE #endif /* RHAPSODY */ #ifndef HOSTTYPE # define HOSTTYPE "unknown" #endif #ifndef OSTYPE # define OSTYPE "unknown" #endif #ifndef MACHTYPE # define MACHTYPE "unknown" #endif #endif /* _CONFTYPES_H_ */ /* dispose_cmd.h -- Functions appearing in dispose_cmd.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_DISPOSE_CMD_H_) #define _DISPOSE_CMD_H_ #include "stdc.h" extern void dispose_command __P((COMMAND *)); extern void dispose_word __P((WORD_DESC *)); extern void dispose_words __P((WORD_LIST *)); extern void dispose_word_array __P((char **)); extern void dispose_redirects __P((REDIRECT *)); #if defined (COND_COMMAND) extern void dispose_cond_node __P((COND_COM *)); #endif #endif /* !_DISPOSE_CMD_H_ */ /* error.h -- External declarations of functions appearing in error.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_ERROR_H_) #define _ERROR_H_ #include "stdc.h" /* Get the name of the shell or shell script for an error message. */ extern char *get_name_for_error __P((void)); /* Report an error having to do with FILENAME. */ extern void file_error __P((const char *)); /* Report a programmer's error, and abort. Pass REASON, and ARG1 ... ARG5. */ extern void programming_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* General error reporting. Pass FORMAT and ARG1 ... ARG5. */ extern void report_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Error messages for parts of the parser that don't call report_syntax_error */ extern void parser_error __P((int, const char *, ...)) __attribute__((__format__ (printf, 2, 3))); /* Report an unrecoverable error and exit. Pass FORMAT and ARG1 ... ARG5. */ extern void fatal_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Report a system error, like BSD warn(3). */ extern void sys_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Report an internal error. */ extern void internal_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Report an internal warning. */ extern void internal_warning __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Report an error having to do with command parsing or execution. */ extern void command_error __P((const char *, int, int, int)); extern char *command_errstr __P((int)); /* Debugging function, not enabled in released version. */ extern void itrace __P((const char *, ...)) __attribute__ ((__format__ (printf, 1, 2))); #endif /* !_ERROR_H_ */ /* execute_cmd.h - functions from execute_cmd.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_EXECUTE_CMD_H_) #define _EXECUTE_CMD_H_ #include "stdc.h" extern struct fd_bitmap *new_fd_bitmap __P((int)); extern void dispose_fd_bitmap __P((struct fd_bitmap *)); extern void close_fd_bitmap __P((struct fd_bitmap *)); extern int executing_line_number __P((void)); extern int execute_command __P((COMMAND *)); extern int execute_command_internal __P((COMMAND *, int, int, int, struct fd_bitmap *)); extern int shell_execve __P((char *, char **, char **)); extern void setup_async_signals __P((void)); extern void dispose_exec_redirects __P ((void)); extern int execute_shell_function __P((SHELL_VAR *, WORD_LIST *)); #if defined (PROCESS_SUBSTITUTION) extern void close_all_files __P((void)); #endif #endif /* _EXECUTE_CMD_H_ */ /* externs.h -- extern function declarations which do not appear in their own header file. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 long evalexp __P((char *, int *)); /* Functions from print_cmd.c. */ extern char *make_command_string __P((COMMAND *)); extern void print_command __P((COMMAND *)); extern void print_simple_command __P((SIMPLE_COM *)); extern char *named_function_string __P((char *, COMMAND *, int)); extern void print_word_list __P((WORD_LIST *, char *)); extern void xtrace_print_word_list __P((WORD_LIST *)); #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 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 *, const 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 *)); /* Declarations for functions defined in list.c. */ extern void map_over_list __P((GENERIC_LIST *, sh_glist_func_t *)); extern void map_over_words __P((WORD_LIST *, sh_icpfunc_t *)); extern GENERIC_LIST *reverse_list (); extern int list_length (); extern GENERIC_LIST *list_append (); extern GENERIC_LIST *delete_element (); /* Declarations for functions defined in stringlib.c */ extern char **word_list_to_argv __P((WORD_LIST *, int, int, int *)); extern WORD_LIST *argv_to_word_list __P((char **, int, int)); extern int find_string_in_alist __P((char *, STRING_INT_ALIST *, 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 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/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((long, char *, size_t)); extern char *itos __P((long)); extern char *uinttostr __P((unsigned long, char *, size_t)); extern char *uitos __P((unsigned long)); /* 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/netopen.c */ extern int netopen __P((char *)); /* Declarations for functions defined in lib/sh/oslib.c */ extern int dup2 __P((int, int)); #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 */ /* 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/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/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; extern STRINGLIST *alloc_stringlist __P((int)); extern STRINGLIST *realloc_stringlist __P((STRINGLIST *, int)); extern void free_stringlist __P((STRINGLIST *)); extern STRINGLIST *copy_stringlist __P((STRINGLIST *)); extern STRINGLIST *merge_stringlists __P((STRINGLIST *, STRINGLIST *)); extern STRINGLIST *append_stringlist __P((STRINGLIST *, STRINGLIST *)); extern STRINGLIST *prefix_suffix_stringlist __P((STRINGLIST *, char *, char *)); extern void print_stringlist __P((STRINGLIST *, char *)); extern void sort_stringlist __P((STRINGLIST *)); /* declarations for functions defined in lib/sh/stringvec.c */ extern int find_name_in_array __P((char *, char **)); extern char **alloc_array __P((int)); extern int array_len __P((char **)); extern void free_array_members __P((char **)); extern void free_array __P((char **)); extern char **copy_array __P((char **)); extern int qsort_string_compare __P((char **, char **)); extern void sort_char_array __P((char **)); /* 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 */ #ifdef NEED_STRTOIMAX_DECL #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 #endif /* NEED_STRTOIMAX_DECL */ /* 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 *)); /* 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/zread.c */ extern ssize_t zread __P((int, char *, size_t)); extern ssize_t zread1 __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_ */ /* findcmd.h - functions from findcmd.c. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_FINDCMD_H_) #define _FINDCMD_H_ #include "stdc.h" extern int file_status __P((const char *)); extern int executable_file __P((const char *)); extern int is_directory __P((const char *)); extern int executable_or_directory __P((const char *)); extern char *find_user_command __P((const char *)); extern char *find_path_file __P((const char *)); extern char *search_for_command __P((const char *)); extern char *user_command_matches __P((const char *, int, int)); #endif /* _FINDCMD_H_ */ /* flags.h -- a list of all the flags that the shell knows about. You add a flag to this program by adding the name here, and in flags.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 (_FLAGS_H_) #define _FLAGS_H_ #include "stdc.h" /* Welcome to the world of Un*x, where everything is slightly backwards. */ #define FLAG_ON '-' #define FLAG_OFF '+' #define FLAG_ERROR -1 #define FLAG_UNKNOWN (int *)0 /* The thing that we build the array of flags out of. */ struct flags_alist { char name; int *value; }; extern struct flags_alist shell_flags[]; extern int mark_modified_vars, exit_immediately_on_error, disallow_filename_globbing, place_keywords_in_env, read_but_dont_execute, just_one_command, unbound_vars_is_error, echo_input_at_read, echo_command_at_execute, no_invisible_vars, noclobber, hashing_enabled, forced_interactive, privileged_mode, asynchronous_notification, interactive_comments, no_symbolic_links; #if 0 extern int lexical_scoping; #endif #if defined (BRACE_EXPANSION) extern int brace_expansion; #endif #if defined (BANG_HISTORY) extern int history_expansion; #endif /* BANG_HISTORY */ #if defined (RESTRICTED_SHELL) extern int restricted; extern int restricted_shell; #endif /* RESTRICTED_SHELL */ extern int *find_flag __P((int)); extern int change_flag __P((int, int)); extern char *which_set_flags __P((void)); extern void reset_shell_flags __P((void)); /* A macro for efficiency. */ #define change_flag_char(flag, on_or_off) change_flag (flag, on_or_off) #endif /* _FLAGS_H_ */ /* general.h -- defines that everybody likes to use. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 */ #define pointer_to_int(x) (int)((long)(x)) #if defined (alpha) && defined (__GNUC__) && !defined (strchr) && !defined (__STDC__) extern char *strchr (), *strrchr (); #endif #if !defined (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 *)strchr ((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)reverse_list ((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 /* 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 *, long *)); extern int legal_identifier __P((char *)); extern int check_identifier __P((WORD_DESC *, int)); extern int sh_unset_nodelay_mode __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 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 *)); extern int group_member __P((gid_t)); extern char **get_group_list __P((int *)); extern int *get_group_array __P((int *)); #endif /* _GENERAL_H_ */ /* hashcmd.h - Common defines for hashing filenames. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 "stdc.h" #include "hashlib.h" #define FILENAME_HASH_BUCKETS 107 extern HASH_TABLE *hashed_filenames; typedef struct { char *path; /* The full pathname of the file. */ int flags; } PATH_DATA; #define HASH_RELPATH 0x01 /* this filename is a relative pathname. */ #define HASH_CHKDOT 0x02 /* check `.' since it was earlier in $PATH */ #define pathdata(x) ((PATH_DATA *)(x)->data) extern void initialize_filename_hashing __P((void)); extern void flush_hashed_filenames __P((void)); extern void remove_hashed_filename __P((const char *)); extern void remember_filename __P((char *, char *, int, int)); extern char *find_hashed_filename __P((const char *)); /* hashlib.h -- the data structures used in hashing in Bash. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_HASHLIB_H_) #define _HASHLIB_H_ #include "stdc.h" typedef struct bucket_contents { struct bucket_contents *next; /* Link to next hashed key in this bucket. */ char *key; /* What we look up. */ char *data; /* What we really want. */ int times_found; /* Number of times this item has been found. */ } BUCKET_CONTENTS; typedef struct hash_table { BUCKET_CONTENTS **bucket_array; /* Where the data is kept. */ int nbuckets; /* How many buckets does this table have. */ int nentries; /* How many entries does this table have. */ } HASH_TABLE; extern int hash_string __P((const char *, HASH_TABLE *)); extern int hash_table_nentries __P((HASH_TABLE *)); extern HASH_TABLE *make_hash_table __P((int)); extern HASH_TABLE *copy_hash_table __P((HASH_TABLE *, sh_string_func_t *)); extern BUCKET_CONTENTS *find_hash_item __P((const char *, HASH_TABLE *)); extern BUCKET_CONTENTS *remove_hash_item __P((const char *, HASH_TABLE *)); extern BUCKET_CONTENTS *add_hash_item __P((char *, HASH_TABLE *)); extern void flush_hash_table __P((HASH_TABLE *, sh_free_func_t *)); extern void dispose_hash_table __P((HASH_TABLE *)); /* Redefine the function as a macro for speed. */ #define get_hash_bucket(bucket, table) \ ((table && (bucket < table->nbuckets)) ? \ table->bucket_array[bucket] : \ (BUCKET_CONTENTS *)NULL) /* Default number of buckets in the hash table. */ #define DEFAULT_HASH_BUCKETS 53 /* was 107 */ #define HASH_ENTRIES(ht) ((ht) ? (ht)->nentries : 0) #if !defined (NULL) # if defined (__STDC__) # define NULL ((void *) 0) # else # define NULL 0x0 # endif /* !__STDC__ */ #endif /* !NULL */ #endif /* _HASHLIB_H */ /* input.h -- Structures and unions used for reading input. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_INPUT_H_) #define _INPUT_H_ #include "stdc.h" /* Function pointers can be declared as (Function *)foo. */ #if !defined (_FUNCTION_DEF) # define _FUNCTION_DEF typedef int Function (); typedef void VFunction (); typedef char *CPFunction (); /* no longer used */ typedef char **CPPFunction (); /* no longer used */ #endif /* _FUNCTION_DEF */ typedef int sh_cget_func_t __P((void)); /* sh_ivoidfunc_t */ typedef int sh_cunget_func_t __P((int)); /* sh_intfunc_t */ enum stream_type {st_none, st_stdin, st_stream, st_string, st_bstream}; #if defined (BUFFERED_INPUT) /* Possible values for b_flag. */ #undef B_EOF #undef B_ERROR /* There are some systems with this define */ #undef B_UNBUFF #define B_EOF 0x01 #define B_ERROR 0x02 #define B_UNBUFF 0x04 #define B_WASBASHINPUT 0x08 /* A buffered stream. Like a FILE *, but with our own buffering and synchronization. Look in input.c for the implementation. */ typedef struct BSTREAM { int b_fd; char *b_buffer; /* The buffer that holds characters read. */ size_t b_size; /* How big the buffer is. */ size_t b_used; /* How much of the buffer we're using, */ int b_flag; /* Flag values. */ size_t b_inputp; /* The input pointer, index into b_buffer. */ } BUFFERED_STREAM; #if 0 extern BUFFERED_STREAM **buffers; #endif extern int default_buffered_input; #endif /* BUFFERED_INPUT */ typedef union { FILE *file; char *string; #if defined (BUFFERED_INPUT) int buffered_fd; #endif } INPUT_STREAM; typedef struct { enum stream_type type; char *name; INPUT_STREAM location; sh_cget_func_t *getter; sh_cunget_func_t *ungetter; } BASH_INPUT; extern BASH_INPUT bash_input; /* Functions from parse.y. */ extern void initialize_bash_input __P((void)); extern void init_yy_io __P((sh_cget_func_t *, sh_cunget_func_t *, enum stream_type, const char *, INPUT_STREAM)); extern void with_input_from_stdin __P((void)); extern void with_input_from_string __P((char *, const char *)); extern void with_input_from_stream __P((FILE *, const char *)); extern void push_stream __P((int)); extern void pop_stream __P((void)); extern int stream_on_stack __P((enum stream_type)); extern char *read_secondary_line __P((int)); extern int find_reserved_word __P((char *)); extern char *decode_prompt_string __P((char *)); extern void gather_here_documents __P((void)); extern void execute_prompt_command __P((char *)); extern int *save_token_state __P((void)); extern void restore_token_state __P((int *)); /* Functions from input.c */ extern int getc_with_restart __P((FILE *)); extern int ungetc_with_restart __P((int, FILE *)); #if defined (BUFFERED_INPUT) /* Functions from input.c. */ extern int fd_is_bash_input __P((int)); extern int set_bash_input_fd __P((int)); extern int save_bash_input __P((int, int)); extern int check_bash_input __P((int)); extern int duplicate_buffered_stream __P((int, int)); extern BUFFERED_STREAM *fd_to_buffered_stream __P((int)); extern BUFFERED_STREAM *set_buffered_stream __P((int, BUFFERED_STREAM *)); extern BUFFERED_STREAM *open_buffered_stream __P((char *)); extern void free_buffered_stream __P((BUFFERED_STREAM *)); extern int close_buffered_stream __P((BUFFERED_STREAM *)); extern int close_buffered_fd __P((int)); extern int sync_buffered_stream __P((int)); extern int buffered_getchar __P((void)); extern int buffered_ungetchar __P((int)); extern void with_input_from_buffered_stream __P((int, char *)); #endif /* BUFFERED_INPUT */ #endif /* _INPUT_H_ */ /* jobs.h -- structures and stuff used by the jobs.c file. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_JOBS_H_) # define _JOBS_H_ #include "quit.h" #include "siglist.h" #include "stdc.h" #include "posixwait.h" /* Defines controlling the fashion in which jobs are listed. */ #define JLIST_STANDARD 0 #define JLIST_LONG 1 #define JLIST_PID_ONLY 2 #define JLIST_CHANGED_ONLY 3 #define JLIST_NONINTERACTIVE 4 /* I looked it up. For pretty_print_job (). The real answer is 24. */ #define LONGEST_SIGNAL_DESC 24 /* We keep an array of jobs. Each entry in the array is a linked list of processes that are piped together. The first process encountered is the group leader. */ /* Values for the `running' field of a struct process. */ #define PS_DONE 0 #define PS_RUNNING 1 #define PS_STOPPED 2 /* Each child of the shell is remembered in a STRUCT PROCESS. A chain of such structures is a pipeline. The chain is circular. */ typedef struct process { struct process *next; /* Next process in the pipeline. A circular chain. */ pid_t pid; /* Process ID. */ WAIT status; /* The status of this command as returned by wait. */ int running; /* Non-zero if this process is running. */ char *command; /* The particular program that is running. */ } PROCESS; /* A description of a pipeline's state. */ typedef enum { JRUNNING, JSTOPPED, JDEAD, JMIXED } JOB_STATE; #define JOBSTATE(job) (jobs[(job)]->state) #define STOPPED(j) (jobs[(j)]->state == JSTOPPED) #define RUNNING(j) (jobs[(j)]->state == JRUNNING) #define DEADJOB(j) (jobs[(j)]->state == JDEAD) /* Values for the FLAGS field in the JOB struct below. */ #define J_FOREGROUND 0x01 /* Non-zero if this is running in the foreground. */ #define J_NOTIFIED 0x02 /* Non-zero if already notified about job state. */ #define J_JOBCONTROL 0x04 /* Non-zero if this job started under job control. */ #define J_NOHUP 0x08 /* Don't send SIGHUP to job if shell gets SIGHUP. */ #define IS_FOREGROUND(j) ((jobs[j]->flags & J_FOREGROUND) != 0) #define IS_NOTIFIED(j) ((jobs[j]->flags & J_NOTIFIED) != 0) #define IS_JOBCONTROL(j) ((jobs[j]->flags & J_JOBCONTROL) != 0) typedef struct job { char *wd; /* The working directory at time of invocation. */ PROCESS *pipe; /* The pipeline of processes that make up this job. */ pid_t pgrp; /* The process ID of the process group (necessary). */ JOB_STATE state; /* The state that this job is in. */ int flags; /* Flags word: J_NOTIFIED, J_FOREGROUND, or J_JOBCONTROL. */ #if defined (JOB_CONTROL) COMMAND *deferred; /* Commands that will execute when this job is done. */ sh_vptrfunc_t *j_cleanup; /* Cleanup function to call when job marked JDEAD */ PTR_T cleanarg; /* Argument passed to (*j_cleanup)() */ #endif /* JOB_CONTROL */ } JOB; #define NO_JOB -1 /* An impossible job array index. */ #define DUP_JOB -2 /* A possible return value for get_job_spec (). */ /* A value which cannot be a process ID. */ #define NO_PID (pid_t)-1 /* System calls. */ #if !defined (HAVE_UNISTD_H) extern pid_t fork (), getpid (), getpgrp (); #endif /* !HAVE_UNISTD_H */ /* Stuff from the jobs.c file. */ extern pid_t original_pgrp, shell_pgrp, pipeline_pgrp; extern pid_t last_made_pid, last_asynchronous_pid; extern int current_job, previous_job; extern int asynchronous_notification; extern JOB **jobs; extern int job_slots; extern void making_children __P((void)); extern void stop_making_children __P((void)); extern void cleanup_the_pipeline __P((void)); extern void save_pipeline __P((int)); extern void restore_pipeline __P((int)); extern void start_pipeline __P((void)); extern int stop_pipeline __P((int, COMMAND *)); extern void delete_job __P((int, int)); extern void nohup_job __P((int)); extern void delete_all_jobs __P((int)); extern void nohup_all_jobs __P((int)); extern int count_all_jobs __P((void)); extern void terminate_current_pipeline __P((void)); extern void terminate_stopped_jobs __P((void)); extern void hangup_all_jobs __P((void)); extern void kill_current_pipeline __P((void)); #if defined (__STDC__) && defined (pid_t) extern int get_job_by_pid __P((int, int)); extern void describe_pid __P((int)); #else extern int get_job_by_pid __P((pid_t, int)); extern void describe_pid __P((pid_t)); #endif extern void list_one_job __P((JOB *, int, int, int)); extern void list_all_jobs __P((int)); extern void list_stopped_jobs __P((int)); extern void list_running_jobs __P((int)); extern pid_t make_child __P((char *, int)); extern int get_tty_state __P((void)); extern int set_tty_state __P((void)); extern int wait_for_single_pid __P((pid_t)); extern void wait_for_background_pids __P((void)); extern int wait_for __P((pid_t)); extern int wait_for_job __P((int)); extern void notify_and_cleanup __P((void)); extern void reap_dead_jobs __P((void)); extern int start_job __P((int, int)); extern int kill_pid __P((pid_t, int, int)); extern int initialize_job_control __P((int)); extern void initialize_job_signals __P((void)); extern int give_terminal_to __P((pid_t, int)); extern void set_sigwinch_handler __P((void)); extern void unset_sigwinch_handler __P((void)); extern void unfreeze_jobs_list __P((void)); extern int set_job_control __P((int)); extern void without_job_control __P((void)); extern void end_job_control __P((void)); extern void restart_job_control __P((void)); extern void set_sigchld_handler __P((void)); extern void ignore_tty_job_signals __P((void)); extern void default_tty_job_signals __P((void)); #if defined (JOB_CONTROL) extern int job_control; #endif #endif /* _JOBS_H_ */ /* mailcheck.h -- variables and function declarations for mail checking. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_MAILCHECK_H_) #define _MAILCHECK_H_ /* Functions from mailcheck.c */ extern int time_to_check_mail __P((void)); extern void reset_mail_timer __P((void)); extern void reset_mail_files __P((void)); extern void free_mail_files __P((void)); extern char *make_default_mailpath __P((void)); extern void remember_mail_dates __P((void)); extern void check_mail __P((void)); #endif /* _MAILCHECK_H */ /* make_cmd.h -- Declarations of functions found in make_cmd.c */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_MAKE_CMD_H_) #define _MAKE_CMD_H_ #include "stdc.h" extern WORD_LIST *make_word_list __P((WORD_DESC *, WORD_LIST *)); extern WORD_LIST *add_string_to_list __P((char *, WORD_LIST *)); extern WORD_DESC *make_bare_word __P((const char *)); extern WORD_DESC *make_word_flags __P((WORD_DESC *, const char *)); extern WORD_DESC *make_word __P((const char *)); extern WORD_DESC *make_word_from_token __P((int)); extern COMMAND *make_command __P((enum command_type, SIMPLE_COM *)); extern COMMAND *command_connect __P((COMMAND *, COMMAND *, int)); extern COMMAND *make_for_command __P((WORD_DESC *, WORD_LIST *, COMMAND *)); extern COMMAND *make_group_command __P((COMMAND *)); extern COMMAND *make_case_command __P((WORD_DESC *, PATTERN_LIST *)); extern PATTERN_LIST *make_pattern_list __P((WORD_LIST *, COMMAND *)); extern COMMAND *make_if_command __P((COMMAND *, COMMAND *, COMMAND *)); extern COMMAND *make_while_command __P((COMMAND *, COMMAND *)); extern COMMAND *make_until_command __P((COMMAND *, COMMAND *)); extern COMMAND *make_bare_simple_command __P((void)); extern COMMAND *make_simple_command __P((ELEMENT, COMMAND *)); extern void make_here_document __P((REDIRECT *)); extern REDIRECT *make_redirection __P((int, enum r_instruction, REDIRECTEE)); extern COMMAND *make_function_def __P((WORD_DESC *, COMMAND *, int, int)); extern COMMAND *clean_simple_command __P((COMMAND *)); extern COMMAND *make_arith_command __P((WORD_LIST *)); extern COMMAND *make_select_command __P((WORD_DESC *, WORD_LIST *, COMMAND *)); #if defined (COND_COMMAND) extern COND_COM *make_cond_node __P((int, WORD_DESC *, COND_COM *, COND_COM *)); extern COMMAND *make_cond_command __P((COND_COM *)); #endif extern COMMAND *make_arith_for_command __P((WORD_LIST *, COMMAND *, int)); extern COMMAND *make_subshell_command __P((COMMAND *)); extern COMMAND *connect_async_list __P((COMMAND *, COMMAND *, int)); #endif /* !_MAKE_CMD_H */ /* parser.h -- Everything you wanted to know about the parser, but were afraid to ask. */ /* Copyright (C) 1995 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 (_PARSER_H_) # define _PARSER_H_ # include "command.h" # include "input.h" /* Definition of the delimiter stack. Needed by parse.y and bashhist.c. */ struct dstack { /* DELIMITERS is a stack of the nested delimiters that we have encountered so far. */ char *delimiters; /* Offset into the stack of delimiters. */ int delimiter_depth; /* How many slots are allocated to DELIMITERS. */ int delimiter_space; }; #endif /* _PARSER_H_ */ /* patchlevel.h -- current bash patch level */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_PATCHLEVEL_H_) #define _PATCHLEVEL_H_ /* It's important that there be no other strings in this file that match the regexp `^#define[ ]*PATCHLEVEL', since that's what support/mkversion.sh looks for to find the patch level (for the sccs version string). */ #define PATCHLEVEL 0 #endif /* _PATCHLEVEL_H_ */ /* pathexp.h -- The shell interface to the globbing library. */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 (_PATHEXP_H_) #define _PATHEXP_H_ #if defined (USE_POSIX_GLOB_LIBRARY) # define GLOB_FAILED(glist) !(glist) #else /* !USE_POSIX_GLOB_LIBRARY */ # define GLOB_FAILED(glist) (glist) == (char **)&glob_error_return extern int noglob_dot_filenames; extern char *glob_error_return; #endif /* !USE_POSIX_GLOB_LIBRARY */ /* Flag values for quote_string_for_globbing */ #define QGLOB_CVTNULL 0x01 /* convert QUOTED_NULL strings to '\0' */ #define QGLOB_FILENAME 0x02 /* do correct quoting for matching filenames */ #if defined (EXTENDED_GLOB) /* Flags to OR with other flag args to strmatch() to enabled the extended pattern matching. */ # define FNMATCH_EXTFLAG (extended_glob ? FNM_EXTMATCH : 0) #else # define FNMATCH_EXTFLAG 0 #endif /* !EXTENDED_GLOB */ extern int glob_dot_filenames; extern int extended_glob; extern int unquoted_glob_pattern_p __P((char *)); /* PATHNAME can contain characters prefixed by CTLESC; this indicates that the character is to be quoted. We quote it here in the style that the glob library recognizes. If flags includes QGLOB_CVTNULL, we change quoted null strings (pathname[0] == CTLNUL) into empty strings (pathname[0] == 0). If this is called after quote removal is performed, (flags & QGLOB_CVTNULL) should be 0; if called when quote removal has not been done (for example, before attempting to match a pattern while executing a case statement), flags should include QGLOB_CVTNULL. If flags includes QGLOB_FILENAME, appropriate quoting to match a filename should be performed. */ extern char *quote_string_for_globbing __P((const char *, int)); extern char *quote_globbing_chars __P((char *)); /* Call the glob library to do globbing on PATHNAME. */ extern char **shell_glob_filename __P((const char *)); /* Filename completion ignore. Used to implement the "fignore" facility of tcsh and GLOBIGNORE (like ksh-93 FIGNORE). It is passed a NULL-terminated array of (char *)'s that must be free()'d if they are deleted. The first element (names[0]) is the least-common-denominator string of the matching patterns (i.e. u produces names[0] = "und", names[1] = "under.c", names[2] = "undun.c", name[3] = NULL). */ struct ign { char *val; int len, flags; }; typedef int sh_iv_item_func_t __P((struct ign *)); struct ignorevar { char *varname; /* FIGNORE or GLOBIGNORE */ struct ign *ignores; /* Store the ignore strings here */ int num_ignores; /* How many are there? */ char *last_ignoreval; /* Last value of variable - cached for speed */ sh_iv_item_func_t *item_func; /* Called when each item is parsed from $`varname' */ }; extern void setup_ignore_patterns __P((struct ignorevar *)); extern void setup_glob_ignore __P((char *)); extern int should_ignore_glob_matches __P((void)); extern void ignore_glob_matches __P((char **)); #endif /* pathnames.h -- absolute filenames that bash wants for various defaults. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_PATHNAMES_H_) #define _PATHNAMES_H_ /* The default file for hostname completion. */ #define DEFAULT_HOSTS_FILE "/etc/hosts" /* The default login shell startup file. */ #define SYS_PROFILE "/etc/profile" #endif /* _PATHNAMES_H */ /* pcomplete.h - structure definitions and other stuff for programmable completion. */ /* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_PCOMPLETE_H_) # define _PCOMPLETE_H_ #include "stdc.h" #include "hashlib.h" typedef struct compspec { int refcount; unsigned long actions; unsigned long options; char *globpat; char *words; char *prefix; char *suffix; char *funcname; char *command; char *filterpat; } COMPSPEC; /* Values for COMPSPEC actions. These are things the shell knows how to build internally. */ #define CA_ALIAS (1<<0) #define CA_ARRAYVAR (1<<1) #define CA_BINDING (1<<2) #define CA_BUILTIN (1<<3) #define CA_COMMAND (1<<4) #define CA_DIRECTORY (1<<5) #define CA_DISABLED (1<<6) #define CA_ENABLED (1<<7) #define CA_EXPORT (1<<8) #define CA_FILE (1<<9) #define CA_FUNCTION (1<<10) #define CA_GROUP (1<<11) #define CA_HELPTOPIC (1<<12) #define CA_HOSTNAME (1<<13) #define CA_JOB (1<<14) #define CA_KEYWORD (1<<15) #define CA_RUNNING (1<<16) #define CA_SETOPT (1<<17) #define CA_SHOPT (1<<18) #define CA_SIGNAL (1<<19) #define CA_STOPPED (1<<20) #define CA_USER (1<<21) #define CA_VARIABLE (1<<22) /* Values for COMPSPEC options field. */ #define COPT_RESERVED (1<<0) /* reserved for other use */ #define COPT_DEFAULT (1<<1) #define COPT_FILENAMES (1<<2) #define COPT_DIRNAMES (1<<3) /* List of items is used by the code that implements the programmable completions. */ typedef struct _list_of_items { int flags; int (*list_getter) __P((struct _list_of_items *)); /* function to call to get the list */ STRINGLIST *slist; /* These may or may not be used. */ STRINGLIST *genlist; /* for handing to the completion code one item at a time */ int genindex; /* index of item last handed to completion code */ } ITEMLIST; /* Values for ITEMLIST -> flags */ #define LIST_DYNAMIC 0x001 #define LIST_DIRTY 0x002 #define LIST_INITIALIZED 0x004 #define LIST_MUSTSORT 0x008 #define LIST_DONTFREE 0x010 #define LIST_DONTFREEMEMBERS 0x020 extern HASH_TABLE *prog_completes; extern int prog_completion_enabled; /* Not all of these are used yet. */ extern ITEMLIST it_aliases; extern ITEMLIST it_arrayvars; extern ITEMLIST it_bindings; extern ITEMLIST it_builtins; extern ITEMLIST it_commands; extern ITEMLIST it_directories; extern ITEMLIST it_disabled; extern ITEMLIST it_enabled; extern ITEMLIST it_exports; extern ITEMLIST it_files; extern ITEMLIST it_functions; extern ITEMLIST it_groups; extern ITEMLIST it_hostnames; extern ITEMLIST it_jobs; extern ITEMLIST it_keywords; extern ITEMLIST it_running; extern ITEMLIST it_setopts; extern ITEMLIST it_shopts; extern ITEMLIST it_signals; extern ITEMLIST it_stopped; extern ITEMLIST it_users; extern ITEMLIST it_variables; /* Functions from pcomplib.c */ typedef void sh_csprint_func_t __P((char *, COMPSPEC *)); extern COMPSPEC *alloc_compspec __P((void)); extern void free_compspec __P((COMPSPEC *)); extern COMPSPEC *copy_compspec __P((COMPSPEC *)); extern void initialize_progcomp __P((void)); extern void clear_progcomps __P((void)); extern int remove_progcomp __P((char *)); extern int add_progcomp __P((char *, COMPSPEC *)); extern int num_progcomps __P((void)); extern COMPSPEC *find_compspec __P((const char *)); extern void print_all_compspecs __P((sh_csprint_func_t *)); /* Functions from pcomplete.c */ extern void set_itemlist_dirty __P((ITEMLIST *)); extern STRINGLIST *completions_to_stringlist __P((char **)); extern STRINGLIST *gen_compspec_completions __P((COMPSPEC *, const char *, const char *, int, int)); extern char **programmable_completions __P((const char *, const char *, int, int, int *)); #endif /* _PCOMPLETE_H_ */ /* quit.h -- How to handle SIGINT gracefully. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_QUIT_H_) #define _QUIT_H_ /* Non-zero means SIGINT has already ocurred. */ extern int interrupt_state; /* Macro to call a great deal. SIGINT just sets above variable. When it is safe, put QUIT in the code, and the "interrupt" will take place. */ #define QUIT if (interrupt_state) throw_to_top_level () #define SETINTERRUPT interrupt_state = 1 #define CLRINTERRUPT interrupt_state = 0 #define ADDINTERRUPT interrupt_state++ #define DELINTERRUPT interrupt_state-- #endif /* _QUIT_H_ */ /* redir.h - functions from redir.c. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_REDIR_H_) #define _REDIR_H_ #include "stdc.h" extern void redirection_error __P((REDIRECT *, int)); extern int do_redirections __P((REDIRECT *, int, int, int)); extern char *redirection_expand __P((WORD_DESC *)); extern int stdin_redirects __P((REDIRECT *)); #endif /* _REDIR_H_ */ /* shell.h -- The data structures used by the shell */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "bashjmp.h" #include "command.h" #include "syntax.h" #include "general.h" #include "error.h" #include "variables.h" #include "arrayfunc.h" #include "quit.h" #include "maxpath.h" #include "unwind_prot.h" #include "dispose_cmd.h" #include "make_cmd.h" #include "subst.h" #include "sig.h" #include "pathnames.h" #include "externs.h" #include "version.h" extern int EOF_Reached; #define NO_PIPE -1 #define REDIRECT_BOTH -2 #define NO_VARIABLE -1 /* Values that can be returned by execute_command (). */ #define EXECUTION_FAILURE 1 #define EXECUTION_SUCCESS 0 /* Usage messages by builtins result in a return status of 2. */ #define EX_BADUSAGE 2 /* Special exit statuses used by the shell, internally and externally. */ #define EX_BINARY_FILE 126 #define EX_NOEXEC 126 #define EX_NOINPUT 126 #define EX_NOTFOUND 127 #define EX_SHERRBASE 256 /* all special error values are > this. */ #define EX_BADSYNTAX 257 /* shell syntax error */ #define EX_USAGE 258 /* syntax error in usage */ #define EX_REDIRFAIL 259 /* redirection failed */ #define EX_BADASSIGN 260 /* variable assignment error */ #define EX_EXPFAIL 261 /* word expansion failed */ /* Flag values that control parameter pattern substitution. */ #define MATCH_ANY 0x0 #define MATCH_BEG 0x1 #define MATCH_END 0x2 #define MATCH_TYPEMASK 0x3 #define MATCH_GLOBREP 0x10 #define MATCH_QUOTED 0x20 /* Some needed external declarations. */ extern char **shell_environment; extern WORD_LIST *rest_of_args; /* Generalized global variables. */ extern int executing, login_shell; extern int interactive, interactive_shell; /* Structure to pass around that holds a bitmap of file descriptors to close, and the size of that structure. Used in execute_cmd.c. */ struct fd_bitmap { int size; char *bitmap; }; #define FD_BITMAP_SIZE 32 #define CTLESC '\001' #define CTLNUL '\177' /* Information about the current user. */ struct user_info { uid_t uid, euid; gid_t gid, egid; char *user_name; char *shell; /* shell from the password file */ char *home_dir; }; extern struct user_info current_user; /* Force gcc to not clobber X on a longjmp(). Old versions of gcc mangle this badly. */ #if __GNUC__ == 2 && __GNUC_MINOR__ > 8 # define USE_VAR(x) ((void) &(x)) #else # define USE_VAR(x) #endif /* sig.h -- header file for signal handler definitions. */ /* Copyright (C) 1994 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 (_SIG_H_) # define _SIG_H_ #include "stdc.h" #if !defined (SIGABRT) && defined (SIGIOT) # define SIGABRT SIGIOT #endif #define sighandler RETSIGTYPE typedef RETSIGTYPE SigHandler (); #if defined (VOID_SIGHANDLER) # define SIGRETURN(n) return #else # define SIGRETURN(n) return(n) #endif /* !VOID_SIGHANDLER */ /* Here is a definition for set_signal_handler () which simply expands to a call to signal () for non-Posix systems. The code for set_signal_handler in the Posix case resides in general.c. */ #if !defined (HAVE_POSIX_SIGNALS) # define set_signal_handler(sig, handler) (SigHandler *)signal (sig, handler) #else extern SigHandler *set_signal_handler (); /* in sig.c */ #endif /* _POSIX_VERSION */ /* Definitions used by the job control code. */ #if defined (JOB_CONTROL) #if !defined (SIGCHLD) && defined (SIGCLD) # define SIGCHLD SIGCLD #endif #if !defined (HAVE_POSIX_SIGNALS) && !defined (sigmask) # define sigmask(x) (1 << ((x)-1)) #endif /* !HAVE_POSIX_SIGNALS && !sigmask */ #if !defined (HAVE_POSIX_SIGNALS) # if !defined (SIG_BLOCK) # define SIG_BLOCK 2 # define SIG_SETMASK 3 # endif /* SIG_BLOCK */ /* sigset_t defined in config.h */ /* Make sure there is nothing inside the signal set. */ # define sigemptyset(set) (*(set) = 0) /* Initialize the signal set to hold all signals. */ # define sigfillset(set) (*set) = sigmask (NSIG) - 1 /* Add SIG to the contents of SET. */ # define sigaddset(set, sig) *(set) |= sigmask (sig) /* Delete SIG from signal set SET. */ # define sigdelset(set, sig) *(set) &= ~sigmask (sig) /* Is SIG a member of the signal set SET? */ # define sigismember(set, sig) ((*(set) & sigmask (sig)) != 0) /* Suspend the process until the reception of one of the signals not present in SET. */ # define sigsuspend(set) sigpause (*(set)) #endif /* !HAVE_POSIX_SIGNALS */ /* These definitions are used both in POSIX and non-POSIX implementations. */ #define BLOCK_SIGNAL(sig, nvar, ovar) \ sigemptyset (&nvar); \ sigaddset (&nvar, sig); \ sigemptyset (&ovar); \ sigprocmask (SIG_BLOCK, &nvar, &ovar) #if defined (HAVE_POSIX_SIGNALS) # define BLOCK_CHILD(nvar, ovar) \ BLOCK_SIGNAL (SIGCHLD, nvar, ovar) # define UNBLOCK_CHILD(ovar) \ sigprocmask (SIG_SETMASK, &ovar, (sigset_t *) NULL) #else /* !HAVE_POSIX_SIGNALS */ # define BLOCK_CHILD(nvar, ovar) ovar = sigblock (sigmask (SIGCHLD)) # define UNBLOCK_CHILD(ovar) sigsetmask (ovar) #endif /* !HAVE_POSIX_SIGNALS */ #endif /* JOB_CONTROL */ /* Functions from sig.c. */ extern sighandler termination_unwind_protect __P((int)); extern sighandler sigint_sighandler __P((int)); extern void initialize_signals __P((void)); extern void reinitialize_signals __P((void)); extern void initialize_terminating_signals __P((void)); extern void reset_terminating_signals __P((void)); extern void throw_to_top_level __P((void)); extern void jump_to_top_level __P((int)) __attribute__((__noreturn__)); /* Functions defined in trap.c. */ extern SigHandler *set_sigint_handler __P((void)); extern SigHandler *trap_to_sighandler __P((int)); extern sighandler trap_handler __P((int)); #endif /* _SIG_H_ */ /* siglist.h -- encapsulate various definitions for sys_siglist */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_SIGLIST_H_) #define _SIGLIST_H_ #if !defined (SYS_SIGLIST_DECLARED) && !defined (HAVE_STRSIGNAL) #if defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_SYS_SIGLIST) && !defined (sys_siglist) # define sys_siglist _sys_siglist #endif /* HAVE_UNDER_SYS_SIGLIST && !HAVE_SYS_SIGLIST && !sys_siglist */ #if !defined (sys_siglist) extern char *sys_siglist[]; #endif /* !sys_siglist */ #endif /* !SYS_SIGLIST_DECLARED && !HAVE_STRSIGNAL */ #if !defined (strsignal) && !defined (HAVE_STRSIGNAL) # define strsignal(sig) (char *)sys_siglist[sig] #endif /* !strsignal && !HAVE_STRSIGNAL */ #if !defined (strsignal) && !HAVE_DECL_STRSIGNAL extern char *strsignal __P((int)); #endif #endif /* _SIGLIST_H */ /* subst.h -- Names of externally visible functions in subst.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_SUBST_H_) #define _SUBST_H_ #include "stdc.h" /* Constants which specify how to handle backslashes and quoting in expand_word_internal (). Q_DOUBLE_QUOTES means to use the function slashify_in_quotes () to decide whether the backslash should be retained. Q_HERE_DOCUMENT means slashify_in_here_document () to decide whether to retain the backslash. Q_KEEP_BACKSLASH means to unconditionally retain the backslash. */ #define Q_DOUBLE_QUOTES 0x1 #define Q_HERE_DOCUMENT 0x2 #define Q_KEEP_BACKSLASH 0x4 #define Q_NOQUOTE 0x8 #define Q_QUOTED 0x10 #define Q_ADDEDQUOTES 0x20 #define Q_QUOTEDNULL 0x40 /* Cons a new string from STRING starting at START and ending at END, not including END. */ extern char *substring __P((char *, int, int)); /* Remove backslashes which are quoting backquotes from STRING. Modifies STRING, and returns a pointer to it. */ extern char * de_backslash __P((char *)); /* Replace instances of \! in a string with !. */ extern void unquote_bang __P((char *)); /* Extract the $( construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "$(". Make (SINDEX) get the position just after the matching ")". */ extern char *extract_command_subst __P((char *, int *)); /* Extract the $[ construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "$[". Make (SINDEX) get the position just after the matching "]". */ extern char *extract_arithmetic_subst __P((char *, int *)); #if defined (PROCESS_SUBSTITUTION) /* Extract the <( or >( construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "<(". Make (SINDEX) get the position just after the matching ")". */ extern char *extract_process_subst __P((char *, char *, int *)); #endif /* PROCESS_SUBSTITUTION */ /* Extract the name of the variable to bind to from the assignment string. */ extern char *assignment_name __P((char *)); /* Return a single string of all the words present in LIST, separating each word with a space. */ extern char *string_list __P((WORD_LIST *)); /* Turn $* into a single string, obeying POSIX rules. */ extern char *string_list_dollar_star __P((WORD_LIST *)); /* Expand $@ into a single string, obeying POSIX rules. */ extern char *string_list_dollar_at __P((WORD_LIST *, int)); /* Perform quoted null character removal on each element of LIST. This modifies LIST. */ extern void word_list_remove_quoted_nulls __P((WORD_LIST *)); /* This performs word splitting and quoted null character removal on STRING. */ extern WORD_LIST *list_string __P((char *, char *, int)); extern char *get_word_from_string __P((char **, char *, char **)); extern char *strip_trailing_ifs_whitespace __P((char *, char *, int)); /* Given STRING, an assignment string, get the value of the right side of the `=', and bind it to the left side. If EXPAND is true, then perform parameter expansion, command substitution, and arithmetic expansion on the right-hand side. Perform tilde expansion in any case. Do not perform word splitting on the result of expansion. */ extern int do_assignment __P((const char *)); extern int do_assignment_no_expand __P((const char *)); /* Append SOURCE to TARGET at INDEX. SIZE is the current amount of space allocated to TARGET. SOURCE can be NULL, in which case nothing happens. Gets rid of SOURCE by free ()ing it. Returns TARGET in case the location has changed. */ extern char *sub_append_string __P((char *, char *, int *, int *)); /* Append the textual representation of NUMBER to TARGET. INDEX and SIZE are as in SUB_APPEND_STRING. */ extern char *sub_append_number __P((long, char *, int *, int *)); /* Return the word list that corresponds to `$*'. */ extern WORD_LIST *list_rest_of_args __P((void)); /* Make a single large string out of the dollar digit variables, and the rest_of_args. If DOLLAR_STAR is 1, then obey the special case of "$*" with respect to IFS. */ extern char *string_rest_of_args __P((int)); extern int number_of_args __P((void)); /* Expand STRING by performing parameter expansion, command substitution, and arithmetic expansion. Dequote the resulting WORD_LIST before returning it, but do not perform word splitting. The call to remove_quoted_nulls () is made here because word splitting normally takes care of quote removal. */ extern WORD_LIST *expand_string_unsplit __P((char *, int)); /* Expand a prompt string. */ extern WORD_LIST *expand_prompt_string __P((char *, int)); /* Expand STRING just as if you were expanding a word. This also returns a list of words. Note that filename globbing is *NOT* done for word or string expansion, just when the shell is expanding a command. This does parameter expansion, command substitution, arithmetic expansion, and word splitting. Dequote the resultant WORD_LIST before returning. */ extern WORD_LIST *expand_string __P((char *, int)); /* Convenience functions that expand strings to strings, taking care of converting the WORD_LIST * returned by the expand_string* functions to a string and deallocating the WORD_LIST *. */ extern char *expand_string_to_string __P((char *, int)); extern char *expand_string_unsplit_to_string __P((char *, int)); /* De-quoted quoted characters in STRING. */ extern char *dequote_string __P((char *)); /* Expand WORD, performing word splitting on the result. This does parameter expansion, command substitution, arithmetic expansion, word splitting, and quote removal. */ extern WORD_LIST *expand_word __P((WORD_DESC *, int)); /* Expand WORD, but do not perform word splitting on the result. This does parameter expansion, command substitution, arithmetic expansion, and quote removal. */ extern WORD_LIST *expand_word_unsplit __P((WORD_DESC *, int)); extern WORD_LIST *expand_word_leave_quoted __P((WORD_DESC *, int)); /* Return the value of a positional parameter. This handles values > 10. */ extern char *get_dollar_var_value __P((long)); /* Quote a string to protect it from word splitting. */ extern char *quote_string __P((char *)); /* Quote escape characters (characters special to interals of expansion) in a string. */ extern char *quote_escapes __P((char *)); /* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the backslash quoting rules for within double quotes. */ extern char *string_quote_removal __P((char *, int)); /* Perform quote removal on word WORD. This allocates and returns a new WORD_DESC *. */ extern WORD_DESC *word_quote_removal __P((WORD_DESC *, int)); /* Perform quote removal on all words in LIST. If QUOTED is non-zero, the members of the list are treated as if they are surrounded by double quotes. Return a new list, or NULL if LIST is NULL. */ extern WORD_LIST *word_list_quote_removal __P((WORD_LIST *, int)); /* This splits a single word into a WORD LIST on $IFS, but only if the word is not quoted. list_string () performs quote removal for us, even if we don't do any splitting. */ extern WORD_LIST *word_split __P((WORD_DESC *)); /* Take the list of words in LIST and do the various substitutions. Return a new list of words which is the expanded list, and without things like variable assignments. */ extern WORD_LIST *expand_words __P((WORD_LIST *)); /* Same as expand_words (), but doesn't hack variable or environment variables. */ extern WORD_LIST *expand_words_no_vars __P((WORD_LIST *)); /* Perform the `normal shell expansions' on a WORD_LIST. These are brace expansion, tilde expansion, parameter and variable substitution, command substitution, arithmetic expansion, and word splitting. */ extern WORD_LIST *expand_words_shellexp __P((WORD_LIST *)); extern char *command_substitute __P((char *, int)); extern char *pat_subst __P((char *, char *, char *, int)); extern void unlink_fifo_list __P((void)); extern WORD_LIST *list_string_with_quotes __P((char *)); #if defined (ARRAY_VARS) extern char *extract_array_assignment_list __P((char *, int *)); #endif #if defined (COND_COMMAND) extern char *remove_backslashes __P((char *)); extern char *cond_expand_word __P((WORD_DESC *, int)); #endif #if defined (READLINE) extern int char_is_quoted __P((char *, int)); extern int unclosed_pair __P((char *, int, char *)); extern int skip_to_delim __P((char *, int, char *)); extern WORD_LIST *split_at_delims __P((char *, int, char *, int, int *, int *)); #endif /* How to determine the quoted state of the character C. */ #define QUOTED_CHAR(c) ((c) == CTLESC) /* Is the first character of STRING a quoted NULL character? */ #define QUOTED_NULL(string) ((string)[0] == CTLNUL && (string)[1] == '\0') #endif /* !_SUBST_H_ */ /* 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 */ /* Defines for use by the rest of the shell. */ extern const int sh_syntaxtab[]; #define shellmeta(c) (sh_syntaxtab[(c)] & CSHMETA) #define shellbreak(c) (sh_syntaxtab[(c)] & CSHBRK) #define shellquote(c) (sh_syntaxtab[(c)] & CQUOTE) #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' #endif /* _SYNTAX_H_ */ /* test.h -- external interface to the conditional command code. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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-1307, USA. */ #ifndef _TEST_H_ #define _TEST_H_ #include "stdc.h" /* Values for the flags argument to binary_test */ #define TEST_PATMATCH 0x01 #define TEST_ARITHEXP 0x02 extern int test_eaccess __P((char *, int)); extern int test_unop __P((char *)); extern int test_binop __P((char *)); extern int unary_test __P((char *, char *)); extern int binary_test __P((char *, char *, char *, int)); extern int test_command __P((int, char **)); #endif /* _TEST_H_ */ /* trap.h -- data structures used in the trap mechanism. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_TRAP_H_) #define _TRAP_H_ #include "stdc.h" #if !defined (SIG_DFL) #include "bashtypes.h" #include #endif /* SIG_DFL */ #if !defined (NSIG) #define NSIG 64 #endif /* !NSIG */ #define NO_SIG -1 #define DEFAULT_SIG SIG_DFL #define IGNORE_SIG SIG_IGN /* Special shell trap names. */ #define DEBUG_TRAP NSIG #define ERROR_TRAP NSIG+1 #define EXIT_TRAP 0 /* system signals plus special bash traps */ #define BASH_NSIG NSIG+2 #define signal_object_p(x) (decode_signal (x) != NO_SIG) #define TRAP_STRING(s) \ (signal_is_trapped (s) && signal_is_ignored (s) == 0) ? trap_list[s] \ : (char *)NULL extern char *trap_list[]; /* Externally-visible functions declared in trap.c. */ extern void initialize_traps __P((void)); extern void run_pending_traps __P((void)); extern void maybe_set_sigchld_trap __P((char *)); extern void set_sigchld_trap __P((char *)); extern void set_debug_trap __P((char *)); extern void set_error_trap __P((char *)); extern void set_sigint_trap __P((char *)); extern void set_signal __P((int, char *)); extern void restore_default_signal __P((int)); extern void ignore_signal __P((int)); extern int run_exit_trap __P((void)); extern void run_trap_cleanup __P((int)); extern void run_debug_trap __P((void)); extern void run_error_trap __P((void)); extern void free_trap_strings __P((void)); extern void reset_signal_handlers __P((void)); extern void restore_original_signals __P((void)); extern char *signal_name __P((int)); extern int decode_signal __P((char *)); extern void run_interrupt_trap __P((void)); extern int maybe_call_trap_handler __P((int)); extern int signal_is_trapped __P((int)); extern int signal_is_ignored __P((int)); extern int signal_is_special __P((int)); extern void set_signal_ignored __P((int)); #endif /* _TRAP_H_ */ /* unwind_prot.h - Macros and functions for hacking unwind protection. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_UNWIND_PROT_H) #define _UNWIND_PROT_H /* Run a function without interrupts. */ extern void begin_unwind_frame __P((char *)); extern void discard_unwind_frame __P((char *)); extern void run_unwind_frame __P((char *)); extern void add_unwind_protect (); /* Not portable to arbitrary C99 hosts. */ extern void remove_unwind_protect __P((void)); extern void run_unwind_protects __P((void)); extern void clear_unwind_protect_list __P((int)); /* Define for people who like their code to look a certain way. */ #define end_unwind_frame() /* How to protect a variable. */ #define unwind_protect_var(X) unwind_protect_mem ((char *)&(X), sizeof (X)) extern void unwind_protect_mem __P((char *, int)); /* Backwards compatibility */ #define unwind_protect_int unwind_protect_var #define unwind_protect_short unwind_protect_var #define unwind_protect_string unwind_protect_var #define unwind_protect_pointer unwind_protect_var #define unwind_protect_jmp_buf unwind_protect_var #endif /* _UNWIND_PROT_H */ /* variables.h -- data structures for shell variables. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_VARIABLES_H_) #define _VARIABLES_H_ #include "stdc.h" #include "array.h" /* Shell variables and functions are stored in hash tables. */ #include "hashlib.h" #include "conftypes.h" /* What a shell variable looks like. */ typedef struct variable *DYNAMIC_FUNC (); typedef struct variable { char *name; /* Symbol that the user types. */ char *value; /* Value that is returned. */ char *exportstr; /* String for the environment. */ DYNAMIC_FUNC *dynamic_value; /* Function called to return a `dynamic' value for a variable, like $SECONDS or $RANDOM. */ DYNAMIC_FUNC *assign_func; /* Function called when this `special variable' is assigned a value in bind_variable. */ int attributes; /* export, readonly, array, invisible... */ int context; /* Which context this variable belongs to. */ struct variable *prev_context; /* Value from previous context or NULL. */ } SHELL_VAR; /* The various attributes that a given variable can have. */ #define att_exported 0x001 /* export to environment */ #define att_readonly 0x002 /* cannot change */ #define att_invisible 0x004 /* cannot see */ #define att_array 0x008 /* value is an array */ #define att_nounset 0x010 /* cannot unset */ #define att_function 0x020 /* value is a function */ #define att_integer 0x040 /* internal representation is int */ #define att_imported 0x080 /* came from environment */ #define att_local 0x100 /* variable is local to a function */ #define att_tempvar 0x200 /* variable came from the temp environment */ #define att_importstr 0x400 /* exportstr points into initial environment */ #define att_noassign 0x800 /* assignment not allowed */ #define exported_p(var) ((((var)->attributes) & (att_exported))) #define readonly_p(var) ((((var)->attributes) & (att_readonly))) #define invisible_p(var) ((((var)->attributes) & (att_invisible))) #define array_p(var) ((((var)->attributes) & (att_array))) #define non_unsettable_p(var) ((((var)->attributes) & (att_nounset))) #define function_p(var) ((((var)->attributes) & (att_function))) #define integer_p(var) ((((var)->attributes) & (att_integer))) #define imported_p(var) ((((var)->attributes) & (att_imported))) #define local_p(var) ((((var)->attributes) & (att_local))) #define tempvar_p(var) ((((var)->attributes) & (att_tempvar))) #define noassign_p(var) ((((var)->attributes) & (att_noassign))) #define value_cell(var) ((var)->value) #define function_cell(var) (COMMAND *)((var)->value) #define array_cell(var) ((ARRAY *)(var)->value) #define SETVARATTR(var, attr, undo) \ ((undo == 0) ? ((var)->attributes |= (attr)) \ : ((var)->attributes &= ~(attr))) #define VSETATTR(var, attr) ((var)->attributes |= (attr)) #define VUNSETATTR(var, attr) ((var)->attributes &= ~(attr)) #define VGETFLAGS(var) ((var)->attributes) #define VSETFLAGS(var, flags) ((var)->attributes = (flags)) #define VCLRFLAGS(var) ((var)->attributes = 0) /* Macros to perform various operations on `exportstr' member of a SHELL_VAR. */ #define CLEAR_EXPORTSTR(var) (var)->exportstr = (char *)NULL #define COPY_EXPORTSTR(var) ((var)->exportstr) ? savestring ((var)->exportstr) : (char *)NULL #define SET_EXPORTSTR(var, value) (var)->exportstr = (value) #define SAVE_EXPORTSTR(var, value) (var)->exportstr = (value) ? savestring (value) : (char *)NULL #define FREE_EXPORTSTR(var) \ do { \ if ((var)->exportstr) \ { \ if (((var)->attributes & att_importstr) == 0) \ free ((var)->exportstr); \ } \ } while (0) #if 0 #define CACHE_IMPORTSTR(var, value) \ do { \ (var)->exportstr = value; \ (var)->attributes |= att_importstr; \ } while (0) #else #define CACHE_IMPORTSTR(var, value) \ do { \ (var)->exportstr = savestring (value); \ } while (0) #endif #define INVALIDATE_EXPORTSTR(var) \ do { \ if ((var)->exportstr) \ { \ if (((var)->attributes & att_importstr) == 0) \ free ((var)->exportstr); \ (var)->exportstr = (char *)NULL; \ (var)->attributes &= ~att_importstr; \ } \ } while (0) /* Stuff for hacking variables. */ typedef int sh_var_map_func_t __P((SHELL_VAR *)); extern int variable_context; extern HASH_TABLE *shell_variables, *shell_functions; extern char *dollar_vars[]; extern char **export_env; extern char **non_unsettable_vars; extern void initialize_shell_variables __P((char **, int)); extern SHELL_VAR *set_if_not __P((char *, char *)); extern void sh_set_lines_and_columns __P((int, int)); extern void set_pwd __P((void)); extern void set_ppid __P((void)); extern void make_funcname_visible __P((int)); extern SHELL_VAR *var_lookup __P((const char *, HASH_TABLE *)); extern SHELL_VAR *find_function __P((const char *)); extern SHELL_VAR *find_variable __P((const char *)); extern SHELL_VAR *find_variable_internal __P((const char *, int)); extern SHELL_VAR *find_tempenv_variable __P((const char *)); extern SHELL_VAR *copy_variable __P((SHELL_VAR *)); extern SHELL_VAR *make_local_variable __P((const char *)); extern SHELL_VAR *bind_variable __P((const char *, char *)); extern SHELL_VAR *bind_function __P((const char *, COMMAND *)); extern SHELL_VAR **map_over __P((sh_var_map_func_t *, HASH_TABLE *)); extern SHELL_VAR **all_shell_variables __P((void)); extern SHELL_VAR **all_shell_functions __P((void)); extern SHELL_VAR **all_visible_variables __P((void)); extern SHELL_VAR **all_visible_functions __P((void)); extern SHELL_VAR **all_exported_variables __P((void)); #if defined (ARRAY_VARS) extern SHELL_VAR **all_array_variables __P((void)); #endif extern char **all_variables_matching_prefix __P((const char *)); extern char **make_var_array __P((HASH_TABLE *)); extern char **add_or_supercede_exported_var __P((char *, int)); extern char *get_string_value __P((const char *)); extern char *make_variable_value __P((SHELL_VAR *, char *)); extern SHELL_VAR *bind_variable_value __P((SHELL_VAR *, char *)); extern SHELL_VAR *bind_int_variable __P((char *, char *)); extern SHELL_VAR *bind_var_to_int __P((char *, long)); extern int assignment __P((const char *)); extern int variable_in_context __P((SHELL_VAR *)); extern int assign_in_env __P((const char *)); extern int unbind_variable __P((const char *)); extern int makunbound __P((const char *, HASH_TABLE *)); extern int kill_local_variable __P((const char *)); extern void delete_all_variables __P((HASH_TABLE *)); extern void adjust_shell_level __P((int)); extern void non_unsettable __P((char *)); extern void dispose_variable __P((SHELL_VAR *)); extern void dispose_used_env_vars __P((void)); extern void dispose_function_env __P((void)); extern void dispose_builtin_env __P((void)); extern void merge_temporary_env __P((void)); extern void merge_builtin_env __P((void)); extern void merge_function_env __P((void)); extern void kill_all_local_variables __P((void)); extern void set_var_read_only __P((char *)); extern void set_func_read_only __P((const char *)); extern void set_var_auto_export __P((char *)); extern void set_func_auto_export __P((const char *)); extern void sort_variables __P((SHELL_VAR **)); extern void maybe_make_export_env __P((void)); extern void update_export_env_inplace __P((char *, int, char *)); extern void put_command_name_into_env __P((char *)); extern void put_gnu_argv_flags_into_env __P((long, char *)); extern void print_var_list __P((SHELL_VAR **)); extern void print_func_list __P((SHELL_VAR **)); extern void print_assignment __P((SHELL_VAR *)); extern void print_var_value __P((SHELL_VAR *, int)); extern void print_var_function __P((SHELL_VAR *)); extern char *indirection_level_string __P((void)); #if defined (ARRAY_VARS) extern SHELL_VAR *make_new_array_variable __P((char *)); extern SHELL_VAR *make_local_array_variable __P((char *)); extern void set_pipestatus_array __P((int *)); #endif extern void set_pipestatus_from_exit __P((int)); /* The variable in NAME has just had its state changed. Check to see if it is one of the special ones where something special happens. */ extern void stupidly_hack_special_variables __P((char *)); extern int get_random_number __P((void)); /* The `special variable' functions that get called when a particular variable is set. */ extern void sv_path __P((char *)); extern void sv_mail __P((char *)); extern void sv_globignore __P((char *)); extern void sv_ignoreeof __P((char *)); extern void sv_strict_posix __P((char *)); extern void sv_optind __P((char *)); extern void sv_opterr __P((char *)); extern void sv_locale __P((char *)); #if defined (READLINE) extern void sv_terminal __P((char *)); extern void sv_hostfile __P((char *)); #endif #if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) extern void sv_tz __P((char *)); #endif #if defined (HISTORY) extern void sv_histsize __P((char *)); extern void sv_histignore __P((char *)); extern void sv_history_control __P((char *)); # if defined (BANG_HISTORY) extern void sv_histchars __P((char *)); # endif #endif /* HISTORY */ #endif /* !_VARIABLES_H_ */ /* xmalloc.h -- defines for the `x' memory allocation functions */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (_XMALLOC_H_) #define _XMALLOC_H_ #include "stdc.h" #include "bashansi.h" /* Generic pointer type. */ #ifndef PTR_T #if defined (__STDC__) # define PTR_T void * #else # define PTR_T char * #endif #endif /* PTR_T */ /* Allocation functions in xmalloc.c */ extern PTR_T xmalloc __P((size_t)); extern PTR_T xrealloc __P((void *, size_t)); extern void xfree __P((void *)); #ifdef USING_BASH_MALLOC extern PTR_T sh_xmalloc __P((size_t, const char *, int)); extern PTR_T sh_xrealloc __P((void *, size_t, const char *, int)); extern void sh_xfree __P((void *, const char *, int)); #define xmalloc(x) sh_xmalloc((x), __FILE__, __LINE__) #define xrealloc(x, n) sh_xrealloc((x), (n), __FILE__, __LINE__) #define xfree(x) sh_xfree((x), __FILE__, __LINE__) #ifdef free #undef free #endif #define free(x) sh_xfree((x), __FILE__, __LINE__) #endif /* USING_BASH_MALLOC */ #endif /* _XMALLOC_H_ */ typedef union { WORD_DESC *word; /* the word that we read. */ int number; /* the number that we read. */ WORD_LIST *word_list; COMMAND *command; REDIRECT *redirect; ELEMENT element; PATTERN_LIST *pattern; } YYSTYPE; #define IF 257 #define THEN 258 #define ELSE 259 #define ELIF 260 #define FI 261 #define CASE 262 #define ESAC 263 #define FOR 264 #define SELECT 265 #define WHILE 266 #define UNTIL 267 #define DO 268 #define DONE 269 #define FUNCTION 270 #define COND_START 271 #define COND_END 272 #define COND_ERROR 273 #define IN 274 #define BANG 275 #define TIME 276 #define TIMEOPT 277 #define WORD 278 #define ASSIGNMENT_WORD 279 #define NUMBER 280 #define ARITH_CMD 281 #define ARITH_FOR_EXPRS 282 #define COND_CMD 283 #define AND_AND 284 #define OR_OR 285 #define GREATER_GREATER 286 #define LESS_LESS 287 #define LESS_AND 288 #define GREATER_AND 289 #define SEMI_SEMI 290 #define LESS_LESS_MINUS 291 #define AND_GREATER 292 #define LESS_GREATER 293 #define GREATER_BAR 294 #define yacc_EOF 295 extern YYSTYPE yylval; /* alias.c -- Not a full alias, but just the kind that we use in the shell. Csh style alias is somewhere else (`over there, in a box'). */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (ALIAS) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include #include "chartypes.h" #include "bashansi.h" #include "command.h" #include "general.h" #include "externs.h" #include "alias.h" #if defined (PROGRAMMABLE_COMPLETION) # include "pcomplete.h" #endif typedef int sh_alias_map_func_t __P((alias_t *)); static void free_alias_data __P((PTR_T)); static alias_t **map_over_aliases __P((sh_alias_map_func_t *)); static void sort_aliases __P((alias_t **)); static int qsort_alias_compare __P((alias_t **, alias_t **)); #if defined (READLINE) static int skipquotes __P((char *, int)); static int skipws __P((char *, int)); static int rd_token __P((char *, int)); #endif /* Non-zero means expand all words on the line. Otherwise, expand after first expansion if the expansion ends in a space. */ int alias_expand_all = 0; /* The list of aliases that we have. */ HASH_TABLE *aliases = (HASH_TABLE *)NULL; void initialize_aliases () { if (!aliases) aliases = make_hash_table (0); } /* Scan the list of aliases looking for one with NAME. Return NULL if the alias doesn't exist, else a pointer to the alias_t. */ alias_t * find_alias (name) char *name; { BUCKET_CONTENTS *al; if (aliases == 0) return ((alias_t *)NULL); al = find_hash_item (name, aliases); return (al ? (alias_t *)al->data : (alias_t *)NULL); } /* Return the value of the alias for NAME, or NULL if there is none. */ char * get_alias_value (name) char *name; { alias_t *alias; if (aliases == 0) return ((char *)NULL); alias = find_alias (name); return (alias ? alias->value : (char *)NULL); } /* Make a new alias from NAME and VALUE. If NAME can be found, then replace its value. */ void add_alias (name, value) char *name, *value; { BUCKET_CONTENTS *elt; alias_t *temp; int n; if (!aliases) { initialize_aliases (); temp = (alias_t *)NULL; } else temp = find_alias (name); if (temp) { free (temp->value); temp->value = savestring (value); n = value[strlen (value) - 1]; if (n == ' ' || n == '\t') temp->flags |= AL_EXPANDNEXT; } else { temp = (alias_t *)xmalloc (sizeof (alias_t)); temp->name = savestring (name); temp->value = savestring (value); temp->flags = 0; n = value[strlen (value) - 1]; if (n == ' ' || n == '\t') temp->flags |= AL_EXPANDNEXT; elt = add_hash_item (savestring (name), aliases); elt->data = (char *)temp; #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_aliases); #endif } } /* Delete a single alias structure. */ static void free_alias_data (data) PTR_T data; { register alias_t *a; a = (alias_t *)data; free (a->value); free (a->name); free (data); } /* Remove the alias with name NAME from the alias table. Returns the number of aliases left in the table, or -1 if the alias didn't exist. */ int remove_alias (name) char *name; { BUCKET_CONTENTS *elt; if (aliases == 0) return (-1); elt = remove_hash_item (name, aliases); if (elt) { free_alias_data (elt->data); free (elt->key); /* alias name */ free (elt); /* XXX */ #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_aliases); #endif return (aliases->nentries); } return (-1); } /* Delete all aliases. */ void delete_all_aliases () { if (aliases == 0) return; flush_hash_table (aliases, free_alias_data); dispose_hash_table (aliases); aliases = (HASH_TABLE *)NULL; #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_aliases); #endif } /* Return an array of aliases that satisfy the conditions tested by FUNCTION. If FUNCTION is NULL, return all aliases. */ static alias_t ** map_over_aliases (function) sh_alias_map_func_t *function; { register int i; register BUCKET_CONTENTS *tlist; alias_t *alias, **list; int list_index, list_size; list = (alias_t **)NULL; for (i = list_index = list_size = 0; i < aliases->nbuckets; i++) { tlist = get_hash_bucket (i, aliases); while (tlist) { alias = (alias_t *)tlist->data; if (!function || (*function) (alias)) { if (list_index + 1 >= list_size) { list_size += 20; list = (alias_t **)xrealloc (list, list_size * sizeof (alias_t *)); } list[list_index++] = alias; list[list_index] = (alias_t *)NULL; } tlist = tlist->next; } } return (list); } static void sort_aliases (array) alias_t **array; { qsort (array, array_len ((char **)array), sizeof (alias_t *), (QSFUNC *)qsort_alias_compare); } static int qsort_alias_compare (as1, as2) alias_t **as1, **as2; { int result; if ((result = (*as1)->name[0] - (*as2)->name[0]) == 0) result = strcmp ((*as1)->name, (*as2)->name); return (result); } /* Return a sorted list of all defined aliases */ alias_t ** all_aliases () { alias_t **list; if (!aliases) return ((alias_t **)NULL); list = map_over_aliases ((sh_alias_map_func_t *)NULL); if (list) sort_aliases (list); return (list); } char * alias_expand_word (s) char *s; { alias_t *r; r = find_alias (s); return (r ? savestring (r->value) : (char *)NULL); } /* Readline support functions -- expand all aliases in a line. */ #if defined (READLINE) /* Return non-zero if CHARACTER is a member of the class of characters that are self-delimiting in the shell (this really means that these characters delimit tokens). */ #define self_delimiting(character) (member ((character), " \t\n\r;|&()")) /* Return non-zero if CHARACTER is a member of the class of characters that delimit commands in the shell. */ #define command_separator(character) (member ((character), "\r\n;|&(")) /* If this is 1, we are checking the next token read for alias expansion because it is the first word in a command. */ static int command_word; /* This is for skipping quoted strings in alias expansions. */ #define quote_char(c) (((c) == '\'') || ((c) == '"')) /* Consume a quoted string from STRING, starting at string[START] (so string[START] is the opening quote character), and return the index of the closing quote character matching the opening quote character. This handles single matching pairs of unquoted quotes; it could afford to be a little smarter... This skips words between balanced pairs of quotes, words where the first character is quoted with a `\', and other backslash-escaped characters. */ static int skipquotes (string, start) char *string; int start; { register int i; int delimiter = string[start]; /* i starts at START + 1 because string[START] is the opening quote character. */ for (i = start + 1 ; string[i] ; i++) { if (string[i] == '\\') { i++; /* skip backslash-quoted quote characters, too */ continue; } if (string[i] == delimiter) return i; } return (i); } /* Skip the white space and any quoted characters in STRING, starting at START. Return the new index into STRING, after zero or more characters have been skipped. */ static int skipws (string, start) char *string; int start; { register int i; int pass_next, backslash_quoted_word; unsigned char peekc; /* skip quoted strings, in ' or ", and words in which a character is quoted with a `\'. */ i = backslash_quoted_word = pass_next = 0; /* Skip leading whitespace (or separator characters), and quoted words. But save it in the output. */ for (i = start; string[i]; i++) { if (pass_next) { pass_next = 0; continue; } if (whitespace (string[i])) { backslash_quoted_word = 0; /* we are no longer in a backslash-quoted word */ continue; } if (string[i] == '\\') { peekc = string[i+1]; if (ISLETTER (peekc)) backslash_quoted_word++; /* this is a backslash-quoted word */ else pass_next++; continue; } /* This only handles single pairs of non-escaped quotes. This overloads backslash_quoted_word to also mean that a word like ""f is being scanned, so that the quotes will inhibit any expansion of the word. */ if (quote_char(string[i])) { i = skipquotes (string, i); /* This could be a line that contains a single quote character, in which case skipquotes () terminates with string[i] == '\0' (the end of the string). Check for that here. */ if (string[i] == '\0') break; peekc = string[i + 1]; if (ISLETTER (peekc)) backslash_quoted_word++; continue; } /* If we're in the middle of some kind of quoted word, let it pass through. */ if (backslash_quoted_word) continue; /* If this character is a shell command separator, then set a hint for alias_expand that the next token is the first word in a command. */ if (command_separator (string[i])) { command_word++; continue; } break; } return (i); } /* Characters that may appear in a token. Basically, anything except white space and a token separator. */ #define token_char(c) (!((whitespace (string[i]) || self_delimiting (string[i])))) /* Read from START in STRING until the next separator character, and return the index of that separator. Skip backslash-quoted characters. Call skipquotes () for quoted strings in the middle or at the end of tokens, so all characters show up (e.g. foo'' and foo""bar) */ static int rd_token (string, start) char *string; int start; { register int i; /* From here to next separator character is a token. */ for (i = start; string[i] && token_char (string[i]); i++) { if (string[i] == '\\') { i++; /* skip backslash-escaped character */ continue; } /* If this character is a quote character, we want to call skipquotes to get the whole quoted portion as part of this word. That word will not generally match an alias, even if te unquoted word would have. The presence of the quotes in the token serves then to inhibit expansion. */ if (quote_char (string[i])) { i = skipquotes (string, i); /* This could be a line that contains a single quote character, in which case skipquotes () terminates with string[i] == '\0' (the end of the string). Check for that here. */ if (string[i] == '\0') break; /* Now string[i] is the matching quote character, and the quoted portion of the token has been scanned. */ continue; } } return (i); } /* Return a new line, with any aliases substituted. */ char * alias_expand (string) char *string; { register int i, j, start; char *line, *token; int line_len, tl, real_start, expand_next, expand_this_token; alias_t *alias; line_len = strlen (string) + 1; line = (char *)xmalloc (line_len); token = (char *)xmalloc (line_len); line[0] = i = 0; expand_next = 0; command_word = 1; /* initialized to expand the first word on the line */ /* Each time through the loop we find the next word in line. If it has an alias, substitute the alias value. If the value ends in ` ', then try again with the next word. Else, if there is no value, or if the value does not end in space, we are done. */ for (;;) { token[0] = 0; start = i; /* Skip white space and quoted characters */ i = skipws (string, start); if (start == i && string[i] == '\0') { free (token); return (line); } /* copy the just-skipped characters into the output string, expanding it if there is not enough room. */ j = strlen (line); tl = i - start; /* number of characters just skipped */ RESIZE_MALLOCED_BUFFER (line, j, (tl + 1), line_len, (tl + 50)); strncpy (line + j, string + start, tl); line[j + tl] = '\0'; real_start = i; command_word = command_word || (command_separator (string[i])); expand_this_token = (command_word || expand_next); expand_next = 0; /* Read the next token, and copy it into TOKEN. */ start = i; i = rd_token (string, start); tl = i - start; /* token length */ /* If tl == 0, but we're not at the end of the string, then we have a single-character token, probably a delimiter */ if (tl == 0 && string[i] != '\0') { tl = 1; i++; /* move past it */ } strncpy (token, string + start, tl); token [tl] = '\0'; /* If there is a backslash-escaped character quoted in TOKEN, then we don't do alias expansion. This should check for all other quoting characters, too. */ if (strchr (token, '\\')) expand_this_token = 0; /* If we should be expanding here, if we are expanding all words, or if we are in a location in the string where an expansion is supposed to take place, see if this word has a substitution. If it does, then do the expansion. Note that we defer the alias value lookup until we are sure we are expanding this token. */ if ((token[0]) && (expand_this_token || alias_expand_all) && (alias = find_alias (token))) { char *v; int vlen, llen; v = alias->value; vlen = strlen (v); llen = strlen (line); /* +3 because we possibly add one more character below. */ RESIZE_MALLOCED_BUFFER (line, llen, (vlen + 3), line_len, (vlen + 50)); strcpy (line + llen, v); if ((expand_this_token && vlen && whitespace (v[vlen - 1])) || alias_expand_all) expand_next = 1; } else { int llen, tlen; llen = strlen (line); tlen = i - real_start; /* tlen == strlen(token) */ RESIZE_MALLOCED_BUFFER (line, llen, (tlen + 1), line_len, (llen + tlen + 50)); strncpy (line + llen, string + real_start, tlen); line[llen + tlen] = '\0'; } command_word = 0; } } #endif /* READLINE */ #endif /* ALIAS */ /* * array.c - functions to create, destroy, access, and manipulate arrays * of strings. * * Arrays are sparse doubly-linked lists. An element's index is stored * with it. * * Chet Ramey * chet@ins.cwru.edu */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 (ARRAY_VARS) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include #include "bashansi.h" #include "shell.h" #include "array.h" #include "builtins/common.h" #define ADD_BEFORE(ae, new) \ do { \ ae->prev->next = new; \ new->prev = ae->prev; \ ae->prev = new; \ new->next = ae; \ } while(0) /* * Allocate and return a new array element with index INDEX and value * VALUE. */ ARRAY_ELEMENT * new_array_element(indx, value) arrayind_t indx; char *value; { ARRAY_ELEMENT *r; r = (ARRAY_ELEMENT *)xmalloc(sizeof(ARRAY_ELEMENT)); r->ind = indx; r->value = value ? savestring(value) : (char *)NULL; r->next = r->prev = (ARRAY_ELEMENT *) NULL; return(r); } void destroy_array_element(ae) ARRAY_ELEMENT *ae; { FREE(ae->value); free(ae); } ARRAY * new_array() { ARRAY *r; ARRAY_ELEMENT *head; r =(ARRAY *)xmalloc(sizeof(ARRAY)); r->type = array_indexed; r->max_index = r->max_size = -1; r->num_elements = 0; head = new_array_element(-1, (char *)NULL); /* dummy head */ head->prev = head->next = head; r->head = head; return(r); } void empty_array (a) ARRAY *a; { register ARRAY_ELEMENT *r, *r1; if (a == 0) return; for (r = element_forw(a->head); r != a->head; ) { r1 = element_forw(r); destroy_array_element(r); r = r1; } a->head->next = a->head->prev = a->head; a->max_index = a->max_size = -1; a->num_elements = a->max_size = 0; } void dispose_array(a) ARRAY *a; { if (a == 0) return; empty_array (a); destroy_array_element(a->head); free(a); } ARRAY * dup_array(a) ARRAY *a; { ARRAY *a1; ARRAY_ELEMENT *ae, *new; if (!a) return((ARRAY *) NULL); a1 = new_array(); a1->type = a->type; a1->max_index = a->max_index; a1->num_elements = a->num_elements; a1->max_size = a->max_size; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { new = new_array_element(element_index(ae), element_value(ae)); ADD_BEFORE(a1->head, new); } return(a1); } #ifdef INCLUDE_UNUSED /* * Make and return a new array composed of the elements in array A from * S to E, inclusive. */ ARRAY * dup_array_subrange(array, s, e) ARRAY *array; ARRAY_ELEMENT *s, *e; { ARRAY *a; ARRAY_ELEMENT *p, *n; arrayind_t i; a = new_array (); a->type = array->type; for (p = s, i = 0; p != e; p = element_forw(p), i++) { n = new_array_element (i, element_value(p)); ADD_BEFORE(a->head, n); } a->num_elements = a->max_index = i; return a; } #endif #ifdef INCLUDE_UNUSED ARRAY_ELEMENT * copy_array_element(ae) ARRAY_ELEMENT *ae; { return(ae ? new_array_element(element_index(ae), element_value(ae)) : (ARRAY_ELEMENT *) NULL); } #endif /* * Add a new element with index I and value V to array A (a[i] = v). */ int array_add_element(a, i, v) ARRAY *a; arrayind_t i; char *v; { register ARRAY_ELEMENT *new, *ae; if (!a) return(-1); new = new_array_element(i, v); if (i > array_max_index(a)) { /* * Hook onto the end. This also works for an empty array. * Fast path for the common case of allocating arrays * sequentially. */ ADD_BEFORE(a->head, new); a->max_index = i; a->num_elements++; return(0); } /* * Otherwise we search for the spot to insert it. */ for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { if (element_index(ae) == i) { /* * Replacing an existing element. */ destroy_array_element(new); free(element_value(ae)); ae->value = savestring(v); return(0); } else if (element_index(ae) > i) { ADD_BEFORE(ae, new); a->num_elements++; return(0); } } return (-1); /* problem */ } /* * Delete the element with index I from array A and return it so the * caller can dispose of it. */ ARRAY_ELEMENT * array_delete_element(a, i) ARRAY *a; arrayind_t i; { register ARRAY_ELEMENT *ae; if (!a || array_empty(a)) return((ARRAY_ELEMENT *) NULL); for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) if (element_index(ae) == i) { ae->next->prev = ae->prev; ae->prev->next = ae->next; a->num_elements--; if (i == array_max_index(a)) a->max_index = element_index(ae->prev); return(ae); } return((ARRAY_ELEMENT *) NULL); } /* * Return the value of a[i]. */ char * array_reference(a, i) ARRAY *a; arrayind_t i; { register ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return((char *) NULL); for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) if (element_index(ae) == i) return(element_value(ae)); return((char *) NULL); } #ifdef TEST_ARRAY /* * Walk the array, calling FUNC once for each element, with the array * element as the argument. */ void array_walk(a, func) ARRAY *a; sh_ae_map_func_t *func; { register ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) (*func)(ae); } #endif /* * Return a string that is the concatenation of all the elements in A, * separated by SEP. */ static char * array_to_string_internal (start, end, sep, quoted) ARRAY_ELEMENT *start, *end; char *sep; int quoted; { char *result, *t; ARRAY_ELEMENT *ae; int slen, rsize, rlen, reg; if (start == end) /* XXX - should not happen */ return ((char *)NULL); slen = strlen(sep); result = NULL; for (rsize = rlen = 0, ae = start; ae != end; ae = element_forw(ae)) { if (rsize == 0) result = (char *)xmalloc (rsize = 64); if (element_value(ae)) { t = quoted ? quote_string(element_value(ae)) : element_value(ae); reg = strlen(t); RESIZE_MALLOCED_BUFFER (result, rlen, (reg + slen + 2), rsize, rsize); strcpy(result + rlen, t); rlen += reg; if (quoted && t) free(t); /* * Add a separator only after non-null elements. */ if (element_forw(ae) != end) { strcpy(result + rlen, sep); rlen += slen; } } } if (result) result[rlen] = '\0'; /* XXX */ return(result); } char * array_to_string (a, sep, quoted) ARRAY *a; char *sep; int quoted; { if (a == 0) return((char *)NULL); if (array_empty(a)) return(savestring("")); return (array_to_string_internal (element_forw(a->head), a->head, sep, quoted)); } char * array_to_assignment_string (a) ARRAY *a; { char *result, *indstr, *valstr; ARRAY_ELEMENT *ae; int rsize, rlen, elen; if (a == 0 || array_empty (a)) return((char *)NULL); result = (char *)xmalloc (rsize = 128); result[0] = '('; rlen = 1; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { indstr = itos (element_index(ae)); valstr = element_value (ae) ? sh_double_quote (element_value(ae)) : (char *)NULL; elen = STRLEN (indstr) + 8 + STRLEN (valstr); RESIZE_MALLOCED_BUFFER (result, rlen, (elen + 1), rsize, rsize); result[rlen++] = '['; strcpy (result + rlen, indstr); rlen += STRLEN (indstr); result[rlen++] = ']'; result[rlen++] = '='; if (valstr) { strcpy (result + rlen, valstr); rlen += STRLEN (valstr); } if (element_forw(ae) != a->head) result[rlen++] = ' '; FREE (indstr); FREE (valstr); } RESIZE_MALLOCED_BUFFER (result, rlen, 1, rsize, 8); result[rlen++] = ')'; result[rlen] = '\0'; return(result); } char * quoted_array_assignment_string (a) ARRAY *a; { char *vstr, *sv; sv = array_to_assignment_string (a); if (sv == 0) return ((char *)NULL); vstr = sh_single_quote (sv); free (sv); return (vstr); } #if 0 /* Determine if s2 occurs in s1. If so, return a pointer to the match in s1. The compare is case sensitive. */ static char * sindex (s1, s2) register char *s1, *s2; { register int i, l, len; for (i = 0, l = strlen(s2), len = strlen(s1); (len - i) >= l; i++) if (strncmp (s1 + i, s2, l) == 0) return (s1 + i); return ((char *)NULL); } #endif #if defined (INCLUDE_UNUSED) || defined (TEST_ARRAY) /* * Return an array consisting of elements in S, separated by SEP */ ARRAY * string_to_array(s, sep) char *s, *sep; { ARRAY *a; WORD_LIST *w; if (s == 0) return((ARRAY *)NULL); w = list_string (s, sep, 0); if (w == 0) return((ARRAY *)NULL); a = word_list_to_array (w); return (a); } #endif /* Convenience routines for the shell to translate to and from the form used by the rest of the code. */ WORD_LIST * array_to_word_list(a) ARRAY *a; { WORD_LIST *list; ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return((WORD_LIST *)NULL); list = (WORD_LIST *)NULL; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) list = make_word_list (make_bare_word(element_value(ae)), list); return (REVERSE_LIST(list, WORD_LIST *)); } char ** array_to_argv (a) ARRAY *a; { char **ret, *t; int i; ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return ((char **)NULL); ret = alloc_array (array_num_elements (a) + 1); i = 0; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { t = element_value (ae); ret[i++] = t ? savestring (t) : (char *)NULL; } ret[i] = (char *)NULL; return (ret); } ARRAY * assign_word_list (array, list) ARRAY *array; WORD_LIST *list; { register WORD_LIST *l; register arrayind_t i; for (l = list, i = 0; l; l = l->next, i++) array_add_element(array, i, l->word->word); return array; } ARRAY * word_list_to_array (list) WORD_LIST *list; { ARRAY *a; if (list == 0) return((ARRAY *)NULL); a = new_array(); return (assign_word_list (a, list)); } ARRAY * array_quote(array) ARRAY *array; { ARRAY_ELEMENT *a; char *t; if (array == 0 || array->head == 0 || array_empty (array)) return (ARRAY *)NULL; for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { t = quote_string (a->value); FREE(a->value); a->value = t; } return array; } char * array_subrange (a, start, end, quoted) ARRAY *a; arrayind_t start, end; int quoted; { ARRAY_ELEMENT *h, *p; arrayind_t i; p = array_head (a); if (p == 0 || array_empty (a) || start > array_num_elements (a)) return ((char *)NULL); for (i = 0, p = element_forw(p); p != a->head && i < start; i++, p = element_forw(p)) ; if (p == a->head) return ((char *)NULL); for (h = p; p != a->head && i < end; i++, p = element_forw(p)) ; return (array_to_string_internal (h, p, " ", quoted)); } char * array_pat_subst (a, pat, rep, mflags) ARRAY *a; char *pat, *rep; int mflags; { ARRAY *a2; ARRAY_ELEMENT *e; char *t; if (array_head (a) == 0 || array_empty (a)) return ((char *)NULL); a2 = dup_array (a); for (e = element_forw(a2->head); e != a2->head; e = element_forw(e)) { t = pat_subst(element_value(e), pat, rep, mflags); FREE(element_value(e)); e->value = t; } if (mflags & MATCH_QUOTED) array_quote (a2); t = array_to_string (a2, " ", 0); dispose_array (a2); return t; } #if defined (TEST_ARRAY) print_element(ae) ARRAY_ELEMENT *ae; { printf("array[%ld] = %s\n", element_index(ae), element_value(ae)); } print_array(a) ARRAY *a; { printf("\n"); array_walk(a, print_element); } main() { ARRAY *a, *new_a, *copy_of_a; ARRAY_ELEMENT *ae; char *s; a = new_array(); array_add_element(a, 1, "one"); array_add_element(a, 7, "seven"); array_add_element(a, 4, "four"); array_add_element(a, 1029, "one thousand twenty-nine"); array_add_element(a, 12, "twelve"); array_add_element(a, 42, "forty-two"); print_array(a); s = array_to_string (a, " ", 0); printf("s = %s\n", s); copy_of_a = string_to_array(s, " "); printf("copy_of_a:"); print_array(copy_of_a); dispose_array(copy_of_a); printf("\n"); free(s); ae = array_delete_element(a, 4); destroy_array_element(ae); ae = array_delete_element(a, 1029); destroy_array_element(ae); array_add_element(a, 16, "sixteen"); print_array(a); s = array_to_string (a, " ", 0); printf("s = %s\n", s); copy_of_a = string_to_array(s, " "); printf("copy_of_a:"); print_array(copy_of_a); dispose_array(copy_of_a); printf("\n"); free(s); array_add_element(a, 2, "two"); array_add_element(a, 1029, "new one thousand twenty-nine"); array_add_element(a, 0, "zero"); array_add_element(a, 134, ""); print_array(a); s = array_to_string (a, ":", 0); printf("s = %s\n", s); copy_of_a = string_to_array(s, ":"); printf("copy_of_a:"); print_array(copy_of_a); dispose_array(copy_of_a); printf("\n"); free(s); new_a = copy_array(a); print_array(new_a); s = array_to_string (new_a, ":", 0); printf("s = %s\n", s); copy_of_a = string_to_array(s, ":", 0); printf("copy_of_a:"); print_array(copy_of_a); dispose_array(copy_of_a); printf("\n"); free(s); dispose_array(a); dispose_array(new_a); } #endif /* TEST_ARRAY */ #endif /* ARRAY_VARS */ /* arrayfunc.c -- High-level array functions used by other parts of the shell. */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (ARRAY_VARS) #if defined (HAVE_UNISTD_H) # include #endif #include #include "shell.h" #include "builtins/common.h" extern char *this_command_name; extern int last_command_exit_value; static void quote_array_assignment_chars __P((WORD_LIST *)); static char *array_value_internal __P((char *, int, int)); /* **************************************************************** */ /* */ /* Functions to manipulate array variables and perform assignments */ /* */ /* **************************************************************** */ /* Convert a shell variable to an array variable. The original value is saved as array[0]. */ SHELL_VAR * convert_var_to_array (var) SHELL_VAR *var; { char *oldval; ARRAY *array; oldval = value_cell (var); array = new_array (); array_add_element (array, 0, oldval); FREE (value_cell (var)); var->value = (char *)array; INVALIDATE_EXPORTSTR (var); VSETATTR (var, att_array); VUNSETATTR (var, att_invisible); return var; } /* Perform an array assignment name[ind]=value. If NAME already exists and is not an array, and IND is 0, perform name=value instead. If NAME exists and is not an array, and IND is not 0, convert it into an array with the existing value as name[0]. If NAME does not exist, just create an array variable, no matter what IND's value may be. */ SHELL_VAR * bind_array_variable (name, ind, value) char *name; arrayind_t ind; char *value; { SHELL_VAR *entry; char *newval; entry = var_lookup (name, shell_variables); if (entry == (SHELL_VAR *) 0) entry = make_new_array_variable (name); else if (readonly_p (entry) || noassign_p (entry)) { if (readonly_p (entry)) report_error ("%s: readonly variable", name); return (entry); } else if (array_p (entry) == 0) entry = convert_var_to_array (entry); /* ENTRY is an array variable, and ARRAY points to the value. */ newval = make_variable_value (entry, value); if (entry->assign_func) (*entry->assign_func) (entry, ind, newval); else array_add_element (array_cell (entry), ind, newval); FREE (newval); return (entry); } /* Parse NAME, a lhs of an assignment statement of the form v[s], and assign VALUE to that array element by calling bind_array_variable(). */ SHELL_VAR * assign_array_element (name, value) char *name, *value; { char *sub, *vname; arrayind_t ind; int sublen; SHELL_VAR *entry; vname = array_variable_name (name, &sub, &sublen); if (vname == 0) return ((SHELL_VAR *)NULL); if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1)) { free (vname); report_error ("%s: bad array subscript", name); return ((SHELL_VAR *)NULL); } ind = array_expand_index (sub, sublen); if (ind < 0) { free (vname); report_error ("%s: bad array subscript", name); return ((SHELL_VAR *)NULL); } entry = bind_array_variable (vname, ind, value); free (vname); return (entry); } /* Find the array variable corresponding to NAME. If there is no variable, create a new array variable. If the variable exists but is not an array, convert it to an indexed array. If CHECK_FLAGS is non-zero, an existing variable is checked for the readonly or noassign attribute in preparation for assignment (e.g., by the `read' builtin). */ SHELL_VAR * find_or_make_array_variable (name, check_flags) char *name; int check_flags; { SHELL_VAR *var; var = find_variable (name); if (var == 0) var = make_new_array_variable (name); else if (check_flags && (readonly_p (var) || noassign_p (var))) { if (readonly_p (var)) report_error ("%s: readonly variable", name); return ((SHELL_VAR *)NULL); } else if (array_p (var) == 0) var = convert_var_to_array (var); return (var); } /* Perform a compound assignment statement for array NAME, where VALUE is the text between the parens: NAME=( VALUE ) */ SHELL_VAR * assign_array_from_string (name, value) char *name, *value; { SHELL_VAR *var; var = find_or_make_array_variable (name, 1); if (var == 0) return ((SHELL_VAR *)NULL); return (assign_array_var_from_string (var, value)); } /* Sequentially assign the indices of indexed array variable VAR from the words in LIST. */ SHELL_VAR * assign_array_var_from_word_list (var, list) SHELL_VAR *var; WORD_LIST *list; { register arrayind_t i; register WORD_LIST *l; ARRAY *a; for (a = array_cell (var), l = list, i = 0; l; l = l->next, i++) if (var->assign_func) (*var->assign_func) (var, i, l->word->word); else array_add_element (a, i, l->word->word); return var; } /* Perform a compound array assignment: VAR->name=( VALUE ). The VALUE has already had the parentheses stripped. */ SHELL_VAR * assign_array_var_from_string (var, value) SHELL_VAR *var; char *value; { ARRAY *a; WORD_LIST *list, *nlist; char *w, *val, *nval; int ni, len; arrayind_t ind, last_ind; if (value == 0) return var; /* If this is called from declare_builtin, value[0] == '(' and strchr(value, ')') != 0. In this case, we need to extract the value from between the parens before going on. */ if (*value == '(') /*)*/ { ni = 1; val = extract_array_assignment_list (value, &ni); if (val == 0) return var; } else val = value; /* Expand the value string into a list of words, performing all the shell expansions including pathname generation and word splitting. */ /* First we split the string on whitespace, using the shell parser (ksh93 seems to do this). */ list = parse_string_to_word_list (val, "array assign"); /* If we're using [subscript]=value, we need to quote each [ and ] to prevent unwanted filename expansion. */ if (list) quote_array_assignment_chars (list); /* Now that we've split it, perform the shell expansions on each word in the list. */ nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL; dispose_words (list); if (val != value) free (val); a = array_cell (var); /* Now that we are ready to assign values to the array, kill the existing value. */ if (a) empty_array (a); for (last_ind = 0, list = nlist; list; list = list->next) { w = list->word->word; /* We have a word of the form [ind]=value */ if (w[0] == '[') { len = skipsubscript (w, 0); if (w[len] != ']' || w[len+1] != '=') { nval = make_variable_value (var, w); if (var->assign_func) (*var->assign_func) (var, last_ind, nval); else array_add_element (a, last_ind, nval); FREE (nval); last_ind++; continue; } if (len == 1) { report_error ("%s: bad array subscript", w); continue; } if (ALL_ELEMENT_SUB (w[1]) && len == 2) { report_error ("%s: cannot assign to non-numeric index", w); continue; } ind = array_expand_index (w + 1, len); if (ind < 0) { report_error ("%s: bad array subscript", w); continue; } last_ind = ind; val = w + len + 2; } else /* No [ind]=value, just a stray `=' */ { ind = last_ind; val = w; } if (integer_p (var)) this_command_name = (char *)NULL; /* no command name for errors */ nval = make_variable_value (var, val); if (var->assign_func) (*var->assign_func) (var, ind, nval); else array_add_element (a, ind, nval); FREE (nval); last_ind++; } dispose_words (nlist); return (var); } /* For each word in a compound array assignment, if the word looks like [ind]=value, quote the `[' and `]' before the `=' to protect them from unwanted filename expansion. */ static void quote_array_assignment_chars (list) WORD_LIST *list; { char *s, *t, *nword; int saw_eq; WORD_LIST *l; for (l = list; l; l = l->next) { if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0') continue; /* should not happen, but just in case... */ /* Don't bother if it doesn't look like [ind]=value */ if (l->word->word[0] != '[' || strchr (l->word->word, '=') == 0) /* ] */ continue; s = nword = (char *)xmalloc (strlen (l->word->word) * 2 + 1); saw_eq = 0; for (t = l->word->word; *t; ) { if (*t == '=') saw_eq = 1; if (saw_eq == 0 && (*t == '[' || *t == ']')) *s++ = '\\'; *s++ = *t++; } *s = '\0'; free (l->word->word); l->word->word = nword; } } /* This function assumes s[i] == '['; returns with s[ret] == ']' if an array subscript is correctly parsed. */ int skipsubscript (s, i) const char *s; int i; { int count, c; for (count = 1; count && (c = s[++i]); ) { if (c == '[') count++; else if (c == ']') count--; } return i; } /* This function is called with SUB pointing to just after the beginning `[' of an array subscript and removes the array element to which SUB expands from array VAR. A subscript of `*' or `@' unsets the array. */ int unbind_array_element (var, sub) SHELL_VAR *var; char *sub; { int len; arrayind_t ind; ARRAY_ELEMENT *ae; len = skipsubscript (sub, 0); if (sub[len] != ']' || len == 0) { builtin_error ("%s[%s: bad array subscript", var->name, sub); return -1; } sub[len] = '\0'; if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0) { makunbound (var->name, shell_variables); return (0); } ind = array_expand_index (sub, len+1); if (ind < 0) { builtin_error ("[%s]: bad array subscript", sub); return -1; } ae = array_delete_element (array_cell (var), ind); if (ae) destroy_array_element (ae); return 0; } /* Format and output an array assignment in compound form VAR=(VALUES), suitable for re-use as input. */ void print_array_assignment (var, quoted) SHELL_VAR *var; int quoted; { char *vstr; if (quoted) vstr = quoted_array_assignment_string (array_cell (var)); else vstr = array_to_assignment_string (array_cell (var)); if (vstr == 0) printf ("%s=%s\n", var->name, quoted ? "'()'" : "()"); else { printf ("%s=%s\n", var->name, vstr); free (vstr); } } /***********************************************************************/ /* */ /* Utility functions to manage arrays and their contents for expansion */ /* */ /***********************************************************************/ /* Return 1 if NAME is a properly-formed array reference v[sub]. */ int valid_array_reference (name) char *name; { char *t; int r, len; t = strchr (name, '['); /* ] */ if (t) { *t = '\0'; r = legal_identifier (name); *t = '['; if (r == 0) return 0; /* Check for a properly-terminated non-blank subscript. */ len = skipsubscript (t, 0); if (t[len] != ']' || len == 1) return 0; for (r = 1; r < len; r++) if (whitespace (t[r]) == 0) return 1; return 0; } return 0; } /* Expand the array index beginning at S and extending LEN characters. */ arrayind_t array_expand_index (s, len) char *s; int len; { char *exp, *t; int expok; arrayind_t val; exp = (char *)xmalloc (len); strncpy (exp, s, len - 1); exp[len - 1] = '\0'; t = expand_string_to_string (exp, 0); this_command_name = (char *)NULL; val = evalexp (t, &expok); free (t); free (exp); if (expok == 0) { last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (DISCARD); } return val; } /* Return the name of the variable specified by S without any subscript. If SUBP is non-null, return a pointer to the start of the subscript in *SUBP. If LENP is non-null, the length of the subscript is returned in *LENP. This returns newly-allocated memory. */ char * array_variable_name (s, subp, lenp) char *s, **subp; int *lenp; { char *t, *ret; int ind, ni; t = strchr (s, '['); if (t == 0) return ((char *)NULL); ind = t - s; ni = skipsubscript (s, ind); if (ni <= ind + 1 || s[ni] != ']') { report_error ("%s: bad array subscript", s); return ((char *)NULL); } *t = '\0'; ret = savestring (s); *t++ = '['; /* ] */ if (subp) *subp = t; if (lenp) *lenp = ni - ind; return ret; } /* Return the variable specified by S without any subscript. If SUBP is non-null, return a pointer to the start of the subscript in *SUBP. If LENP is non-null, the length of the subscript is returned in *LENP. */ SHELL_VAR * array_variable_part (s, subp, lenp) char *s, **subp; int *lenp; { char *t; SHELL_VAR *var; t = array_variable_name (s, subp, lenp); if (t == 0) return ((SHELL_VAR *)NULL); var = find_variable (t); free (t); return var; } /* Return a string containing the elements in the array and subscript described by S. If the subscript is * or @, obeys quoting rules akin to the expansion of $* and $@ including double quoting. */ static char * array_value_internal (s, quoted, allow_all) char *s; int quoted, allow_all; { int len; arrayind_t ind; char *retval, *t, *temp; WORD_LIST *l; SHELL_VAR *var; var = array_variable_part (s, &t, &len); if (var == 0) return (char *)NULL; /* [ */ if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') { if (allow_all == 0) { report_error ("%s: bad array subscript", s); return ((char *)NULL); } else if (array_p (var) == 0) { l = (WORD_LIST *)NULL; l = add_string_to_list (value_cell (var), l); } else { l = array_to_word_list (array_cell (var)); if (l == (WORD_LIST *)NULL) return ((char *) NULL); } if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) { temp = string_list_dollar_star (l); retval = quote_string (temp); free (temp); } else /* ${name[@]} or unquoted ${name[*]} */ retval = string_list_dollar_at (l, quoted); dispose_words (l); } else { ind = array_expand_index (t, len); if (ind < 0) { report_error ("%s: bad array subscript", var->name); return ((char *)NULL); } if (array_p (var) == 0) return (ind == 0 ? savestring (value_cell (var)) : (char *)NULL); retval = array_reference (array_cell (var), ind); if (retval) retval = quote_escapes (retval); } return retval; } /* Return a string containing the elements described by the array and subscript contained in S, obeying quoting for subscripts * and @. */ char * array_value (s, quoted) char *s; int quoted; { return (array_value_internal (s, quoted, 1)); } /* Return the value of the array indexing expression S as a single string. If ALLOW_ALL is 0, do not allow `@' and `*' subscripts. This is used by other parts of the shell such as the arithmetic expression evaluator in expr.c. */ char * get_array_value (s, allow_all) char *s; int allow_all; { return (array_value_internal (s, 0, allow_all)); } #endif /* ARRAY_VARS */ /* bashhist.c -- bash interface to the GNU history library. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (HISTORY) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "bashtypes.h" #include #include #include "bashansi.h" #include "posixstat.h" #include "filecntl.h" #include "shell.h" #include "flags.h" #include "input.h" #include "parser.h" /* for the struct dstack stuff. */ #include "pathexp.h" /* for the struct ignorevar stuff */ #include "bashhist.h" /* matching prototypes and declarations */ #include "builtins/common.h" #include #include #include #if defined (READLINE) # include "bashline.h" #endif #if !defined (errno) extern int errno; #endif static int histignore_item_func __P((struct ign *)); static struct ignorevar histignore = { "HISTIGNORE", (struct ign *)0, 0, (char *)0, (sh_iv_item_func_t *)histignore_item_func, }; #define HIGN_EXPAND 0x01 /* Declarations of bash history variables. */ /* Non-zero means to remember lines typed to the shell on the history list. This is different than the user-controlled behaviour; this becomes zero when we read lines from a file, for example. */ int remember_on_history = 1; /* The number of lines that Bash has added to this history session. */ int history_lines_this_session; /* The number of lines that Bash has read from the history file. */ int history_lines_in_file; #if defined (BANG_HISTORY) /* Non-zero means do no history expansion on this line, regardless of what history_expansion says. */ int history_expansion_inhibited; #endif /* By default, every line is saved in the history individually. I.e., if the user enters: bash$ for i in a b c > do > echo $i > done Each line will be individually saved in the history. bash$ history 10 for i in a b c 11 do 12 echo $i 13 done 14 history If the variable command_oriented_history is set, multiple lines which form one command will be saved as one history entry. bash$ for i in a b c > do > echo $i > done bash$ history 10 for i in a b c do echo $i done 11 history The user can then recall the whole command all at once instead of just being able to recall one line at a time. */ int command_oriented_history = 1; /* Non-zero means to store newlines in the history list when using command_oriented_history rather than trying to use semicolons. */ int literal_history; /* Non-zero means to append the history to the history file at shell exit, even if the history has been stifled. */ int force_append_history; /* A nit for picking at history saving. Value of 0 means save all lines parsed by the shell on the history. Value of 1 means save all lines that do not start with a space. Value of 2 means save all lines that do not match the last line saved. */ int history_control; /* Set to 1 if the last command was added to the history list successfully as a separate history entry; set to 0 if the line was ignored or added to a previous entry as part of command-oriented-history processing. */ int hist_last_line_added; #if defined (READLINE) /* If non-zero, and readline is being used, the user is offered the chance to re-edit a failed history expansion. */ int history_reediting; /* If non-zero, and readline is being used, don't directly execute a line with history substitution. Reload it into the editing buffer instead and let the user further edit and confirm with a newline. */ int hist_verify; #endif /* READLINE */ /* Non-zero means to not save function definitions in the history list. */ int dont_save_function_defs; /* Variables declared in other files used here. */ extern int current_command_line_count; extern struct dstack dstack; static int bash_history_inhibit_expansion __P((char *, int)); #if defined (READLINE) static void re_edit __P((char *)); #endif static int history_expansion_p __P((char *)); static int shell_comment __P((char *)); static int should_expand __P((char *)); static HIST_ENTRY *last_history_entry __P((void)); static char *expand_histignore_pattern __P((char *)); static int history_should_ignore __P((char *)); /* Is the history expansion starting at string[i] one that should not be expanded? */ static int bash_history_inhibit_expansion (string, i) char *string; int i; { /* The shell uses ! as a pattern negation character in globbing [...] expressions, so let those pass without expansion. */ if (i > 0 && (string[i - 1] == '[') && member (']', string + i + 1)) return (1); /* The shell uses ! as the indirect expansion character, so let those expansions pass as well. */ else if (i > 1 && string[i - 1] == '{' && string[i - 2] == '$' && member ('}', string + i + 1)) return (1); #if defined (EXTENDED_GLOB) else if (extended_glob && i > 1 && string[i+1] == '(' && member (')', string + i + 2)) return (1); #endif else return (0); } void bash_initialize_history () { history_quotes_inhibit_expansion = 1; history_search_delimiter_chars = ";&()|<>"; history_inhibit_expansion_function = bash_history_inhibit_expansion; } void bash_history_reinit (interact) int interact; { #if defined (BANG_HISTORY) history_expansion = interact != 0; history_expansion_inhibited = 1; #endif remember_on_history = interact != 0; history_inhibit_expansion_function = bash_history_inhibit_expansion; } void bash_history_disable () { remember_on_history = 0; #if defined (BANG_HISTORY) history_expansion_inhibited = 1; #endif } void bash_history_enable () { remember_on_history = 1; #if defined (BANG_HISTORY) history_expansion_inhibited = 0; #endif history_inhibit_expansion_function = bash_history_inhibit_expansion; sv_history_control ("HISTCONTROL"); sv_histignore ("HISTIGNORE"); } /* Load the history list from the history file. */ void load_history () { char *hf; struct stat buf; /* Truncate history file for interactive shells which desire it. Note that the history file is automatically truncated to the size of HISTSIZE if the user does not explicitly set the size differently. */ set_if_not ("HISTFILESIZE", get_string_value ("HISTSIZE")); sv_histsize ("HISTFILESIZE"); /* Read the history in HISTFILE into the history list. */ hf = get_string_value ("HISTFILE"); if (hf && *hf && stat (hf, &buf) == 0) { read_history (hf); using_history (); history_lines_in_file = where_history (); } } #ifdef INCLUDE_UNUSED /* Write the existing history out to the history file. */ void save_history () { char *hf; struct stat buf; hf = get_string_value ("HISTFILE"); if (hf && *hf && stat (hf, &buf) == 0) { /* Append only the lines that occurred this session to the history file. */ using_history (); if (history_lines_this_session < where_history () || force_append_history) append_history (history_lines_this_session, hf); else write_history (hf); sv_histsize ("HISTFILESIZE"); } } #endif int maybe_append_history (filename) char *filename; { int fd, result; struct stat buf; result = EXECUTION_SUCCESS; if (history_lines_this_session && (history_lines_this_session < where_history ())) { /* If the filename was supplied, then create it if necessary. */ if (stat (filename, &buf) == -1 && errno == ENOENT) { fd = open (filename, O_WRONLY|O_CREAT, 0600); if (fd < 0) { builtin_error ("%s: cannot create: %s", filename, strerror (errno)); return (EXECUTION_FAILURE); } close (fd); } result = append_history (history_lines_this_session, filename); history_lines_in_file += history_lines_this_session; history_lines_this_session = 0; } return (result); } /* If this is an interactive shell, then append the lines executed this session to the history file. */ int maybe_save_shell_history () { int result; char *hf; struct stat buf; result = 0; if (history_lines_this_session) { hf = get_string_value ("HISTFILE"); if (hf && *hf) { /* If the file doesn't exist, then create it. */ if (stat (hf, &buf) == -1) { int file; file = open (hf, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (file != -1) close (file); } /* Now actually append the lines if the history hasn't been stifled. If the history has been stifled, rewrite the history file. */ using_history (); if (history_lines_this_session <= where_history () || force_append_history) { result = append_history (history_lines_this_session, hf); history_lines_in_file += history_lines_this_session; } else { result = write_history (hf); history_lines_in_file = history_lines_this_session; } history_lines_this_session = 0; sv_histsize ("HISTFILESIZE"); } } return (result); } #if defined (READLINE) /* Tell readline () that we have some text for it to edit. */ static void re_edit (text) char *text; { if (bash_input.type == st_stdin) bash_re_edit (text); } #endif /* READLINE */ /* Return 1 if this line needs history expansion. */ static int history_expansion_p (line) char *line; { register char *s; for (s = line; *s; s++) if (*s == history_expansion_char || *s == history_subst_char) return 1; return 0; } /* Do pre-processing on LINE. If PRINT_CHANGES is non-zero, then print the results of expanding the line if there were any changes. If there is an error, return NULL, otherwise the expanded line is returned. If ADDIT is non-zero the line is added to the history list after history expansion. ADDIT is just a suggestion; REMEMBER_ON_HISTORY can veto, and does. Right now this does history expansion. */ char * pre_process_line (line, print_changes, addit) char *line; int print_changes, addit; { char *history_value; char *return_value; int expanded; return_value = line; expanded = 0; # if defined (BANG_HISTORY) /* History expand the line. If this results in no errors, then add that line to the history if ADDIT is non-zero. */ if (!history_expansion_inhibited && history_expansion && history_expansion_p (line)) { expanded = history_expand (line, &history_value); if (expanded) { if (print_changes) { if (expanded < 0) internal_error ("%s", history_value); #if defined (READLINE) else if (hist_verify == 0 || expanded == 2) #else else #endif fprintf (stderr, "%s\n", history_value); } /* If there was an error, return NULL. */ if (expanded < 0 || expanded == 2) /* 2 == print only */ { free (history_value); # if defined (READLINE) /* New hack. We can allow the user to edit the failed history expansion. */ if (history_reediting && expanded < 0) re_edit (line); # endif /* READLINE */ return ((char *)NULL); } # if defined (READLINE) if (hist_verify && expanded == 1) { re_edit (history_value); return ((char *)NULL); } # endif } /* Let other expansions know that return_value can be free'ed, and that a line has been added to the history list. Note that we only add lines that have something in them. */ expanded = 1; return_value = history_value; } # endif /* BANG_HISTORY */ if (addit && remember_on_history && *return_value) maybe_add_history (return_value); #if 0 if (expanded == 0) return_value = savestring (line); #endif return (return_value); } /* Return 1 if the first non-whitespace character in LINE is a `#', indicating * that the line is a shell comment. */ static int shell_comment (line) char *line; { char *p; for (p = line; p && *p && whitespace (*p); p++) ; return (p && *p == '#'); } #ifdef INCLUDE_UNUSED /* Remove shell comments from LINE. A `#' and anything after it is a comment. This isn't really useful yet, since it doesn't handle quoting. */ static char * filter_comments (line) char *line; { char *p; for (p = line; p && *p && *p != '#'; p++) ; if (p && *p == '#') *p = '\0'; return (line); } #endif /* Add LINE to the history list depending on the value of HISTORY_CONTROL. */ void maybe_add_history (line) char *line; { static int first_line_saved = 0; HIST_ENTRY *temp; hist_last_line_added = 0; /* Don't use the value of history_control to affect the second and subsequent lines of a multi-line command (old code did this only when command_oriented_history is enabled). */ #if 0 if (command_oriented_history && current_command_line_count > 1) #else if (current_command_line_count > 1) #endif { if (first_line_saved && (literal_history || dstack.delimiter_depth != 0 || shell_comment (line) == 0)) bash_add_history (line); return; } /* This is the first line of a (possible multi-line) command. Note whether or not we should save the first line and remember it. */ first_line_saved = 0; switch (history_control) { case 0: first_line_saved = 1; break; case 1: if (*line != ' ') first_line_saved = 1; break; case 3: if (*line == ' ') break; /* FALLTHROUGH if case == 3 (`ignoreboth') */ case 2: using_history (); temp = previous_history (); if (temp == 0 || STREQ (temp->line, line) == 0) first_line_saved = 1; using_history (); break; } if (first_line_saved && history_should_ignore (line) == 0) bash_add_history (line); else first_line_saved = 0; } /* Add a line to the history list. The variable COMMAND_ORIENTED_HISTORY controls the style of history remembering; when non-zero, and LINE is not the first line of a complete parser construct, append LINE to the last history line instead of adding it as a new line. */ void bash_add_history (line) char *line; { int add_it, offset, curlen; HIST_ENTRY *current, *old; char *chars_to_add, *new_line; add_it = 1; if (command_oriented_history && current_command_line_count > 1) { chars_to_add = literal_history ? "\n" : history_delimiting_chars (); using_history (); current = previous_history (); if (current) { /* If the previous line ended with an escaped newline (escaped with backslash, but otherwise unquoted), then remove the quoted newline, since that is what happens when the line is parsed. */ curlen = strlen (current->line); if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\\' && current->line[curlen - 2] != '\\') { current->line[curlen - 1] = '\0'; curlen--; chars_to_add = ""; } new_line = (char *)xmalloc (1 + curlen + strlen (line) + strlen (chars_to_add)); sprintf (new_line, "%s%s%s", current->line, chars_to_add, line); offset = where_history (); old = replace_history_entry (offset, new_line, current->data); free (new_line); if (old) { FREE (old->line); free (old); } add_it = 0; } } if (add_it) { hist_last_line_added = 1; add_history (line); history_lines_this_session++; } using_history (); } int history_number () { using_history (); return (get_string_value ("HISTSIZE") ? history_base + where_history () : 1); } static int should_expand (s) char *s; { char *p; for (p = s; p && *p; p++) { if (*p == '\\') p++; else if (*p == '&') return 1; } return 0; } static int histignore_item_func (ign) struct ign *ign; { if (should_expand (ign->val)) ign->flags |= HIGN_EXPAND; return (0); } void setup_history_ignore (varname) char *varname; { setup_ignore_patterns (&histignore); } static HIST_ENTRY * last_history_entry () { HIST_ENTRY *he; using_history (); he = previous_history (); using_history (); return he; } char * last_history_line () { HIST_ENTRY *he; he = last_history_entry (); if (he == 0) return ((char *)NULL); return he->line; } static char * expand_histignore_pattern (pat) char *pat; { HIST_ENTRY *phe; char *ret; phe = last_history_entry (); if (phe == (HIST_ENTRY *)0) return (savestring (pat)); ret = strcreplace (pat, '&', phe->line, 1); return ret; } /* Return 1 if we should not put LINE into the history according to the patterns in HISTIGNORE. */ static int history_should_ignore (line) char *line; { register int i, match; char *npat; if (histignore.num_ignores == 0) return 0; for (i = match = 0; i < histignore.num_ignores; i++) { if (histignore.ignores[i].flags & HIGN_EXPAND) npat = expand_histignore_pattern (histignore.ignores[i].val); else npat = histignore.ignores[i].val; match = strmatch (npat, line, FNMATCH_EXTFLAG) != FNM_NOMATCH; if (histignore.ignores[i].flags & HIGN_EXPAND) free (npat); if (match) break; } return match; } #endif /* HISTORY */ /* bashline.c -- Bash's interface to the readline library. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (READLINE) #include "bashtypes.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include #endif #if defined (HAVE_GRP_H) # include #endif #include #include "chartypes.h" #include "bashansi.h" #include "shell.h" #include "builtins.h" #include "bashhist.h" #include "bashline.h" #include "execute_cmd.h" #include "findcmd.h" #include "pathexp.h" #include "builtins/common.h" #include #include #include #include #if defined (ALIAS) # include "alias.h" #endif #if defined (PROGRAMMABLE_COMPLETION) # include "pcomplete.h" #endif #if defined (BRACE_COMPLETION) extern int bash_brace_completion __P((int, int)); #endif /* BRACE_COMPLETION */ /* Forward declarations */ /* Functions bound to keys in Readline for Bash users. */ static int shell_expand_line __P((int, int)); static int display_shell_version __P((int, int)); static int operate_and_get_next __P((int, int)); static int bash_ignore_filenames __P((char **)); static int bash_ignore_everything __P((char **)); #if defined (BANG_HISTORY) static char *history_expand_line_internal __P((char *)); static int history_expand_line __P((int, int)); static int tcsh_magic_space __P((int, int)); #endif /* BANG_HISTORY */ #ifdef ALIAS static int alias_expand_line __P((int, int)); #endif #if defined (BANG_HISTORY) && defined (ALIAS) static int history_and_alias_expand_line __P((int, int)); #endif /* Helper functions for Readline. */ static int bash_directory_completion_hook __P((char **)); static int filename_completion_ignore __P((char **)); static int bash_push_line __P((void)); static void cleanup_expansion_error __P((void)); static void maybe_make_readline_line __P((char *)); static void set_up_new_line __P((char *)); static int check_redir __P((int)); static char **attempt_shell_completion __P((const char *, int, int)); static char *variable_completion_function __P((const char *, int)); static char *hostname_completion_function __P((const char *, int)); static char *command_subst_completion_function __P((const char *, int)); static void build_history_completion_array __P((void)); static char *history_completion_generator __P((const char *, int)); static int dynamic_complete_history __P((int, int)); static void initialize_hostname_list __P((void)); static void add_host_name __P((char *)); static void snarf_hosts_from_file __P((char *)); static char **hostnames_matching __P((char *)); static void _ignore_completion_names __P((char **, sh_ignore_func_t *)); static int name_is_acceptable __P((const char *)); static int test_for_directory __P((const char *)); static int return_zero __P((const char *)); static char *bash_dequote_filename __P((char *, int)); static char *quote_word_break_chars __P((char *)); static char *bash_quote_filename __P((char *, int, char *)); static int bash_execute_unix_command __P((int, int)); static void init_unix_command_map __P((void)); static int isolate_sequence __P((char *, int, int, int *)); static int set_saved_history __P((void)); #if defined (ALIAS) static int posix_edit_macros __P((int, int)); #endif #if defined (PROGRAMMABLE_COMPLETION) static int find_cmd_start __P((int)); static int find_cmd_end __P((int)); static char *find_cmd_name __P((int)); static char *prog_complete_return __P((const char *, int)); static char **prog_complete_matches; #endif /* Variables used here but defined in other files. */ extern int current_command_line_count; extern int posixly_correct, no_symbolic_links; extern char *current_prompt_string, *ps1_prompt; extern STRING_INT_ALIST word_token_alist[]; /* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual completion functions which indicate what type of completion should be done (at or before point) that can be bound to key sequences with the readline library. */ #define SPECIFIC_COMPLETION_FUNCTIONS #if defined (SPECIFIC_COMPLETION_FUNCTIONS) static int bash_specific_completion __P((int, rl_compentry_func_t *)); static int bash_complete_filename_internal __P((int)); static int bash_complete_username_internal __P((int)); static int bash_complete_hostname_internal __P((int)); static int bash_complete_variable_internal __P((int)); static int bash_complete_command_internal __P((int)); static int bash_complete_filename __P((int, int)); static int bash_possible_filename_completions __P((int, int)); static int bash_complete_username __P((int, int)); static int bash_possible_username_completions __P((int, int)); static int bash_complete_hostname __P((int, int)); static int bash_possible_hostname_completions __P((int, int)); static int bash_complete_variable __P((int, int)); static int bash_possible_variable_completions __P((int, int)); static int bash_complete_command __P((int, int)); static int bash_possible_command_completions __P((int, int)); static char *glob_complete_word __P((const char *, int)); static int bash_glob_completion_internal __P((int)); static int bash_glob_expand_word __P((int, int)); static int bash_glob_list_expansions __P((int, int)); #endif /* SPECIFIC_COMPLETION_FUNCTIONS */ #if defined (VI_MODE) static int vi_edit_and_execute_command __P((int, int)); #endif /* Non-zero once initalize_readline () has been called. */ int bash_readline_initialized = 0; /* If non-zero, we do hostname completion, breaking words at `@' and trying to complete the stuff after the `@' from our own internal host list. */ int perform_hostname_completion = 1; /* If non-zero, we don't do command completion on an empty line. */ int no_empty_command_completion; static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:"; static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:"; static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL; /* What kind of quoting is performed by bash_quote_filename: COMPLETE_DQUOTE = double-quoting the filename COMPLETE_SQUOTE = single_quoting the filename COMPLETE_BSQUOTE = backslash-quoting special chars in the filename */ #define COMPLETE_DQUOTE 1 #define COMPLETE_SQUOTE 2 #define COMPLETE_BSQUOTE 3 static int completion_quoting_style = COMPLETE_BSQUOTE; /* Change the readline VI-mode keymaps into or out of Posix.2 compliance. Called when the shell is put into or out of `posix' mode. */ void posix_readline_initialize (on_or_off) int on_or_off; { if (on_or_off) rl_variable_bind ("comment-begin", "#"); #if defined (VI_MODE) rl_bind_key_in_map (CTRL('I'), on_or_off ? rl_insert : rl_complete, vi_insertion_keymap); #endif } int enable_hostname_completion (on_or_off) int on_or_off; { int old_value; old_value = perform_hostname_completion; if (on_or_off) { perform_hostname_completion = 1; rl_special_prefixes = "$@"; rl_completer_word_break_characters = bash_completer_word_break_characters; } else { perform_hostname_completion = 0; rl_special_prefixes = "$"; rl_completer_word_break_characters = bash_nohostname_word_break_characters; } return (old_value); } /* Called once from parse.y if we are going to use readline. */ void initialize_readline () { if (bash_readline_initialized) return; rl_terminal_name = get_string_value ("TERM"); rl_instream = stdin; rl_outstream = stderr; /* Allow conditional parsing of the ~/.inputrc file. */ rl_readline_name = "Bash"; /* Add bindable names before calling rl_initialize so they may be referenced in the various inputrc files. */ rl_add_defun ("shell-expand-line", shell_expand_line, -1); #ifdef BANG_HISTORY rl_add_defun ("history-expand-line", history_expand_line, -1); rl_add_defun ("magic-space", tcsh_magic_space, -1); #endif #ifdef ALIAS rl_add_defun ("alias-expand-line", alias_expand_line, -1); # ifdef BANG_HISTORY rl_add_defun ("history-and-alias-expand-line", history_and_alias_expand_line, -1); # endif #endif /* Backwards compatibility. */ rl_add_defun ("insert-last-argument", rl_yank_last_arg, -1); rl_add_defun ("operate-and-get-next", operate_and_get_next, -1); rl_add_defun ("display-shell-version", display_shell_version, -1); #if defined (BRACE_COMPLETION) rl_add_defun ("complete-into-braces", bash_brace_completion, -1); #endif #if defined (SPECIFIC_COMPLETION_FUNCTIONS) rl_add_defun ("complete-filename", bash_complete_filename, -1); rl_add_defun ("possible-filename-completions", bash_possible_filename_completions, -1); rl_add_defun ("complete-username", bash_complete_username, -1); rl_add_defun ("possible-username-completions", bash_possible_username_completions, -1); rl_add_defun ("complete-hostname", bash_complete_hostname, -1); rl_add_defun ("possible-hostname-completions", bash_possible_hostname_completions, -1); rl_add_defun ("complete-variable", bash_complete_variable, -1); rl_add_defun ("possible-variable-completions", bash_possible_variable_completions, -1); rl_add_defun ("complete-command", bash_complete_command, -1); rl_add_defun ("possible-command-completions", bash_possible_command_completions, -1); rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1); rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1); #endif rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1); /* Bind defaults before binding our custom shell keybindings. */ if (RL_ISSTATE(RL_STATE_INITIALIZED) == 0) rl_initialize (); /* Bind up our special shell functions. */ rl_bind_key_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap); /* Bind up our special shell functions. */ #ifdef BANG_HISTORY rl_bind_key_in_map ('^', history_expand_line, emacs_meta_keymap); #endif rl_bind_key_in_map (CTRL ('O'), operate_and_get_next, emacs_standard_keymap); rl_bind_key_in_map (CTRL ('V'), display_shell_version, emacs_ctlx_keymap); /* In Bash, the user can switch editing modes with "set -o [vi emacs]", so it is not necessary to allow C-M-j for context switching. Turn off this occasionally confusing behaviour. */ rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap); rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap); #if defined (VI_MODE) rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap); #endif #if defined (BRACE_COMPLETION) rl_bind_key_in_map ('{', bash_brace_completion, emacs_meta_keymap); #endif /* BRACE_COMPLETION */ #if defined (SPECIFIC_COMPLETION_FUNCTIONS) rl_bind_key_in_map ('/', bash_complete_filename, emacs_meta_keymap); rl_bind_key_in_map ('/', bash_possible_filename_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('~', bash_complete_username, emacs_meta_keymap); rl_bind_key_in_map ('~', bash_possible_username_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('@', bash_complete_hostname, emacs_meta_keymap); rl_bind_key_in_map ('@', bash_possible_hostname_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('$', bash_complete_variable, emacs_meta_keymap); rl_bind_key_in_map ('$', bash_possible_variable_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('!', bash_complete_command, emacs_meta_keymap); rl_bind_key_in_map ('!', bash_possible_command_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap); rl_bind_key_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap); #endif /* SPECIFIC_COMPLETION_FUNCTIONS */ rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap); /* Tell the completer that we want a crack first. */ rl_attempted_completion_function = attempt_shell_completion; /* Tell the completer that we might want to follow symbolic links or do other expansion on directory names. */ rl_directory_completion_hook = bash_directory_completion_hook; /* Tell the filename completer we want a chance to ignore some names. */ rl_ignore_some_completions_function = filename_completion_ignore; #if defined (VI_MODE) rl_bind_key_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap); # if defined (ALIAS) rl_bind_key_in_map ('@', posix_edit_macros, vi_movement_keymap); # endif #endif rl_completer_quote_characters = "'\""; /* This sets rl_completer_word_break_characters and rl_special_prefixes to the appropriate values, depending on whether or not hostname completion is enabled. */ enable_hostname_completion (perform_hostname_completion); /* characters that need to be quoted when appearing in filenames. */ rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{"; /*}*/ rl_filename_quoting_function = bash_quote_filename; rl_filename_dequoting_function = bash_dequote_filename; rl_char_is_quoted_p = char_is_quoted; if (posixly_correct) posix_readline_initialize (1); bash_readline_initialized = 1; } /* On Sun systems at least, rl_attempted_completion_function can end up getting set to NULL, and rl_completion_entry_function set to do command word completion if Bash is interrupted while trying to complete a command word. This just resets all the completion functions to the right thing. It's called from throw_to_top_level(). */ void bashline_reinitialize () { tilde_initialize (); rl_attempted_completion_function = attempt_shell_completion; rl_completion_entry_function = NULL; rl_directory_completion_hook = bash_directory_completion_hook; rl_ignore_some_completions_function = filename_completion_ignore; } /* Contains the line to push into readline. */ static char *push_to_readline = (char *)NULL; /* Push the contents of push_to_readline into the readline buffer. */ static int bash_push_line () { if (push_to_readline) { rl_insert_text (push_to_readline); free (push_to_readline); push_to_readline = (char *)NULL; rl_startup_hook = old_rl_startup_hook; } return 0; } /* Call this to set the initial text for the next line to read from readline. */ int bash_re_edit (line) char *line; { FREE (push_to_readline); push_to_readline = savestring (line); old_rl_startup_hook = rl_startup_hook; rl_startup_hook = bash_push_line; return (0); } static int display_shell_version (count, c) int count, c; { rl_crlf (); show_shell_version (0); putc ('\r', rl_outstream); fflush (rl_outstream); rl_on_new_line (); rl_redisplay (); return 0; } /* **************************************************************** */ /* */ /* Readline Stuff */ /* */ /* **************************************************************** */ /* If the user requests hostname completion, then simply build a list of hosts, and complete from that forever more, or at least until HOSTFILE is unset. */ /* THIS SHOULD BE A STRINGLIST. */ /* The kept list of hostnames. */ static char **hostname_list = (char **)NULL; /* The physical size of the above list. */ static int hostname_list_size; /* The number of hostnames in the above list. */ static int hostname_list_length; /* Whether or not HOSTNAME_LIST has been initialized. */ int hostname_list_initialized = 0; /* Initialize the hostname completion table. */ static void initialize_hostname_list () { char *temp; temp = get_string_value ("HOSTFILE"); if (temp == 0) temp = get_string_value ("hostname_completion_file"); if (temp == 0) temp = DEFAULT_HOSTS_FILE; snarf_hosts_from_file (temp); if (hostname_list) hostname_list_initialized++; } /* Add NAME to the list of hosts. */ static void add_host_name (name) char *name; { size_t size; if (hostname_list_length + 2 > hostname_list_size) { hostname_list_size = (hostname_list_size + 32) - (hostname_list_size % 32); size = hostname_list_size * sizeof (char *); hostname_list = (char **)xrealloc (hostname_list, size); } hostname_list[hostname_list_length++] = savestring (name); hostname_list[hostname_list_length] = (char *)NULL; } #define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c)) static void snarf_hosts_from_file (filename) char *filename; { FILE *file; char *temp, buffer[256], name[256]; register int i, start; file = fopen (filename, "r"); if (file == 0) return; while (temp = fgets (buffer, 255, file)) { /* Skip to first character. */ for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++) ; /* If comment or blank line, ignore. */ if (buffer[i] == '\0' || buffer[i] == '#') continue; /* If `preprocessor' directive, do the include. */ if (strncmp (buffer + i, "$include ", 9) == 0) { char *incfile, *t; /* Find start of filename. */ for (incfile = buffer + i + 9; *incfile && whitespace (*incfile); incfile++) ; /* Find end of filename. */ for (t = incfile; *t && cr_whitespace (*t) == 0; t++) ; *t = '\0'; snarf_hosts_from_file (incfile); continue; } /* Skip internet address if present. */ if (DIGIT (buffer[i])) for (; buffer[i] && cr_whitespace (buffer[i]) == 0; i++); /* Gobble up names. Each name is separated with whitespace. */ while (buffer[i]) { for (; cr_whitespace (buffer[i]); i++) ; if (buffer[i] == '\0' || buffer[i] == '#') break; /* Isolate the current word. */ for (start = i; buffer[i] && cr_whitespace (buffer[i]) == 0; i++) ; if (i == start) continue; strncpy (name, buffer + start, i - start); name[i - start] = '\0'; add_host_name (name); } } fclose (file); } /* Return the hostname list. */ char ** get_hostname_list () { if (hostname_list_initialized == 0) initialize_hostname_list (); return (hostname_list); } void clear_hostname_list () { register int i; if (hostname_list_initialized == 0) return; for (i = 0; i < hostname_list_length; i++) free (hostname_list[i]); hostname_list_length = 0; } /* Return a NULL terminated list of hostnames which begin with TEXT. Initialize the hostname list the first time if neccessary. The array is malloc ()'ed, but not the individual strings. */ static char ** hostnames_matching (text) char *text; { register int i, len, nmatch, rsize; char **result; if (hostname_list_initialized == 0) initialize_hostname_list (); if (hostname_list_initialized == 0) return ((char **)NULL); /* Special case. If TEXT consists of nothing, then the whole list is what is desired. */ if (*text == '\0') { result = alloc_array (1 + hostname_list_length); for (i = 0; i < hostname_list_length; i++) result[i] = hostname_list[i]; result[i] = (char *)NULL; return (result); } /* Scan until found, or failure. */ len = strlen (text); result = (char **)NULL; for (i = nmatch = rsize = 0; i < hostname_list_length; i++) { if (STREQN (text, hostname_list[i], len) == 0) continue; /* OK, it matches. Add it to the list. */ if (nmatch >= (rsize - 1)) { rsize = (rsize + 16) - (rsize % 16); result = (char **)xrealloc (result, rsize * sizeof (char *)); } result[nmatch++] = hostname_list[i]; } if (nmatch) result[nmatch] = (char *)NULL; return (result); } /* The equivalent of the Korn shell C-o operate-and-get-next-history-line editing command. */ static int saved_history_line_to_use = -1; static int set_saved_history () { if (saved_history_line_to_use >= 0) rl_get_previous_history (history_length - saved_history_line_to_use, 0); saved_history_line_to_use = -1; rl_startup_hook = old_rl_startup_hook; return (0); } static int operate_and_get_next (count, c) int count, c; { int where; /* Accept the current line. */ rl_newline (1, c); /* Find the current line, and find the next line to use. */ where = where_history (); if ((history_is_stifled () && (history_length >= history_max_entries)) || (where >= history_length - 1)) saved_history_line_to_use = where; else saved_history_line_to_use = where + 1; old_rl_startup_hook = rl_startup_hook; rl_startup_hook = set_saved_history; return 0; } #if defined (VI_MODE) /* This vi mode command causes VI_EDIT_COMMAND to be run on the current command being entered (if no explicit argument is given), otherwise on a command from the history file. */ #define VI_EDIT_COMMAND "fc -e ${VISUAL:-${EDITOR:-vi}}" static int vi_edit_and_execute_command (count, c) int count, c; { char *command; int r, cclc, rrs; rrs = rl_readline_state; cclc = current_command_line_count; /* Accept the current line. */ rl_newline (1, c); if (rl_explicit_arg) { command = (char *)xmalloc (strlen (VI_EDIT_COMMAND) + 8); sprintf (command, "%s %d", VI_EDIT_COMMAND, count); } else { /* Take the command we were just editing, add it to the history file, then call fc to operate on it. We have to add a dummy command to the end of the history because fc ignores the last command (assumes it's supposed to deal with the command before the `fc'). */ using_history (); bash_add_history (rl_line_buffer); bash_add_history (""); history_lines_this_session++; using_history (); command = savestring (VI_EDIT_COMMAND); } r = parse_and_execute (command, "v", SEVAL_NOHIST); current_command_line_count = cclc; /* Now erase the contents of the current line and undo the effects of the rl_accept_line() above. We don't even want to make the text we just executed available for undoing. */ rl_line_buffer[0] = '\0'; /* XXX */ rl_point = rl_end = 0; rl_done = 0; rl_readline_state = rrs; rl_forced_update_display (); return r; } #endif /* VI_MODE */ #if defined (ALIAS) static int posix_edit_macros (count, key) int count, key; { int c; char alias_name[3], *alias_value, *macro; c = rl_read_key (); alias_name[0] = '_'; alias_name[1] = c; alias_name[2] = '\0'; alias_value = get_alias_value (alias_name); if (alias_value && *alias_value) { macro = savestring (alias_value); rl_push_macro_input (macro); } return 0; } #endif /* **************************************************************** */ /* */ /* How To Do Shell Completion */ /* */ /* **************************************************************** */ #define COMMAND_SEPARATORS ";|&{(`" static int check_redir (ti) int ti; { register int this_char, prev_char; /* Handle the two character tokens `>&', `<&', and `>|'. We are not in a command position after one of these. */ this_char = rl_line_buffer[ti]; prev_char = rl_line_buffer[ti - 1]; if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) || (this_char == '|' && prev_char == '>')) return (1); else if ((this_char == '{' && prev_char == '$') || /* } */ (char_is_quoted (rl_line_buffer, ti))) return (1); return (0); } #if defined (PROGRAMMABLE_COMPLETION) /* * XXX - because of the <= start test, and setting os = s+1, this can * potentially return os > start. This is probably not what we want to * happen, but fix later after 2.05a-release. */ static int find_cmd_start (start) int start; { register int s, os; os = 0; while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS)) <= start) && rl_line_buffer[s]) os = s+1; return os; } static int find_cmd_end (end) int end; { register int e; e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS); return e; } static char * find_cmd_name (start) int start; { char *name; register int s, e; for (s = start; whitespace (rl_line_buffer[s]); s++) ; /* skip until a shell break character */ e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n"); name = substring (rl_line_buffer, s, e); return (name); } static char * prog_complete_return (text, matchnum) const char *text; int matchnum; { static int ind; if (matchnum == 0) ind = 0; if (prog_complete_matches == 0 || prog_complete_matches[ind] == 0) return (char *)NULL; return (prog_complete_matches[ind++]); } #endif /* PROGRAMMABLE_COMPLETION */ /* Do some completion on TEXT. The indices of TEXT in RL_LINE_BUFFER are at START and END. Return an array of matches, or NULL if none. */ static char ** attempt_shell_completion (text, start, end) const char *text; int start, end; { int in_command_position, ti, saveti, qc; char **matches, *command_separator_chars; command_separator_chars = COMMAND_SEPARATORS; matches = (char **)NULL; rl_ignore_some_completions_function = filename_completion_ignore; /* Determine if this could be a command word. It is if it appears at the start of the line (ignoring preceding whitespace), or if it appears after a character that separates commands. It cannot be a command word if we aren't at the top-level prompt. */ ti = start - 1; saveti = qc = -1; while ((ti > -1) && (whitespace (rl_line_buffer[ti]))) ti--; #if 1 /* If this is an open quote, maybe we're trying to complete a quoted command name. */ if (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\'') { qc = rl_line_buffer[ti]; saveti = ti--; while (ti > -1 && (whitespace (rl_line_buffer[ti]))) ti--; } #endif in_command_position = 0; if (ti < 0) { /* Only do command completion at the start of a line when we are prompting at the top level. */ if (current_prompt_string == ps1_prompt) in_command_position++; } else if (member (rl_line_buffer[ti], command_separator_chars)) { in_command_position++; if (check_redir (ti) == 1) in_command_position = 0; } else { /* This still could be in command position. It is possible that all of the previous words on the line are variable assignments. */ } /* Check that we haven't incorrectly flagged a closed command substitution as indicating we're in a command position. */ if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' && *text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0) in_command_position = 0; /* Special handling for command substitution. If *TEXT is a backquote, it can be the start or end of an old-style command substitution, or unmatched. If it's unmatched, both calls to unclosed_pair will succeed. */ if (*text == '`' && (in_command_position || (unclosed_pair (rl_line_buffer, start, "`") && unclosed_pair (rl_line_buffer, end, "`")))) matches = rl_completion_matches (text, command_subst_completion_function); #if defined (PROGRAMMABLE_COMPLETION) /* Attempt programmable completion. */ if (!matches && in_command_position == 0 && prog_completion_enabled && (num_progcomps () > 0) && current_prompt_string == ps1_prompt) { int s, e, foundcs; char *n; /* XXX - don't free the members */ if (prog_complete_matches) free (prog_complete_matches); prog_complete_matches = (char **)NULL; s = find_cmd_start (start); e = find_cmd_end (end); n = find_cmd_name (s); if (e > s) prog_complete_matches = programmable_completions (n, text, s, e, &foundcs); else foundcs = 0; FREE (n); /* XXX - if we found a COMPSPEC for the command, just return whatever the programmable completion code returns, and disable the default filename completion that readline will do unless the COPT_DEFAULT option has been set with the `-o default' option to complete. */ if (foundcs) { /* If the user specified that the compspec returns filenames, make sure that readline knows it. */ if (foundcs & COPT_FILENAMES) rl_filename_completion_desired = 1; /* Turn what the programmable completion code returns into what readline wants. I should have made compute_lcd_of_matches external... */ matches = rl_completion_matches (text, prog_complete_return); if ((foundcs & COPT_DEFAULT) == 0) rl_attempted_completion_over = 1; /* no default */ return (matches); } } #endif /* New posix-style command substitution or variable name? */ if (!matches && *text == '$') { if (qc != '\'' && text[1] == '(') /* ) */ matches = rl_completion_matches (text, command_subst_completion_function); else matches = rl_completion_matches (text, variable_completion_function); } /* If the word starts in `~', and there is no slash in the word, then try completing this word as a username. */ if (!matches && *text == '~' && !strchr (text, '/')) matches = rl_completion_matches (text, rl_username_completion_function); /* Another one. Why not? If the word starts in '@', then look through the world of known hostnames for completion first. */ if (!matches && perform_hostname_completion && *text == '@') matches = rl_completion_matches (text, hostname_completion_function); /* And last, (but not least) if this word is in a command position, then complete over possible command names, including aliases, functions, and command names. */ if (!matches && in_command_position) { if (start == 0 && end == 0 && text[0] == '\0' && no_empty_command_completion) { matches = (char **)NULL; rl_ignore_some_completions_function = bash_ignore_everything; } else { matches = rl_completion_matches (text, command_word_completion_function); /* If we are attempting command completion and nothing matches, we do not want readline to perform filename completion for us. We still want to be able to complete partial pathnames, so set the completion ignore function to something which will remove filenames and leave directories in the match list. */ if (matches == (char **)NULL) rl_ignore_some_completions_function = bash_ignore_filenames; } } /* This could be a globbing pattern, so try to expand it using pathname expansion. */ if (!matches && glob_pattern_p (text)) { matches = rl_completion_matches (text, glob_complete_word); /* A glob expression that matches more than one filename is problematic. If we match more than one filename, punt. */ if (matches && matches[1]) { free_array (matches); matches = (char **)0; } } return (matches); } /* This is the function to call when the word to complete is in a position where a command word can be found. It grovels $PATH, looking for commands that match. It also scans aliases, function names, and the shell_builtin table. */ char * command_word_completion_function (hint_text, state) const char *hint_text; int state; { static char *hint = (char *)NULL; static char *path = (char *)NULL; static char *val = (char *)NULL; static char *filename_hint = (char *)NULL; static int path_index, hint_len, istate; static int mapping_over, local_index; static SHELL_VAR **varlist = (SHELL_VAR **)NULL; #if defined (ALIAS) static alias_t **alias_list = (alias_t **)NULL; #endif /* ALIAS */ /* We have to map over the possibilities for command words. If we have no state, then make one just for that purpose. */ if (!state) { if (hint) free (hint); mapping_over = 0; val = (char *)NULL; /* If this is an absolute program name, do not check it against aliases, reserved words, functions or builtins. We must check whether or not it is unique, and, if so, whether that filename is executable. */ if (absolute_program (hint_text)) { /* Perform tilde expansion on what's passed, so we don't end up passing filenames with tildes directly to stat(). */ if (*hint_text == '~') hint = bash_tilde_expand (hint_text); else hint = savestring (hint_text); hint_len = strlen (hint); if (filename_hint) free (filename_hint); filename_hint = savestring (hint); mapping_over = 4; istate = 0; goto inner; } hint = savestring (hint_text); hint_len = strlen (hint); path = get_string_value ("PATH"); path_index = 0; /* Initialize the variables for each type of command word. */ local_index = 0; if (varlist) free (varlist); varlist = all_visible_functions (); #if defined (ALIAS) if (alias_list) free (alias_list); alias_list = all_aliases (); #endif /* ALIAS */ } /* mapping_over says what we are currently hacking. Note that every case in this list must fall through when there are no more possibilities. */ switch (mapping_over) { case 0: /* Aliases come first. */ #if defined (ALIAS) while (alias_list && alias_list[local_index]) { register char *alias; alias = alias_list[local_index++]->name; if (STREQN (alias, hint, hint_len)) return (savestring (alias)); } #endif /* ALIAS */ local_index = 0; mapping_over++; case 1: /* Then shell reserved words. */ { while (word_token_alist[local_index].word) { register char *reserved_word; reserved_word = word_token_alist[local_index++].word; if (STREQN (reserved_word, hint, hint_len)) return (savestring (reserved_word)); } local_index = 0; mapping_over++; } case 2: /* Then function names. */ while (varlist && varlist[local_index]) { register char *varname; varname = varlist[local_index++]->name; if (STREQN (varname, hint, hint_len)) return (savestring (varname)); } local_index = 0; mapping_over++; case 3: /* Then shell builtins. */ for (; local_index < num_shell_builtins; local_index++) { /* Ignore it if it doesn't have a function pointer or if it is not currently enabled. */ if (!shell_builtins[local_index].function || (shell_builtins[local_index].flags & BUILTIN_ENABLED) == 0) continue; if (STREQN (shell_builtins[local_index].name, hint, hint_len)) { int i = local_index++; return (savestring (shell_builtins[i].name)); } } local_index = 0; mapping_over++; } /* Repeatedly call filename_completion_function while we have members of PATH left. Question: should we stat each file? Answer: we call executable_file () on each file. */ outer: istate = (val != (char *)NULL); if (!istate) { char *current_path; /* Get the next directory from the path. If there is none, then we are all done. */ if (!path || !path[path_index] || (current_path = extract_colon_unit (path, &path_index)) == 0) return ((char *)NULL); if (*current_path == 0) { free (current_path); current_path = savestring ("."); } if (*current_path == '~') { char *t; t = bash_tilde_expand (current_path); free (current_path); current_path = t; } if (filename_hint) free (filename_hint); filename_hint = (char *)xmalloc (2 + strlen (current_path) + hint_len); sprintf (filename_hint, "%s/%s", current_path, hint); free (current_path); } inner: val = rl_filename_completion_function (filename_hint, istate); istate = 1; if (val == 0) { /* If the hint text is an absolute program, then don't bother searching through PATH. */ if (absolute_program (hint)) return ((char *)NULL); goto outer; } else { int match, freetemp; char *temp; if (absolute_program (hint)) { match = strncmp (val, hint, hint_len) == 0; /* If we performed tilde expansion, restore the original filename. */ if (*hint_text == '~') { int l, tl, vl; vl = strlen (val); tl = strlen (hint_text); l = vl - hint_len; /* # of chars added */ temp = (char *)xmalloc (l + 2 + tl); strcpy (temp, hint_text); strcpy (temp + tl, val + vl - l); } else temp = savestring (val); freetemp = 1; } else { temp = strrchr (val, '/'); if (temp) { temp++; freetemp = match = strncmp (temp, hint, hint_len) == 0; if (match) temp = savestring (temp); } else freetemp = match = 0; } /* If we have found a match, and it is an executable file or a directory name, return it. */ if (match && executable_or_directory (val)) { free (val); val = ""; /* So it won't be NULL. */ return (temp); } else { if (freetemp) free (temp); free (val); goto inner; } } } /* Completion inside an unterminated command substitution. */ static char * command_subst_completion_function (text, state) const char *text; int state; { static char **matches = (char **)NULL; static const char *orig_start; static char *filename_text = (char *)NULL; static int cmd_index, start_len; char *value; if (state == 0) { if (filename_text) free (filename_text); orig_start = text; if (*text == '`') text++; else if (*text == '$' && text[1] == '(') /* ) */ text += 2; start_len = text - orig_start; filename_text = savestring (text); if (matches) free (matches); matches = rl_completion_matches (filename_text, command_word_completion_function); cmd_index = 0; } if (!matches || !matches[cmd_index]) { rl_filename_quoting_desired = 0; /* disable quoting */ return ((char *)NULL); } else { value = (char *)xmalloc (1 + start_len + strlen (matches[cmd_index])); if (start_len == 1) value[0] = *orig_start; else strncpy (value, orig_start, start_len); strcpy (value + start_len, matches[cmd_index]); cmd_index++; return (value); } } /* Okay, now we write the entry_function for variable completion. */ static char * variable_completion_function (text, state) const char *text; int state; { static char **varlist = (char **)NULL; static int varlist_index; static char *varname = (char *)NULL; static int namelen; static int first_char, first_char_loc; if (!state) { if (varname) free (varname); first_char_loc = 0; first_char = text[0]; if (first_char == '$') first_char_loc++; if (text[first_char_loc] == '{') first_char_loc++; varname = savestring (text + first_char_loc); namelen = strlen (varname); if (varlist) free_array (varlist); varlist = all_variables_matching_prefix (varname); varlist_index = 0; } if (!varlist || !varlist[varlist_index]) { return ((char *)NULL); } else { char *value; value = (char *)xmalloc (4 + strlen (varlist[varlist_index])); if (first_char_loc) { value[0] = first_char; if (first_char_loc == 2) value[1] = '{'; } strcpy (value + first_char_loc, varlist[varlist_index]); if (first_char_loc == 2) strcat (value, "}"); varlist_index++; return (value); } } /* How about a completion function for hostnames? */ static char * hostname_completion_function (text, state) const char *text; int state; { static char **list = (char **)NULL; static int list_index = 0; static int first_char, first_char_loc; /* If we don't have any state, make some. */ if (state == 0) { FREE (list); list = (char **)NULL; first_char_loc = 0; first_char = *text; if (first_char == '@') first_char_loc++; list = hostnames_matching ((char *)text+first_char_loc); list_index = 0; } if (list && list[list_index]) { char *t; t = (char *)xmalloc (2 + strlen (list[list_index])); *t = first_char; strcpy (t + first_char_loc, list[list_index]); list_index++; return (t); } return ((char *)NULL); } char * bash_groupname_completion_function (text, state) const char *text; int state; { #if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GRP_H) return ((char *)NULL); #else static char *gname = (char *)NULL; static struct group *grent; static int gnamelen; char *value; if (state == 0) { FREE (gname); gname = savestring (text); gnamelen = strlen (gname); setgrent (); } while (grent = getgrent ()) { if (gnamelen == 0 || (STREQN (gname, grent->gr_name, gnamelen))) break; } if (grent == 0) { endgrent (); return ((char *)NULL); } value = savestring (grent->gr_name); return (value); #endif } /* Functions to perform history and alias expansions on the current line. */ #if defined (BANG_HISTORY) /* Perform history expansion on the current line. If no history expansion is done, pre_process_line() returns what it was passed, so we need to allocate a new line here. */ static char * history_expand_line_internal (line) char *line; { char *new_line; new_line = pre_process_line (line, 0, 0); return (new_line == line) ? savestring (line) : new_line; } #endif /* There was an error in expansion. Let the preprocessor print the error here. */ static void cleanup_expansion_error () { char *to_free; fprintf (rl_outstream, "\r\n"); to_free = pre_process_line (rl_line_buffer, 1, 0); if (to_free != rl_line_buffer) free (to_free); putc ('\r', rl_outstream); rl_forced_update_display (); } /* If NEW_LINE differs from what is in the readline line buffer, add an undo record to get from the readline line buffer contents to the new line and make NEW_LINE the current readline line. */ static void maybe_make_readline_line (new_line) char *new_line; { if (strcmp (new_line, rl_line_buffer) != 0) { rl_point = rl_end; rl_add_undo (UNDO_BEGIN, 0, 0, 0); rl_delete_text (0, rl_point); rl_point = rl_end = 0; rl_insert_text (new_line); rl_add_undo (UNDO_END, 0, 0, 0); } } /* Make NEW_LINE be the current readline line. This frees NEW_LINE. */ static void set_up_new_line (new_line) char *new_line; { int old_point, at_end; old_point = rl_point; at_end = rl_point == rl_end; /* If the line was history and alias expanded, then make that be one thing to undo. */ maybe_make_readline_line (new_line); free (new_line); /* Place rl_point where we think it should go. */ if (at_end) rl_point = rl_end; else if (old_point < rl_end) { rl_point = old_point; if (!whitespace (rl_line_buffer[rl_point])) rl_forward_word (1, 0); } } #if defined (ALIAS) /* Expand aliases in the current readline line. */ static int alias_expand_line (count, ignore) int count, ignore; { char *new_line; new_line = alias_expand (rl_line_buffer); if (new_line) { set_up_new_line (new_line); return (0); } else { cleanup_expansion_error (); return (1); } } #endif #if defined (BANG_HISTORY) /* History expand the line. */ static int history_expand_line (count, ignore) int count, ignore; { char *new_line; new_line = history_expand_line_internal (rl_line_buffer); if (new_line) { set_up_new_line (new_line); return (0); } else { cleanup_expansion_error (); return (1); } } /* Expand history substitutions in the current line and then insert a space (hopefully close to where we were before). */ static int tcsh_magic_space (count, ignore) int count, ignore; { int dist_from_end, old_point; old_point = rl_point; dist_from_end = rl_end - rl_point; if (history_expand_line (count, ignore) == 0) { /* Try a simple heuristic from Stephen Gildea . This works if all expansions were before rl_point or if no expansions were performed. */ rl_point = (old_point == 0) ? old_point : rl_end - dist_from_end; rl_insert (1, ' '); return (0); } else return (1); } #endif /* History and alias expand the line. */ static int history_and_alias_expand_line (count, ignore) int count, ignore; { char *new_line; new_line = pre_process_line (rl_line_buffer, 0, 0); if (new_line == rl_line_buffer) new_line = savestring (new_line); #if defined (ALIAS) if (new_line) { char *alias_line; alias_line = alias_expand (new_line); free (new_line); new_line = alias_line; } #endif /* ALIAS */ if (new_line) { set_up_new_line (new_line); return (0); } else { cleanup_expansion_error (); return (1); } } /* History and alias expand the line, then perform the shell word expansions by calling expand_string. This can't use set_up_new_line() because we want the variable expansions as a separate undo'able set of operations. */ static int shell_expand_line (count, ignore) int count, ignore; { char *new_line; WORD_LIST *expanded_string; new_line = pre_process_line (rl_line_buffer, 0, 0); if (new_line == rl_line_buffer) new_line = savestring (new_line); #if defined (ALIAS) if (new_line) { char *alias_line; alias_line = alias_expand (new_line); free (new_line); new_line = alias_line; } #endif /* ALIAS */ if (new_line) { int old_point = rl_point; int at_end = rl_point == rl_end; /* If the line was history and alias expanded, then make that be one thing to undo. */ maybe_make_readline_line (new_line); free (new_line); /* If there is variable expansion to perform, do that as a separate operation to be undone. */ new_line = savestring (rl_line_buffer); expanded_string = expand_string (new_line, 0); FREE (new_line); if (expanded_string == 0) { new_line = (char *)xmalloc (1); new_line[0] = '\0'; } else { new_line = string_list (expanded_string); dispose_words (expanded_string); } maybe_make_readline_line (new_line); free (new_line); /* Place rl_point where we think it should go. */ if (at_end) rl_point = rl_end; else if (old_point < rl_end) { rl_point = old_point; if (!whitespace (rl_line_buffer[rl_point])) rl_forward_word (1, 0); } return 0; } else { cleanup_expansion_error (); return 1; } } /* Define NO_FORCE_FIGNORE if you want to match filenames that would otherwise be ignored if they are the only possible matches. */ /* #define NO_FORCE_FIGNORE */ /* If FIGNORE is set, then don't match files with the given suffixes when completing filenames. If only one of the possibilities has an acceptable suffix, delete the others, else just return and let the completer signal an error. It is called by the completer when real completions are done on filenames by the completer's internal function, not for completion lists (M-?) and not on "other" completion types, such as hostnames or commands. */ static struct ignorevar fignore = { "FIGNORE", (struct ign *)0, 0, (char *)0, (sh_iv_item_func_t *) 0, }; static void _ignore_completion_names (names, name_func) char **names; sh_ignore_func_t *name_func; { char **newnames; int idx, nidx; #ifdef NO_FORCE_FIGNORE char **oldnames; int oidx; #endif /* If there is only one completion, see if it is acceptable. If it is not, free it up. In any case, short-circuit and return. This is a special case because names[0] is not the prefix of the list of names if there is only one completion; it is the completion itself. */ if (names[1] == (char *)0) { #ifndef NO_FORCE_FIGNORE if ((*name_func) (names[0]) == 0) { free (names[0]); names[0] = (char *)NULL; } #endif return; } /* Allocate space for array to hold list of pointers to matching filenames. The pointers are copied back to NAMES when done. */ for (nidx = 1; names[nidx]; nidx++) ; newnames = alloc_array (nidx + 1); #ifdef NO_FORCE_FIGNORE oldnames = alloc_array (nidx - 1); oidx = 0; #endif newnames[0] = names[0]; for (idx = nidx = 1; names[idx]; idx++) { if ((*name_func) (names[idx])) newnames[nidx++] = names[idx]; else #ifndef NO_FORCE_FIGNORE free (names[idx]); #else oldnames[oidx++] = names[idx]; #endif } newnames[nidx] = (char *)NULL; /* If none are acceptable then let the completer handle it. */ if (nidx == 1) { #ifndef NO_FORCE_FIGNORE free (names[0]); names[0] = (char *)NULL; #else free (oldnames); #endif free (newnames); return; } #ifdef NO_FORCE_FIGNORE while (oidx) free (oldnames[--oidx]); free (oldnames); #endif /* If only one is acceptable, copy it to names[0] and return. */ if (nidx == 2) { free (names[0]); names[0] = newnames[1]; names[1] = (char *)NULL; free (newnames); return; } /* Copy the acceptable names back to NAMES, set the new array end, and return. */ for (nidx = 1; newnames[nidx]; nidx++) names[nidx] = newnames[nidx]; names[nidx] = (char *)NULL; free (newnames); } static int name_is_acceptable (name) const char *name; { struct ign *p; int nlen; for (nlen = strlen (name), p = fignore.ignores; p->val; p++) { if (nlen > p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len])) return (0); } return (1); } #if 0 static int ignore_dot_names (name) char *name; { return (name[0] != '.'); } #endif static int filename_completion_ignore (names) char **names; { #if 0 if (glob_dot_filenames == 0) _ignore_completion_names (names, ignore_dot_names); #endif setup_ignore_patterns (&fignore); if (fignore.num_ignores == 0) return 0; _ignore_completion_names (names, name_is_acceptable); return 0; } /* Return 1 if NAME is a directory. */ static int test_for_directory (name) const char *name; { struct stat finfo; char *fn; fn = bash_tilde_expand (name); if (stat (fn, &finfo) != 0) { free (fn); return 0; } free (fn); return (S_ISDIR (finfo.st_mode)); } /* Remove files from NAMES, leaving directories. */ static int bash_ignore_filenames (names) char **names; { _ignore_completion_names (names, test_for_directory); return 0; } static int return_zero (name) const char *name; { return 0; } static int bash_ignore_everything (names) char **names; { _ignore_completion_names (names, return_zero); return 0; } /* Handle symbolic link references and other directory name expansions while hacking completion. */ static int bash_directory_completion_hook (dirname) char **dirname; { char *local_dirname, *new_dirname, *t; int return_value, should_expand_dirname; WORD_LIST *wl; return_value = should_expand_dirname = 0; local_dirname = *dirname; #if 0 should_expand_dirname = strchr (local_dirname, '$') || strchr (local_dirname, '`'); #else if (strchr (local_dirname, '$')) should_expand_dirname = 1; else { t = strchr (local_dirname, '`'); if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0) should_expand_dirname = 1; } #endif if (should_expand_dirname) { new_dirname = savestring (local_dirname); wl = expand_string (new_dirname, 0); if (wl) { *dirname = string_list (wl); /* Tell the completer to replace the directory name only if we actually expanded something. */ return_value = STREQ (local_dirname, *dirname) == 0; free (local_dirname); free (new_dirname); dispose_words (wl); local_dirname = *dirname; } else { free (new_dirname); free (local_dirname); *dirname = (char *)xmalloc (1); **dirname = '\0'; return 1; } } if (!no_symbolic_links && (local_dirname[0] != '.' || local_dirname[1])) { char *temp1, *temp2; int len1, len2; t = get_working_directory ("symlink-hook"); temp1 = make_absolute (local_dirname, t); free (t); temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); /* If we can't canonicalize, bail. */ if (temp2 == 0) { free (temp1); return 1; } len1 = strlen (temp1); if (temp1[len1 - 1] == '/') { len2 = strlen (temp2); temp2 = (char *)xrealloc (temp2, len2 + 2); temp2[len2] = '/'; temp2[len2 + 1] = '\0'; } free (local_dirname); *dirname = temp2; free (temp1); } return (return_value); } static char **history_completion_array = (char **)NULL; static int harry_size; static int harry_len; static void build_history_completion_array () { register int i, j; HIST_ENTRY **hlist; char **tokens; /* First, clear out the current dynamic history completion list. */ if (harry_size) { for (i = 0; history_completion_array[i]; i++) free (history_completion_array[i]); free (history_completion_array); history_completion_array = (char **)NULL; harry_size = 0; harry_len = 0; } /* Next, grovel each line of history, making each shell-sized token a separate entry in the history_completion_array. */ hlist = history_list (); if (hlist) { for (i = 0; hlist[i]; i++) { /* Separate each token, and place into an array. */ tokens = history_tokenize (hlist[i]->line); for (j = 0; tokens && tokens[j]; j++) { if (harry_len + 2 > harry_size) { harry_size += 10; history_completion_array = (char **)xrealloc (history_completion_array, harry_size * sizeof (char *)); } history_completion_array[harry_len++] = tokens[j]; history_completion_array[harry_len] = (char *)NULL; } free (tokens); } /* Sort the complete list of tokens. */ qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)qsort_string_compare); } } static char * history_completion_generator (hint_text, state) const char *hint_text; int state; { static int local_index, len; static const char *text; /* If this is the first call to the generator, then initialize the list of strings to complete over. */ if (state == 0) { local_index = 0; build_history_completion_array (); text = hint_text; len = strlen (text); } while (history_completion_array && history_completion_array[local_index]) { if (strncmp (text, history_completion_array[local_index++], len) == 0) return (savestring (history_completion_array[local_index - 1])); } return ((char *)NULL); } static int dynamic_complete_history (count, key) int count, key; { int r; rl_compentry_func_t *orig_func; rl_completion_func_t *orig_attempt_func; orig_func = rl_completion_entry_function; orig_attempt_func = rl_attempted_completion_function; rl_completion_entry_function = history_completion_generator; rl_attempted_completion_function = (rl_completion_func_t *)NULL; if (rl_last_func == dynamic_complete_history) r = rl_complete_internal ('?'); else r = rl_complete_internal (TAB); rl_completion_entry_function = orig_func; rl_attempted_completion_function = orig_attempt_func; return r; } #if defined (SPECIFIC_COMPLETION_FUNCTIONS) static int bash_complete_username (ignore, ignore2) int ignore, ignore2; { return bash_complete_username_internal (TAB); } static int bash_possible_username_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_username_internal ('?'); } static int bash_complete_username_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, rl_username_completion_function); } static int bash_complete_filename (ignore, ignore2) int ignore, ignore2; { return bash_complete_filename_internal (TAB); } static int bash_possible_filename_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_filename_internal ('?'); } static int bash_complete_filename_internal (what_to_do) int what_to_do; { rl_compentry_func_t *orig_func; rl_completion_func_t *orig_attempt_func; rl_icppfunc_t *orig_dir_func; const char *orig_rl_completer_word_break_characters; int r; orig_func = rl_completion_entry_function; orig_attempt_func = rl_attempted_completion_function; orig_dir_func = rl_directory_completion_hook; orig_rl_completer_word_break_characters = rl_completer_word_break_characters; rl_completion_entry_function = rl_filename_completion_function; rl_attempted_completion_function = (rl_completion_func_t *)NULL; rl_directory_completion_hook = (rl_icppfunc_t *)NULL; rl_completer_word_break_characters = " \t\n\"\'"; r = rl_complete_internal (what_to_do); rl_completion_entry_function = orig_func; rl_attempted_completion_function = orig_attempt_func; rl_directory_completion_hook = orig_dir_func; rl_completer_word_break_characters = orig_rl_completer_word_break_characters; return r; } static int bash_complete_hostname (ignore, ignore2) int ignore, ignore2; { return bash_complete_hostname_internal (TAB); } static int bash_possible_hostname_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_hostname_internal ('?'); } static int bash_complete_variable (ignore, ignore2) int ignore, ignore2; { return bash_complete_variable_internal (TAB); } static int bash_possible_variable_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_variable_internal ('?'); } static int bash_complete_command (ignore, ignore2) int ignore, ignore2; { return bash_complete_command_internal (TAB); } static int bash_possible_command_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_command_internal ('?'); } static int bash_complete_hostname_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, hostname_completion_function); } static int bash_complete_variable_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, variable_completion_function); } static int bash_complete_command_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, command_word_completion_function); } static char * glob_complete_word (text, state) const char *text; int state; { static char **matches = (char **)NULL; static int ind; char *ret; if (state == 0) { rl_filename_completion_desired = 1; if (matches) free (matches); matches = shell_glob_filename (text); if (GLOB_FAILED (matches)) matches = (char **)NULL; ind = 0; } ret = matches ? matches[ind] : (char *)NULL; ind++; return ret; } static int bash_glob_completion_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, glob_complete_word); } static int bash_glob_expand_word (count, key) int count, key; { return bash_glob_completion_internal ('*'); } static int bash_glob_list_expansions (count, key) int count, key; { return bash_glob_completion_internal ('?'); } static int bash_specific_completion (what_to_do, generator) int what_to_do; rl_compentry_func_t *generator; { rl_compentry_func_t *orig_func; rl_completion_func_t *orig_attempt_func; int r; orig_func = rl_completion_entry_function; orig_attempt_func = rl_attempted_completion_function; rl_completion_entry_function = generator; rl_attempted_completion_function = NULL; r = rl_complete_internal (what_to_do); rl_completion_entry_function = orig_func; rl_attempted_completion_function = orig_attempt_func; return r; } #endif /* SPECIFIC_COMPLETION_FUNCTIONS */ /* Filename quoting for completion. */ /* A function to strip unquoted quote characters (single quotes, double quotes, and backslashes). It allows single quotes to appear within double quotes, and vice versa. It should be smarter. */ static char * bash_dequote_filename (text, quote_char) char *text; int quote_char; { char *ret, *p, *r; int l, quoted; l = strlen (text); ret = (char *)xmalloc (l + 1); for (quoted = quote_char, p = text, r = ret; p && *p; p++) { /* Allow backslash-quoted characters to pass through unscathed. */ if (*p == '\\') { *r++ = *++p; if (*p == '\0') break; continue; } /* Close quote. */ if (quoted && *p == quoted) { quoted = 0; continue; } /* Open quote. */ if (quoted == 0 && (*p == '\'' || *p == '"')) { quoted = *p; continue; } *r++ = *p; } *r = '\0'; return ret; } /* Quote characters that the readline completion code would treat as word break characters with backslashes. Pass backslash-quoted characters through without examination. */ static char * quote_word_break_chars (text) char *text; { char *ret, *r, *s; int l; l = strlen (text); ret = (char *)xmalloc ((2 * l) + 1); for (s = text, r = ret; *s; s++) { /* Pass backslash-quoted characters through, including the backslash. */ if (*s == '\\') { *r++ = '\\'; *r++ = *++s; if (*s == '\0') break; continue; } /* OK, we have an unquoted character. Check its presence in rl_completer_word_break_characters. */ if (strchr (rl_completer_word_break_characters, *s)) *r++ = '\\'; *r++ = *s; } *r = '\0'; return ret; } /* Quote a filename using double quotes, single quotes, or backslashes depending on the value of completion_quoting_style. If we're completing using backslashes, we need to quote some additional characters (those that readline treats as word breaks), so we call quote_word_break_chars on the result. */ static char * bash_quote_filename (s, rtype, qcp) char *s; int rtype; char *qcp; { char *rtext, *mtext, *ret; int rlen, cs; rtext = (char *)NULL; /* If RTYPE == MULT_MATCH, it means that there is more than one match. In this case, we do not add the closing quote or attempt to perform tilde expansion. If RTYPE == SINGLE_MATCH, we try to perform tilde expansion, because single and double quotes inhibit tilde expansion by the shell. */ mtext = s; if (mtext[0] == '~' && rtype == SINGLE_MATCH) mtext = bash_tilde_expand (s); cs = completion_quoting_style; /* Might need to modify the default completion style based on *qcp, since it's set to any user-provided opening quote. We also change to single-quoting if there is no user-provided opening quote and the word being completed contains newlines, since those are not quoted correctly using backslashes (a backslash-newline pair is special to the shell parser). */ if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && strchr (mtext, '\n')) cs = COMPLETE_SQUOTE; else if (*qcp == '"') cs = COMPLETE_DQUOTE; else if (*qcp == '\'') cs = COMPLETE_SQUOTE; #if defined (BANG_HISTORY) else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE && history_expansion_inhibited == 0 && strchr (mtext, '!')) cs = COMPLETE_BSQUOTE; if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE && history_expansion_inhibited == 0 && strchr (mtext, '!')) { cs = COMPLETE_BSQUOTE; *qcp = '\0'; } #endif switch (cs) { case COMPLETE_DQUOTE: rtext = sh_double_quote (mtext); break; case COMPLETE_SQUOTE: rtext = sh_single_quote (mtext); break; case COMPLETE_BSQUOTE: rtext = sh_backslash_quote (mtext); break; } if (mtext != s) free (mtext); /* We may need to quote additional characters: those that readline treats as word breaks that are not quoted by backslash_quote. */ if (rtext && cs == COMPLETE_BSQUOTE) { mtext = quote_word_break_chars (rtext); free (rtext); rtext = mtext; } /* Leave the opening quote intact. The readline completion code takes care of avoiding doubled opening quotes. */ rlen = strlen (rtext); ret = (char *)xmalloc (rlen + 1); strcpy (ret, rtext); /* If there are multiple matches, cut off the closing quote. */ if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE) ret[rlen - 1] = '\0'; free (rtext); return ret; } /* Support for binding readline key sequences to Unix commands. */ static Keymap cmd_xmap; static int bash_execute_unix_command (count, key) int count; /* ignored */ int key; { Keymap ckmap; /* current keymap */ Keymap xkmap; /* unix command executing keymap */ register int i; char *cmd; /* First, we need to find the right command to execute. This is tricky, because we might have already indirected into another keymap. */ ckmap = rl_get_keymap (); if (ckmap != rl_executing_keymap) { /* bogus. we have to search. only handle one level of indirection. */ for (i = 0; i < KEYMAP_SIZE; i++) { if (ckmap[i].type == ISKMAP && (Keymap)ckmap[i].function == rl_executing_keymap) break; } if (i < KEYMAP_SIZE) xkmap = (Keymap)cmd_xmap[i].function; else { rl_crlf (); internal_error ("bash_execute_unix_command: cannot find keymap for command"); rl_forced_update_display (); return 1; } } else xkmap = cmd_xmap; cmd = (char *)xkmap[key].function; if (cmd == 0) { rl_ding (); return 1; } rl_crlf (); /* move to a new line */ cmd = savestring (cmd); parse_and_execute (cmd, "bash_execute_unix_command", 0); /* and restore the readline buffer and display after command execution. */ rl_forced_update_display (); return 0; } static void init_unix_command_map () { cmd_xmap = rl_make_bare_keymap (); } static int isolate_sequence (string, ind, need_dquote, startp) char *string; int ind, need_dquote, *startp; { register int i; int c, passc, delim; for (i = ind; string[i] && whitespace (string[i]); i++) ; /* NEED_DQUOTE means that the first non-white character *must* be `"'. */ if (need_dquote && string[i] != '"') { builtin_error ("%s: first non-whitespace character is not `\"'", string); return -1; } /* We can have delimited strings even if NEED_DQUOTE == 0, like the command string to bind the key sequence to. */ delim = (string[i] == '"' || string[i] == '\'') ? string[i] : 0; if (startp) *startp = delim ? ++i : i; for (passc = 0; c = string[i]; i++) { if (passc) { passc = 0; continue; } if (c == '\\') { passc++; continue; } if (c == delim) break; } if (delim && string[i] != delim) { builtin_error ("%s: no closing `%c'", string, delim); return -1; } return i; } int bind_keyseq_to_unix_command (line) char *line; { Keymap kmap; char *kseq, *value; int i, kstart; if (cmd_xmap == 0) init_unix_command_map (); kmap = rl_get_keymap (); /* We duplicate some of the work done by rl_parse_and_bind here, but this code only has to handle `"keyseq": ["]command["]' and can generate an error for anything else. */ i = isolate_sequence (line, 0, 1, &kstart); if (i < 0) return -1; /* Create the key sequence string to pass to rl_generic_bind */ kseq = substring (line, kstart, i); for ( ; line[i] && line[i] != ':'; i++) ; if (line[i] != ':') { builtin_error ("%s: missing colon separator", line); return -1; } i = isolate_sequence (line, i + 1, 0, &kstart); if (i < 0) return -1; /* Create the value string containing the command to execute. */ value = substring (line, kstart, i); /* Save the command to execute and the key sequence in the CMD_XMAP */ rl_generic_bind (ISMACR, kseq, value, cmd_xmap); /* and bind the key sequence in the current keymap to a function that understands how to execute from CMD_XMAP */ rl_set_key (kseq, bash_execute_unix_command, kmap); return 0; } /* Used by the programmable completion code. Complete TEXT as a filename, but return only directories as matches. Dequotes the filename before attempting to find matches. */ char ** bash_directory_completion_matches (text) const char *text; { char **m1; char *dfn; int qc; qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0; dfn = bash_dequote_filename ((char *)text, qc); m1 = rl_completion_matches (dfn, rl_filename_completion_function); free (dfn); if (m1 == 0 || m1[0] == 0) return m1; /* We don't bother recomputing the lcd of the matches, because it will just get thrown away by the programmable completion code and recomputed later. */ (void)bash_ignore_filenames (m1); return m1; } #endif /* READLINE */ /* bracecomp.c -- Complete a filename with the possible completions enclosed in csh-style braces such that the list of completions is available to the shell. */ /* Original version by tromey@cns.caltech.edu, Fri Feb 7 1992. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 (BRACE_EXPANSION) && defined (READLINE) #include #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "bashansi.h" #include "shell.h" #include /* Find greatest common prefix of two strings. */ static int string_gcd (s1, s2) char *s1, *s2; { register int i; if (s1 == NULL || s2 == NULL) return (0); for (i = 0; *s1 && *s2; ++s1, ++s2, ++i) { if (*s1 != *s2) break; } return (i); } static char * really_munge_braces (array, real_start, real_end, gcd_zero) char **array; int real_start, real_end, gcd_zero; { int start, end, gcd; char *result, *subterm, *x; int result_size, flag, tlen; flag = 0; if (real_start == real_end) { x = array[real_start] ? sh_backslash_quote (array[real_start] + gcd_zero) : sh_backslash_quote (array[0]); return x; } result = (char *)xmalloc (result_size = 16); *result = '\0'; for (start = real_start; start < real_end; start = end + 1) { gcd = strlen (array[start]); for (end = start + 1; end < real_end; end++) { int temp; temp = string_gcd (array[start], array[end]); if (temp <= gcd_zero) break; gcd = temp; } end--; if (gcd_zero == 0 && start == real_start && end != (real_end - 1)) { /* In this case, add in a leading '{', because we are at top level, and there isn't a consistent prefix. */ result_size += 1; result = (char *)xrealloc (result, result_size); result[0] = '{'; result[1] = '\0'; flag++; } /* Make sure we backslash quote every substring we insert into the resultant brace expression. This is so the default filename quoting function won't inappropriately quote the braces. */ if (start == end) { x = savestring (array[start] + gcd_zero); subterm = sh_backslash_quote (x); free (x); } else { /* If there is more than one element in the subarray, insert the (quoted) prefix and an opening brace. */ tlen = gcd - gcd_zero; x = (char *)xmalloc (tlen + 1); strncpy (x, array[start] + gcd_zero, tlen); x[tlen] = '\0'; subterm = sh_backslash_quote (x); free (x); result_size += strlen (subterm) + 1; result = (char *)xrealloc (result, result_size); strcat (result, subterm); free (subterm); strcat (result, "{"); subterm = really_munge_braces (array, start, end + 1, gcd); subterm[strlen (subterm) - 1] = '}'; } result_size += strlen (subterm) + 1; result = (char *)xrealloc (result, result_size); strcat (result, subterm); strcat (result, ","); free (subterm); } if (gcd_zero == 0) result[strlen (result) - 1] = flag ? '}' : '\0'; return (result); } static int hack_braces_completion (names) char **names; { register int i; char *temp; temp = really_munge_braces (names, 1, array_len (names), 0); for (i = 0; names[i]; ++i) { free (names[i]); names[i] = NULL; } names[0] = temp; return 0; } /* We handle quoting ourselves within hack_braces_completion, so we turn off rl_filename_quoting_desired and rl_filename_quoting_function. */ int bash_brace_completion (count, ignore) int count, ignore; { rl_compignore_func_t *orig_ignore_func; rl_compentry_func_t *orig_entry_func; rl_quote_func_t *orig_quoting_func; rl_completion_func_t *orig_attempt_func; int orig_quoting_desired, r; orig_ignore_func = rl_ignore_some_completions_function; orig_attempt_func = rl_attempted_completion_function; orig_entry_func = rl_completion_entry_function; orig_quoting_func = rl_filename_quoting_function; orig_quoting_desired = rl_filename_quoting_desired; rl_completion_entry_function = rl_filename_completion_function; rl_attempted_completion_function = (rl_completion_func_t *)NULL; rl_ignore_some_completions_function = hack_braces_completion; rl_filename_quoting_function = (rl_quote_func_t *)NULL; rl_filename_quoting_desired = 0; r = rl_complete_internal (TAB); rl_ignore_some_completions_function = orig_ignore_func; rl_attempted_completion_function = orig_attempt_func; rl_completion_entry_function = orig_entry_func; rl_filename_quoting_function = orig_quoting_func; rl_filename_quoting_desired = orig_quoting_desired; return r; } #endif /* BRACE_EXPANSION && READLINE */ /* braces.c -- code for doing word expansion in curly braces. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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. */ /* Stuff in curly braces gets expanded before all other shell expansions. */ #include "config.h" #if defined (BRACE_EXPANSION) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "bashansi.h" #if defined (SHELL) # include "shell.h" #endif /* SHELL */ #include "general.h" #define brace_whitespace(c) (!(c) || (c) == ' ' || (c) == '\t' || (c) == '\n') /* Basic idea: Segregate the text into 3 sections: preamble (stuff before an open brace), postamble (stuff after the matching close brace) and amble (stuff after preamble, and before postamble). Expand amble, and then tack on the expansions to preamble. Expand postamble, and tack on the expansions to the result so far. */ /* The character which is used to separate arguments. */ int brace_arg_separator = ','; #if defined (__P) static int brace_gobbler __P((char *, int *, int)); static char **expand_amble __P((char *)); static char **array_concat __P((char **, char **)); #else static int brace_gobbler (); static char **expand_amble (); static char **array_concat (); #endif /* Return an array of strings; the brace expansion of TEXT. */ char ** brace_expand (text) char *text; { register int start; char *preamble, *postamble, *amble; char **tack, **result; int i, j, c; /* Find the text of the preamble. */ i = 0; c = brace_gobbler (text, &i, '{'); preamble = (char *)xmalloc (i + 1); strncpy (preamble, text, i); preamble[i] = '\0'; result = (char **)xmalloc (2 * sizeof (char *)); result[0] = preamble; result[1] = (char *)NULL; /* Special case. If we never found an exciting character, then the preamble is all of the text, so just return that. */ if (c != '{') return (result); /* Find the amble. This is the stuff inside this set of braces. */ start = ++i; c = brace_gobbler (text, &i, '}'); /* What if there isn't a matching close brace? */ if (c == 0) { #if defined (NOTDEF) /* Well, if we found an unquoted BRACE_ARG_SEPARATOR between START and I, then this should be an error. Otherwise, it isn't. */ for (j = start; j < i; j++) { if (text[j] == '\\') { j++; continue; } if (text[j] == brace_arg_separator) { free_array (result); report_error ("missing `}'"); throw_to_top_level (); } } #endif free (preamble); /* Same as result[0]; see initialization. */ result[0] = savestring (text); return (result); } #if defined (SHELL) amble = substring (text, start, i); #else amble = (char *)xmalloc (1 + (i - start)); strncpy (amble, &text[start], (i - start)); amble[i - start] = '\0'; #endif #if defined (SHELL) /* If the amble does not contain an unquoted BRACE_ARG_SEPARATOR, then just return without doing any expansion. */ for (j = 0; amble[j]; j++) { if (amble[j] == '\\') { j++; continue; } if (amble[j] == brace_arg_separator) break; } if (!amble[j]) { free (amble); free (preamble); result[0] = savestring (text); return (result); } #endif /* SHELL */ postamble = &text[i + 1]; tack = expand_amble (amble); result = array_concat (result, tack); free (amble); free_array (tack); tack = brace_expand (postamble); result = array_concat (result, tack); free_array (tack); return (result); } /* Expand the text found inside of braces. We simply try to split the text at BRACE_ARG_SEPARATORs into separate strings. We then brace expand each slot which needs it, until there are no more slots which need it. */ static char ** expand_amble (text) char *text; { char **result, **partial; char *tem; int start, i, c; result = (char **)NULL; for (start = 0, i = 0, c = 1; c; start = ++i) { c = brace_gobbler (text, &i, brace_arg_separator); #if defined (SHELL) tem = substring (text, start, i); #else tem = (char *)xmalloc (1 + (i - start)); strncpy (tem, &text[start], (i - start)); tem[i- start] = '\0'; #endif partial = brace_expand (tem); if (!result) result = partial; else { register int lr = array_len (result); register int lp = array_len (partial); register int j; result = (char **)xrealloc (result, (1 + lp + lr) * sizeof (char *)); for (j = 0; j < lp; j++) result[lr + j] = partial[j]; result[lr + j] = (char *)NULL; free (partial); } free (tem); } return (result); } /* Start at INDEX, and skip characters in TEXT. Set INDEX to the index of the character matching SATISFY. This understands about quoting. Return the character that caused us to stop searching; this is either the same as SATISFY, or 0. */ static int brace_gobbler (text, indx, satisfy) char *text; int *indx; int satisfy; { register int i, c, quoted, level, pass_next; #if defined (SHELL) int si; char *t; #endif level = quoted = pass_next = 0; for (i = *indx; c = text[i]; i++) { if (pass_next) { pass_next = 0; continue; } /* A backslash escapes the next character. This allows backslash to escape the quote character in a double-quoted string. */ if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`')) { pass_next = 1; continue; } if (quoted) { if (c == quoted) quoted = 0; continue; } if (c == '"' || c == '\'' || c == '`') { quoted = c; continue; } #if defined (SHELL) /* Pass new-style command substitutions through unchanged. */ if (c == '$' && text[i+1] == '(') /* ) */ { si = i + 2; t = extract_command_subst (text, &si); i = si; free (t); continue; } #endif if (c == satisfy && level == 0 && quoted == 0) { /* We ignore an open brace surrounded by whitespace, and also an open brace followed immediately by a close brace preceded by whitespace. */ if (c == '{' && ((!i || brace_whitespace (text[i - 1])) && (brace_whitespace (text[i + 1]) || text[i + 1] == '}'))) continue; #if defined (SHELL) /* If this is being compiled as part of bash, ignore the `{' in a `${}' construct */ if ((c != '{') || i == 0 || (text[i - 1] != '$')) #endif /* SHELL */ break; } if (c == '{') level++; else if (c == '}' && level) level--; } *indx = i; return (c); } /* Return a new array of strings which is the result of appending each string in ARR2 to each string in ARR1. The resultant array is len (arr1) * len (arr2) long. For convenience, ARR1 (and its contents) are free ()'ed. ARR1 can be NULL, in that case, a new version of ARR2 is returned. */ static char ** array_concat (arr1, arr2) char **arr1, **arr2; { register int i, j, len, len1, len2; register char **result; if (arr1 == 0) return (copy_array (arr2)); if (arr2 == 0) return (copy_array (arr1)); len1 = array_len (arr1); len2 = array_len (arr2); result = (char **)xmalloc ((1 + (len1 * len2)) * sizeof (char *)); len = 0; for (i = 0; i < len1; i++) { int strlen_1 = strlen (arr1[i]); for (j = 0; j < len2; j++) { result[len] = (char *)xmalloc (1 + strlen_1 + strlen (arr2[j])); strcpy (result[len], arr1[i]); strcpy (result[len] + strlen_1, arr2[j]); len++; } free (arr1[i]); } free (arr1); result[len] = (char *)NULL; return (result); } #if defined (TEST) #include fatal_error (format, arg1, arg2) char *format, *arg1, *arg2; { report_error (format, arg1, arg2); exit (1); } report_error (format, arg1, arg2) char *format, *arg1, *arg2; { fprintf (stderr, format, arg1, arg2); fprintf (stderr, "\n"); } main () { char example[256]; for (;;) { char **result; int i; fprintf (stderr, "brace_expand> "); if ((!fgets (example, 256, stdin)) || (strncmp (example, "quit", 4) == 0)) break; if (strlen (example)) example[strlen (example) - 1] = '\0'; result = brace_expand (example); for (i = 0; result[i]; i++) printf ("%s\n", result[i]); free_array (result); } } /* * Local variables: * compile-command: "gcc -g -Bstatic -DTEST -o brace_expand braces.c general.o" * end: */ #endif /* TEST */ #endif /* BRACE_EXPANSION */ /* copy_command.c -- copy a COMMAND structure. This is needed primarily for making function definitions, but I'm not sure that anyone else will need it. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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" #if defined (HAVE_UNISTD_H) # include #endif #include #include "shell.h" static PATTERN_LIST *copy_case_clause __P((PATTERN_LIST *)); static PATTERN_LIST *copy_case_clauses __P((PATTERN_LIST *)); static FOR_COM *copy_for_command __P((FOR_COM *)); #if defined (ARITH_FOR_COMMAND) static ARITH_FOR_COM *copy_arith_for_command __P((ARITH_FOR_COM *)); #endif static GROUP_COM *copy_group_command __P((GROUP_COM *)); static SUBSHELL_COM *copy_subshell_command __P((SUBSHELL_COM *)); static CASE_COM *copy_case_command __P((CASE_COM *)); static WHILE_COM *copy_while_command __P((WHILE_COM *)); static IF_COM *copy_if_command __P((IF_COM *)); #if defined (DPAREN_ARITHMETIC) static ARITH_COM *copy_arith_command __P((ARITH_COM *)); #endif #if defined (COND_COMMAND) static COND_COM *copy_cond_command __P((COND_COM *)); #endif static SIMPLE_COM *copy_simple_command __P((SIMPLE_COM *)); static FUNCTION_DEF *copy_function_def __P((FUNCTION_DEF *)); WORD_DESC * copy_word (w) WORD_DESC *w; { WORD_DESC *new_word; new_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC)); #if 1 new_word->flags = w->flags; #else FASTCOPY ((char *)w, (char *)new_word, sizeof (WORD_DESC)); #endif new_word->word = savestring (w->word); return (new_word); } /* Copy the chain of words in LIST. Return a pointer to the new chain. */ WORD_LIST * copy_word_list (list) WORD_LIST *list; { WORD_LIST *new_list, *temp; for (new_list = (WORD_LIST *)NULL; list; list = list->next) { temp = (WORD_LIST *)xmalloc (sizeof (WORD_LIST)); temp->next = new_list; new_list = temp; new_list->word = copy_word (list->word); } return (REVERSE_LIST (new_list, WORD_LIST *)); } static PATTERN_LIST * copy_case_clause (clause) PATTERN_LIST *clause; { PATTERN_LIST *new_clause; new_clause = (PATTERN_LIST *)xmalloc (sizeof (PATTERN_LIST)); new_clause->patterns = copy_word_list (clause->patterns); new_clause->action = copy_command (clause->action); return (new_clause); } static PATTERN_LIST * copy_case_clauses (clauses) PATTERN_LIST *clauses; { PATTERN_LIST *new_list, *new_clause; for (new_list = (PATTERN_LIST *)NULL; clauses; clauses = clauses->next) { new_clause = copy_case_clause (clauses); new_clause->next = new_list; new_list = new_clause; } return (REVERSE_LIST (new_list, PATTERN_LIST *)); } /* Copy a single redirect. */ REDIRECT * copy_redirect (redirect) REDIRECT *redirect; { REDIRECT *new_redirect; new_redirect = (REDIRECT *)xmalloc (sizeof (REDIRECT)); FASTCOPY ((char *)redirect, (char *)new_redirect, (sizeof (REDIRECT))); switch (redirect->instruction) { case r_reading_until: case r_deblank_reading_until: new_redirect->here_doc_eof = savestring (redirect->here_doc_eof); /*FALLTHROUGH*/ case r_appending_to: case r_output_direction: case r_input_direction: case r_inputa_direction: case r_err_and_out: case r_input_output: case r_output_force: case r_duplicating_input_word: case r_duplicating_output_word: new_redirect->redirectee.filename = copy_word (redirect->redirectee.filename); break; case r_duplicating_input: case r_duplicating_output: case r_close_this: break; } return (new_redirect); } REDIRECT * copy_redirects (list) REDIRECT *list; { REDIRECT *new_list, *temp; for (new_list = (REDIRECT *)NULL; list; list = list->next) { temp = copy_redirect (list); temp->next = new_list; new_list = temp; } return (REVERSE_LIST (new_list, REDIRECT *)); } static FOR_COM * copy_for_command (com) FOR_COM *com; { FOR_COM *new_for; new_for = (FOR_COM *)xmalloc (sizeof (FOR_COM)); new_for->flags = com->flags; new_for->name = copy_word (com->name); new_for->map_list = copy_word_list (com->map_list); new_for->action = copy_command (com->action); return (new_for); } #if defined (ARITH_FOR_COMMAND) static ARITH_FOR_COM * copy_arith_for_command (com) ARITH_FOR_COM *com; { ARITH_FOR_COM *new_arith_for; new_arith_for = (ARITH_FOR_COM *)xmalloc (sizeof (ARITH_FOR_COM)); new_arith_for->flags = com->flags; new_arith_for->line = com->line; new_arith_for->init = copy_word_list (com->init); new_arith_for->test = copy_word_list (com->test); new_arith_for->step = copy_word_list (com->step); new_arith_for->action = copy_command (com->action); return (new_arith_for); } #endif /* ARITH_FOR_COMMAND */ static GROUP_COM * copy_group_command (com) GROUP_COM *com; { GROUP_COM *new_group; new_group = (GROUP_COM *)xmalloc (sizeof (GROUP_COM)); new_group->command = copy_command (com->command); return (new_group); } static SUBSHELL_COM * copy_subshell_command (com) SUBSHELL_COM *com; { SUBSHELL_COM *new_subshell; new_subshell = (SUBSHELL_COM *)xmalloc (sizeof (SUBSHELL_COM)); new_subshell->command = copy_command (com->command); new_subshell->flags = com->flags; return (new_subshell); } static CASE_COM * copy_case_command (com) CASE_COM *com; { CASE_COM *new_case; new_case = (CASE_COM *)xmalloc (sizeof (CASE_COM)); new_case->flags = com->flags; new_case->word = copy_word (com->word); new_case->clauses = copy_case_clauses (com->clauses); return (new_case); } static WHILE_COM * copy_while_command (com) WHILE_COM *com; { WHILE_COM *new_while; new_while = (WHILE_COM *)xmalloc (sizeof (WHILE_COM)); new_while->flags = com->flags; new_while->test = copy_command (com->test); new_while->action = copy_command (com->action); return (new_while); } static IF_COM * copy_if_command (com) IF_COM *com; { IF_COM *new_if; new_if = (IF_COM *)xmalloc (sizeof (IF_COM)); new_if->flags = com->flags; new_if->test = copy_command (com->test); new_if->true_case = copy_command (com->true_case); new_if->false_case = copy_command (com->false_case); return (new_if); } #if defined (DPAREN_ARITHMETIC) static ARITH_COM * copy_arith_command (com) ARITH_COM *com; { ARITH_COM *new_arith; new_arith = (ARITH_COM *)xmalloc (sizeof (ARITH_COM)); new_arith->flags = com->flags; new_arith->exp = copy_word_list (com->exp); new_arith->line = com->line; return (new_arith); } #endif #if defined (COND_COMMAND) static COND_COM * copy_cond_command (com) COND_COM *com; { COND_COM *new_cond; new_cond = (COND_COM *)xmalloc (sizeof (COND_COM)); new_cond->flags = com->flags; new_cond->line = com->line; new_cond->type = com->type; new_cond->op = com->op ? copy_word (com->op) : com->op; new_cond->left = com->left ? copy_cond_command (com->left) : (COND_COM *)NULL; new_cond->right = com->right ? copy_cond_command (com->right) : (COND_COM *)NULL; return (new_cond); } #endif static SIMPLE_COM * copy_simple_command (com) SIMPLE_COM *com; { SIMPLE_COM *new_simple; new_simple = (SIMPLE_COM *)xmalloc (sizeof (SIMPLE_COM)); new_simple->flags = com->flags; new_simple->words = copy_word_list (com->words); new_simple->redirects = copy_redirects (com->redirects); new_simple->line = com->line; return (new_simple); } static FUNCTION_DEF * copy_function_def (com) FUNCTION_DEF *com; { FUNCTION_DEF *new_def; new_def = (FUNCTION_DEF *)xmalloc (sizeof (FUNCTION_DEF)); new_def->name = copy_word (com->name); new_def->command = copy_command (com->command); new_def->flags = com->flags; new_def->line = com->line; return (new_def); } /* Copy the command structure in COMMAND. Return a pointer to the copy. Don't you forget to dispose_command () on this pointer later! */ COMMAND * copy_command (command) COMMAND *command; { COMMAND *new_command; if (command == NULL) return (command); new_command = (COMMAND *)xmalloc (sizeof (COMMAND)); FASTCOPY ((char *)command, (char *)new_command, sizeof (COMMAND)); new_command->flags = command->flags; new_command->line = command->line; if (command->redirects) new_command->redirects = copy_redirects (command->redirects); switch (command->type) { case cm_for: new_command->value.For = copy_for_command (command->value.For); break; #if defined (ARITH_FOR_COMMAND) case cm_arith_for: new_command->value.ArithFor = copy_arith_for_command (command->value.ArithFor); break; #endif #if defined (SELECT_COMMAND) case cm_select: new_command->value.Select = (SELECT_COM *)copy_for_command ((FOR_COM *)command->value.Select); break; #endif case cm_group: new_command->value.Group = copy_group_command (command->value.Group); break; case cm_subshell: new_command->value.Subshell = copy_subshell_command (command->value.Subshell); break; case cm_case: new_command->value.Case = copy_case_command (command->value.Case); break; case cm_until: case cm_while: new_command->value.While = copy_while_command (command->value.While); break; case cm_if: new_command->value.If = copy_if_command (command->value.If); break; #if defined (DPAREN_ARITHMETIC) case cm_arith: new_command->value.Arith = copy_arith_command (command->value.Arith); break; #endif #if defined (COND_COMMAND) case cm_cond: new_command->value.Cond = copy_cond_command (command->value.Cond); break; #endif case cm_simple: new_command->value.Simple = copy_simple_command (command->value.Simple); break; case cm_connection: { CONNECTION *new_connection; new_connection = (CONNECTION *)xmalloc (sizeof (CONNECTION)); new_connection->connector = command->value.Connection->connector; new_connection->first = copy_command (command->value.Connection->first); new_connection->second = copy_command (command->value.Connection->second); new_command->value.Connection = new_connection; break; } case cm_function_def: new_command->value.Function_def = copy_function_def (command->value.Function_def); break; } return (new_command); } /* dispose_command.c -- dispose of a COMMAND structure. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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" #if defined (HAVE_UNISTD_H) # include #endif #include "bashansi.h" #include "shell.h" /* Dispose of the command structure passed. */ void dispose_command (command) COMMAND *command; { if (command == 0) return; if (command->redirects) dispose_redirects (command->redirects); switch (command->type) { case cm_for: #if defined (SELECT_COMMAND) case cm_select: #endif { register FOR_COM *c; #if defined (SELECT_COMMAND) if (command->type == cm_select) c = (FOR_COM *)command->value.Select; else #endif c = command->value.For; dispose_word (c->name); dispose_words (c->map_list); dispose_command (c->action); free (c); break; } #if defined (ARITH_FOR_COMMAND) case cm_arith_for: { register ARITH_FOR_COM *c; c = command->value.ArithFor; dispose_words (c->init); dispose_words (c->test); dispose_words (c->step); dispose_command (c->action); free (c); break; } #endif /* ARITH_FOR_COMMAND */ case cm_group: { dispose_command (command->value.Group->command); free (command->value.Group); break; } case cm_subshell: { dispose_command (command->value.Subshell->command); free (command->value.Subshell); break; } case cm_case: { register CASE_COM *c; PATTERN_LIST *t, *p; c = command->value.Case; dispose_word (c->word); for (p = c->clauses; p; ) { dispose_words (p->patterns); dispose_command (p->action); t = p; p = p->next; free (t); } free (c); break; } case cm_until: case cm_while: { register WHILE_COM *c; c = command->value.While; dispose_command (c->test); dispose_command (c->action); free (c); break; } case cm_if: { register IF_COM *c; c = command->value.If; dispose_command (c->test); dispose_command (c->true_case); dispose_command (c->false_case); free (c); break; } case cm_simple: { register SIMPLE_COM *c; c = command->value.Simple; dispose_words (c->words); dispose_redirects (c->redirects); free (c); break; } case cm_connection: { register CONNECTION *c; c = command->value.Connection; dispose_command (c->first); dispose_command (c->second); free (c); break; } #if defined (DPAREN_ARITHMETIC) case cm_arith: { register ARITH_COM *c; c = command->value.Arith; dispose_words (c->exp); free (c); break; } #endif /* DPAREN_ARITHMETIC */ #if defined (COND_COMMAND) case cm_cond: { register COND_COM *c; c = command->value.Cond; dispose_cond_node (c); break; } #endif /* COND_COMMAND */ case cm_function_def: { register FUNCTION_DEF *c; c = command->value.Function_def; dispose_word (c->name); dispose_command (c->command); free (c); break; } default: command_error ("dispose_command", CMDERR_BADTYPE, command->type, 0); break; } free (command); } #if defined (COND_COMMAND) /* How to free a node in a conditional command. */ void dispose_cond_node (cond) COND_COM *cond; { if (cond) { if (cond->left) dispose_cond_node (cond->left); if (cond->right) dispose_cond_node (cond->right); if (cond->op) dispose_word (cond->op); free (cond); } } #endif /* COND_COMMAND */ /* How to free a WORD_DESC. */ void dispose_word (w) WORD_DESC *w; { FREE (w->word); free (w); } /* How to get rid of a linked list of words. A WORD_LIST. */ void dispose_words (list) WORD_LIST *list; { WORD_LIST *t; while (list) { t = list; list = list->next; dispose_word (t->word); free (t); } } #ifdef INCLUDE_UNUSED /* How to dispose of an array of pointers to char. This is identical to free_array in stringlib.c. */ void dispose_word_array (array) char **array; { register int count; if (array == 0) return; for (count = 0; array[count]; count++) free (array[count]); free (array); } #endif /* How to dispose of an list of redirections. A REDIRECT. */ void dispose_redirects (list) REDIRECT *list; { register REDIRECT *t; while (list) { t = list; list = list->next; switch (t->instruction) { case r_reading_until: case r_deblank_reading_until: free (t->here_doc_eof); /*FALLTHROUGH*/ case r_output_direction: case r_input_direction: case r_inputa_direction: case r_appending_to: case r_err_and_out: case r_input_output: case r_output_force: case r_duplicating_input_word: case r_duplicating_output_word: dispose_word (t->redirectee.filename); /* FALLTHROUGH */ default: break; } free (t); } } /* error.c -- Functions for handling errors. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 #if defined (HAVE_UNISTD_H) # include #endif #if defined (PREFER_STDARG) # include #else # if defined (PREFER_VARARGS) # include # endif #endif #include #include #if !defined (errno) extern int errno; #endif /* !errno */ #include "bashansi.h" #include "flags.h" #include "error.h" #include "command.h" #include "general.h" #include "externs.h" #include "input.h" #if defined (HISTORY) # include "bashhist.h" #endif extern int interactive_shell, interactive; extern char *dollar_vars[]; extern char *shell_name; #if defined (JOB_CONTROL) extern pid_t shell_pgrp; extern int give_terminal_to __P((pid_t, int)); #endif /* JOB_CONTROL */ /* The current maintainer of the shell. You change this in the Makefile. */ #if !defined (MAINTAINER) #define MAINTAINER "bash-maintainers@gnu.org" #endif char *the_current_maintainer = MAINTAINER; /* Return the name of the shell or the shell script for error reporting. */ char * get_name_for_error () { char *name; name = (char *)NULL; if (interactive_shell == 0) name = dollar_vars[0]; if (name == 0 && shell_name && *shell_name) name = base_pathname (shell_name); if (name == 0) #if defined (PROGRAM) name = PROGRAM; #else name = "bash"; #endif return (name); } /* Report an error having to do with FILENAME. This does not use sys_error so the filename is not interpreted as a printf-style format string. */ void file_error (filename) const char *filename; { report_error ("%s: %s", filename, strerror (errno)); } #if !defined (USE_VARARGS) void programming_error (reason, arg1, arg2, arg3, arg4, arg5) char *reason; { char *h; #if defined (JOB_CONTROL) give_terminal_to (shell_pgrp); #endif /* JOB_CONTROL */ report_error (reason, arg1, arg2); #if defined (HISTORY) if (remember_on_history) { h = last_history_line (); fprintf (stderr, "last command: %s\n", h ? h : "(null)"); } #endif #if 0 fprintf (stderr, "Report this to %s\n", the_current_maintainer); #endif fprintf (stderr, "Stopping myself..."); fflush (stderr); abort (); } void report_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); if (exit_immediately_on_error) exit (1); } void parser_error (lineno, format, arg1, arg2, arg3, arg4, arg5); int lineno; char *format; va_dcl { char *ename, *iname; ename = get_name_for_error (); iname = bash_input.name ? bash_input.name : "stdin"; if (interactive) fprintf (stderr, "%s: ", ename); else if (interactive_shell) fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); else if (STREQ (ename, iname)) fprintf (stderr, "%s: line %d: ", ename, lineno); else fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); if (exit_immediately_on_error) exit (2); } void fatal_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); exit (2); } void internal_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); } void internal_warning (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: warning: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); } void sys_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, ": %s\n", strerror (errno)); } #else /* We have VARARGS support, so use it. */ void #if defined (PREFER_STDARG) programming_error (const char *format, ...) #else programming_error (format, va_alist) const char *format; va_dcl #endif { va_list args; char *h; #if defined (JOB_CONTROL) give_terminal_to (shell_pgrp, 0); #endif /* JOB_CONTROL */ #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); #if defined (HISTORY) if (remember_on_history) { h = last_history_line (); fprintf (stderr, "last command: %s\n", h ? h : "(null)"); } #endif #if 0 fprintf (stderr, "Report this to %s\n", the_current_maintainer); #endif fprintf (stderr, "Stopping myself..."); fflush (stderr); abort (); } void #if defined (PREFER_STDARG) report_error (const char *format, ...) #else report_error (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); if (exit_immediately_on_error) exit (1); } void #if defined (PREFER_STDARG) fatal_error (const char *format, ...) #else fatal_error (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); exit (2); } void #if defined (PREFER_STDARG) internal_error (const char *format, ...) #else internal_error (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); } void #if defined (PREFER_STDARG) internal_warning (const char *format, ...) #else internal_warning (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: warning: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); } void #if defined (PREFER_STDARG) sys_error (const char *format, ...) #else sys_error (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, ": %s\n", strerror (errno)); va_end (args); } /* An error from the parser takes the general form shell_name: input file name: line number: message The input file name and line number are omitted if the shell is currently interactive. If the shell is not currently interactive, the input file name is inserted only if it is different from the shell name. */ void #if defined (PREFER_STDARG) parser_error (int lineno, const char *format, ...) #else parser_error (lineno, format, va_alist) int lineno; const char *format; va_dcl #endif { va_list args; char *ename, *iname; ename = get_name_for_error (); iname = bash_input.name ? bash_input.name : "stdin"; if (interactive) fprintf (stderr, "%s: ", ename); else if (interactive_shell) fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); else if (STREQ (ename, iname)) fprintf (stderr, "%s: line %d: ", ename, lineno); else fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); if (exit_immediately_on_error) exit (2); } #ifdef DEBUG void #if defined (PREFER_STDARG) itrace (const char *format, ...) #else itrace (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf(stderr, "TRACE: pid %ld: ", (long)getpid()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); fflush(stderr); } /* A trace function for silent debugging -- doesn't require a control terminal. */ void #if defined (PREFER_STDARG) trace (const char *format, ...) #else trace (format, va_alist) const char *format; va_dcl #endif { va_list args; static FILE *tracefp = (FILE *)NULL; if (tracefp == NULL) tracefp = fopen("/tmp/bash-trace.log", "a+"); if (tracefp == NULL) tracefp = stderr; else fcntl (fileno (tracefp), F_SETFD, 1); /* close-on-exec */ fprintf(tracefp, "TRACE: pid %ld: ", (long)getpid()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (tracefp, format, args); fprintf (tracefp, "\n"); va_end (args); fflush(tracefp); } #endif /* USE_VARARGS */ #endif /* DEBUG */ static char *cmd_error_table[] = { "unknown command error", /* CMDERR_DEFAULT */ "bad command type", /* CMDERR_BADTYPE */ "bad connector", /* CMDERR_BADCONN */ "bad jump", /* CMDERR_BADJUMP */ 0 }; void command_error (func, code, e, flags) const char *func; int code, e, flags; /* flags currently unused */ { if (code > CMDERR_LAST) code = CMDERR_DEFAULT; programming_error ("%s: %s: %d", func, cmd_error_table[code], e); } char * command_errstr (code) int code; { if (code > CMDERR_LAST) code = CMDERR_DEFAULT; return (cmd_error_table[code]); } /* eval.c -- reading and evaluating commands. */ /* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "bashansi.h" #include #include "shell.h" #include "flags.h" #include "trap.h" #include "builtins/common.h" #include "input.h" #include "execute_cmd.h" #if defined (HISTORY) # include "bashhist.h" #endif extern int EOF_reached; extern int indirection_level; extern int posixly_correct; extern int subshell_environment, running_under_emacs; extern int last_command_exit_value, stdin_redir; extern int need_here_doc; extern int current_command_number, current_command_line_count, line_number; extern int expand_aliases; /* Read and execute commands until EOF is reached. This assumes that the input source has already been initialized. */ int reader_loop () { int our_indirection_level; COMMAND *current_command = (COMMAND *)NULL; USE_VAR(current_command); our_indirection_level = ++indirection_level; while (EOF_Reached == 0) { int code; code = setjmp (top_level); #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ if (interactive_shell && signal_is_ignored (SIGINT) == 0) set_signal_handler (SIGINT, sigint_sighandler); if (code != NOT_JUMPED) { indirection_level = our_indirection_level; switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: case EXITPROG: current_command = (COMMAND *)NULL; if (exit_immediately_on_error) variable_context = 0; /* not in a function */ EOF_Reached = EOF; goto exec_done; case DISCARD: last_command_exit_value = 1; if (subshell_environment) { current_command = (COMMAND *)NULL; EOF_Reached = EOF; goto exec_done; } /* Obstack free command elements, etc. */ if (current_command) { dispose_command (current_command); current_command = (COMMAND *)NULL; } break; default: command_error ("reader_loop", CMDERR_BADJUMP, code, 0); } } executing = 0; dispose_used_env_vars (); #if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA) /* Attempt to reclaim memory allocated with alloca (). */ (void) alloca (0); #endif if (read_command () == 0) { if (interactive_shell == 0 && read_but_dont_execute) { last_command_exit_value = EXECUTION_SUCCESS; dispose_command (global_command); global_command = (COMMAND *)NULL; } else if (current_command = global_command) { global_command = (COMMAND *)NULL; current_command_number++; executing = 1; stdin_redir = 0; execute_command (current_command); exec_done: if (current_command) { dispose_command (current_command); current_command = (COMMAND *)NULL; } QUIT; } } else { /* Parse error, maybe discard rest of stream if not interactive. */ if (interactive == 0) EOF_Reached = EOF; } if (just_one_command) EOF_Reached = EOF; } indirection_level--; return (last_command_exit_value); } static sighandler alrm_catcher(i) int i; { printf ("\007timed out waiting for input: auto-logout\n"); jump_to_top_level (EXITPROG); SIGRETURN (0); } /* Send an escape sequence to emacs term mode to tell it the current working directory. */ static void send_pwd_to_eterm () { char *pwd; pwd = get_string_value ("PWD"); if (pwd == 0) pwd = get_working_directory ("eterm"); fprintf (stderr, "\032/%s\n", pwd); } /* Call the YACC-generated parser and return the status of the parse. Input is read from the current input stream (bash_input). yyparse leaves the parsed command in the global variable GLOBAL_COMMAND. This is where PROMPT_COMMAND is executed. */ int parse_command () { int r; char *command_to_execute; need_here_doc = 0; run_pending_traps (); /* Allow the execution of a random command just before the printing of each primary prompt. If the shell variable PROMPT_COMMAND is set then the value of it is the command to execute. */ if (interactive && bash_input.type != st_string) { command_to_execute = get_string_value ("PROMPT_COMMAND"); if (command_to_execute) execute_prompt_command (command_to_execute); if (running_under_emacs == 2) send_pwd_to_eterm (); /* Yuck */ } current_command_line_count = 0; r = yyparse (); if (need_here_doc) gather_here_documents (); return (r); } /* Read and parse a command, returning the status of the parse. The command is left in the globval variable GLOBAL_COMMAND for use by reader_loop. This is where the shell timeout code is executed. */ int read_command () { SHELL_VAR *tmout_var; int tmout_len, result; SigHandler *old_alrm; set_current_prompt_level (1); global_command = (COMMAND *)NULL; /* Only do timeouts if interactive. */ tmout_var = (SHELL_VAR *)NULL; tmout_len = 0; old_alrm = (SigHandler *)NULL; if (interactive) { tmout_var = find_variable ("TMOUT"); if (tmout_var && tmout_var->value) { tmout_len = atoi (tmout_var->value); if (tmout_len > 0) { old_alrm = set_signal_handler (SIGALRM, alrm_catcher); alarm (tmout_len); } } } QUIT; current_command_line_count = 0; result = parse_command (); if (interactive && tmout_var && (tmout_len > 0)) { alarm(0); set_signal_handler (SIGALRM, old_alrm); } return (result); } /* execute_command.c -- Execute a COMMAND structure. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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" #ifndef _MINIX # 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 "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, trap_line_number; extern int command_string_index, line_number; extern int dot_found_in_search; extern int already_making_children; extern char **temporary_env, **function_env, **builtin_env; 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 *)); 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 long 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; static COMMAND *currently_executing_command; struct stat SB; /* used for debugging */ static int special_builtin_failed; /* 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; /* 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; /* 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); bzero (ret->bitmap, 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 && variable_context == 0 && currently_executing_command && currently_executing_command->type == cm_simple) return currently_executing_command->value.Simple->line; else if (running_trap) return trap_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, 1, 0, 0); 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, 1, 0, 0); } #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_debug_trap, was_error_trap; REDIRECT *my_undo_list, *exec_undo_list; volatile pid_t last_pid; if (command == 0 || breaking || continuing || read_but_dont_execute) return (EXECUTION_SUCCESS); run_pending_traps (); if (running_trap == 0) 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; /* 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 (running_trap == 0) 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, 1, 1, 0) != 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: { /* We can't rely on this variable retaining its value 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_debug_trap = signal_is_trapped (DEBUG_TRAP) && signal_is_ignored (DEBUG_TRAP) == 0; 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; exec_result = execute_simple_command (command->value.Simple, pipe_in, pipe_out, asynchronous, fds_to_close); /* 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. */ 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. */ if (exec_result == 0) last_made_pid = NO_PID; #endif } } if (was_debug_trap) { last_command_exit_value = exec_result; run_debug_trap (); } 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 (EXITPROG); } 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; exec_result = execute_cond_command (command->value.Cond); 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, 1, 0, 0); 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 (running_trap == 0) 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 ("bad format character in time format: %c", *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; struct timezone dtz; 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) gettimeofday (&before, &dtz); 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) gettimeofday (&after, &dtz); 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); 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); /* 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, 1, 0, 0) != 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 (); } 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; { #if 0 REDIRECT *tr, *tl; #endif 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 */ { #if 0 rd.filename = make_bare_word ("/dev/null"); tr = make_redirection (0, r_inputa_direction, rd); tr->next = tc->redirects; tc->redirects = tr; #endif 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) { #if 0 /* Remove the redirection we added above. It matters, especially for loops, which call execute_command () multiple times with the same command. */ tr = tc->redirects; do { tl = tc->redirects; tc->redirects = tc->redirects->next; } while (tc->redirects && tc->redirects != rp); tl->next = (REDIRECT *)NULL; dispose_redirects (tr); #endif 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; #if 0 SHELL_VAR *old_value = (SHELL_VAR *)NULL; /* Remember the old value of x. */ #endif if (check_identifier (for_command->name, 1) == 0) { if (posixly_correct && interactive_shell == 0) { last_command_exit_value = EX_USAGE; jump_to_top_level (EXITPROG); } 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; this_command_name = (char *)NULL; v = bind_variable (identifier, list->word->word); 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 { run_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--; #if 0 if (lexical_scoping) { if (!old_value) makunbound (identifier, shell_variables); 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 long eval_arith_for_expr (l, okp) WORD_LIST *l; int *okp; { WORD_LIST *new; long expresult; new = expand_words_no_vars (l); if (new) { if (echo_command_at_execute) xtrace_print_arith_cmd (new); expresult = evalexp (new->word->word, okp); 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; { long expresult; int expok, body_status; body_status = EXECUTION_SUCCESS; loop_level++; if (arith_for_command->flags & CMD_IGNORE_RETURN) arith_for_command->action->flags |= CMD_IGNORE_RETURN; this_command_name = "(("; /* )) for expression error messages */ if (variable_context) line_number = arith_for_command->line - function_line_number; /* Evaluate the initialization expression. */ expresult = eval_arith_for_expr (arith_for_command->init, &expok); if (expok == 0) return (EXECUTION_FAILURE); while (1) { /* Evaluate the test expression. */ expresult = eval_arith_for_expr (arith_for_command->test, &expok); 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. */ expresult = eval_arith_for_expr (arith_for_command->step, &expok); if (expok == 0) { body_status = EXECUTION_FAILURE; break; } } loop_level--; 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) WORD_LIST *list; int list_len; char *prompt; { int max_elem_len, indices_len, len; long 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) { 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) 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; if (check_identifier (select_command->name, 1) == 0) return (EXECUTION_FAILURE); 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); 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; while (1) { ps3_prompt = get_string_value ("PS3"); if (ps3_prompt == 0) ps3_prompt = "#? "; QUIT; selection = select_query (list, list_len, ps3_prompt); QUIT; if (selection == 0) 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 { run_unwind_frame ("select"); return (EXECUTION_FAILURE); } } retval = execute_command (select_command->action); REAP (); QUIT; if (breaking) { breaking--; break; } if (continuing) { continuing--; if (continuing) break; } } loop_level--; run_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; /* Posix.2 specifies that the WORD is tilde expanded. */ if (member ('~', case_command->word->word)) { word = bash_tilde_expand (case_command->word->word); 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); 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"); 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; if_command->test->flags |= CMD_IGNORE_RETURN; return_value = execute_command (if_command->test); 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; long expresult; WORD_LIST *new; expresult = 0; this_command_name = "(("; /* )) */ /* If we're in a function, update the line number information. */ if (variable_context) line_number = arith_command->line - function_line_number; new = expand_words (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); expresult = evalexp (new->word->word, &expok); dispose_words (new); 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; 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')); 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); 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 result; result = EXECUTION_SUCCESS; this_command_name = "[["; /* If we're in a function, update the line number information. */ if (variable_context) line_number = cond_command->line - function_line_number; #if 0 debug_print_cond_command (cond_command); #endif last_command_exit_value = result = execute_cond_node (cond_command); return (result); } #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; { 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, 1, 0, 0) == 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. */ if (do_redirections (redirects, 0, 0, 0) != 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 = builtin_address_internal (words->word->word, 0); if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0) return; for (w = words; w; w = w->next) if (w->word->flags & W_ASSIGNMENT) w->word->flags |= (W_NOSPLIT|W_NOGLOB); } /* 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) line_number = simple_command->line - function_line_number; /* Remember what this command line looks like at invocation. */ command_string_index = 0; print_simple_command (simple_command); 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) { /* XXX memory leak if expand_words() error causes a jump_to_top_level */ command_line = savestring (the_printed_command); /* Do this now, because execute_disk_command will do it anyway in the vast majority of cases. */ maybe_make_export_env (); if (make_child (command_line, async) == 0) { 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) { 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); 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"))) { char *word; register int i; int wl, cl, exact_p, substring_p, match, started_status; register PROCESS *p; word = words->word->word; exact_p = STREQ (temp, "exact"); substring_p = STREQ (temp, "substring"); wl = strlen (word); for (i = job_slots - 1; i > -1; i--) { if (jobs[i] == 0 || (JOBSTATE (i) != JSTOPPED)) continue; p = jobs[i]->pipe; do { if (exact_p) { cl = strlen (p->command); match = STREQN (p->command, word, cl); } else if (substring_p) match = strindex (p->command, word) != (char *)0; else match = STREQN (p->command, word, wl); if (match == 0) { p = p->next; continue; } 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 (i, 1); return ((started_status < 0) ? EXECUTION_FAILURE : started_status); } while (p != jobs[i]->pipe); } } #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 (); } execute_subshell_builtin_or_function (words, simple_command->redirects, builtin, func, pipe_in, pipe_out, async, fds_to_close, simple_command->flags); } 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); run_unwind_frame ("simple-command"); 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; 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. */ if (builtin == source_builtin || builtin == eval_builtin) { if (subshell == 0) begin_unwind_frame ("builtin_env"); if (temporary_env) { builtin_env = copy_array (temporary_env); if (subshell == 0) add_unwind_protect (dispose_builtin_env, (char *)NULL); dispose_used_env_vars (); } /* Otherwise we inherit builtin_env from our caller. */ } /* `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 && (builtin == source_builtin || builtin == eval_builtin)) { /* In POSIX mode, if any variable assignments precede the `.' or `eval' builtin, they persist after the builtin completes, since `.' and `eval' are special builtins. */ if (posixly_correct && builtin_env) merge_builtin_env (); 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; char *debug_trap, *error_trap; USE_VAR(fc); 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 (); 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); } this_shell_function = var; make_funcname_visible (1); debug_trap = TRAP_STRING(DEBUG_TRAP); error_trap = TRAP_STRING(ERROR_TRAP); /* The order of the unwind protects for debug_trap and error_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. */ if (debug_trap) { 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); } if (error_trap) { 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); } /* The temporary environment for a function is supposed to apply to all commands executed within the function body. */ if (temporary_env) { function_env = copy_array (temporary_env); /* In POSIX mode, variable assignments preceding function names are supposed to persist in the environment after the function returns, as if a special builtin command had been executed. */ if (subshell == 0) { if (posixly_correct) add_unwind_protect (merge_function_env, (char *)NULL); else add_unwind_protect (dispose_function_env, (char *)NULL); } dispose_used_env_vars (); } /* Otherwise, we inherit function_env from our caller. */ remember_args (words->next, 1); /* 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; variable_context++; } else fc = tc; return_catch_flag++; return_val = setjmp (return_catch); if (return_val) result = return_catch_value; else result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close); if (subshell == 0) run_unwind_frame ("function_calling"); 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, 1, 0, 0) != 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, 1, 1, 0) != 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); /* 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. */ 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) if (restricted && strchr (pathname, '/')) { internal_error ("%s: restricted: cannot specify `/' in command names", pathname); last_command_exit_value = EXECUTION_FAILURE; 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, 1, 0, 0) != 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 = word_list_to_argv (words, 0, 0, (int *)NULL); exit (shell_execve (command, args, export_env)); } else { /* 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 = array_len (args) + size_increment; args = (char **)xrealloc ((char *)args, (1 + larry) * sizeof (char *)); 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). */ builtin_env = 0; 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 (1); } #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 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 = array_len (args) + 1; args = (char **)xrealloc ((char *)args, (1 + larray) * sizeof (char *)); 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 (EXITPROG); } 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); } /* 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) sys_error ("cannot duplicate fd %d to fd 0", pipe_in); if (pipe_in > 0) close (pipe_in); } if (pipe_out != NO_PIPE) { if (pipe_out != REDIRECT_BOTH) { if (dup2 (pipe_out, 1) < 0) sys_error ("cannot duplicate fd %d to fd 1", pipe_out); if (pipe_out == 0 || pipe_out > 1) close (pipe_out); } else { if (dup2 (1, 2) < 0) sys_error ("cannot duplicate fd 1 to fd 2"); } } } /* expr.c -- arithmetic expression evaluation. */ /* Copyright (C) 1990, 1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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. */ /* All arithmetic is done as long integers with no checking for overflow (though division by 0 is caught and flagged as an error). The following operators are handled, grouped into a set of levels in order of decreasing precedence. "id++", "id--" [post-increment and post-decrement] "++id", "--id" [pre-increment and pre-decrement] "-", "+" [(unary operators)] "!", "~" "**" [(exponentiation)] "*", "/", "%" "+", "-" "<<", ">>" "<=", ">=", "<", ">" "==", "!=" "&" "^" "|" "&&" "||" "expr ? expr : expr" "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=" (Note that most of these operators have special meaning to bash, and an entire expression should be quoted, e.g. "a=$a+1" or "a=a+1" to ensure that it is passed intact to the evaluator when using `let'. When using the $[] or $(( )) forms, the text between the `[' and `]' or `((' and `))' is treated as if in double quotes.) Sub-expressions within parentheses have a precedence level greater than all of the above levels and are evaluated first. Within a single prece- dence group, evaluation is left-to-right, except for the arithmetic assignment operator (`='), which is evaluated right-to-left (as in C). The expression evaluator returns the value of the expression (assignment statements have as a value what is returned by the RHS). The `let' builtin, on the other hand, returns 0 if the last expression evaluates to a non-zero, and 1 otherwise. Implementation is a recursive-descent parser. Chet Ramey chet@ins.CWRU.Edu */ #include "config.h" #include #include "bashansi.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "chartypes.h" #include "shell.h" /* Because of the $((...)) construct, expressions may include newlines. Here is a macro which accepts newlines, tabs and spaces as whitespace. */ #define cr_whitespace(c) (whitespace(c) || ((c) == '\n')) /* Size be which the expression stack grows when neccessary. */ #define EXPR_STACK_GROW_SIZE 10 /* Maximum amount of recursion allowed. This prevents a non-integer variable such as "num=num+2" from infinitely adding to itself when "let num=num+2" is given. */ #define MAX_EXPR_RECURSION_LEVEL 1024 /* The Tokens. Singing "The Lion Sleeps Tonight". */ #define EQEQ 1 /* "==" */ #define NEQ 2 /* "!=" */ #define LEQ 3 /* "<=" */ #define GEQ 4 /* ">=" */ #define STR 5 /* string */ #define NUM 6 /* number */ #define LAND 7 /* "&&" Logical AND */ #define LOR 8 /* "||" Logical OR */ #define LSH 9 /* "<<" Left SHift */ #define RSH 10 /* ">>" Right SHift */ #define OP_ASSIGN 11 /* op= expassign as in Posix.2 */ #define COND 12 /* exp1 ? exp2 : exp3 */ #define POWER 13 /* exp1**exp2 */ #define PREINC 14 /* ++var */ #define PREDEC 15 /* --var */ #define POSTINC 16 /* var++ */ #define POSTDEC 17 /* var-- */ #define EQ '=' #define GT '>' #define LT '<' #define PLUS '+' #define MINUS '-' #define MUL '*' #define DIV '/' #define MOD '%' #define NOT '!' #define LPAR '(' #define RPAR ')' #define BAND '&' /* Bitwise AND */ #define BOR '|' /* Bitwise OR. */ #define BXOR '^' /* Bitwise eXclusive OR. */ #define BNOT '~' /* Bitwise NOT; Two's complement. */ #define QUES '?' #define COL ':' #define COMMA ',' /* This should be the function corresponding to the operator with the highest precedence. */ #define EXP_HIGHEST expcomma static char *expression; /* The current expression */ static char *tp; /* token lexical position */ static char *lasttp; /* pointer to last token position */ static int curtok; /* the current token */ static int lasttok; /* the previous token */ static int assigntok; /* the OP in OP= */ static char *tokstr; /* current token string */ static long tokval; /* current token value */ static int noeval; /* set to 1 if no assignment to be done */ static procenv_t evalbuf; static void readtok __P((void)); /* lexical analyzer */ static long strlong __P((char *)); static void evalerror __P((char *)); static void pushexp __P((void)); static void popexp __P((void)); static long subexpr __P((char *)); static long expcomma __P((void)); static long expassign __P((void)); static long expcond __P((void)); static long explor __P((void)); static long expland __P((void)); static long expbor __P((void)); static long expbxor __P((void)); static long expband __P((void)); static long exp5 __P((void)); static long exp4 __P((void)); static long expshift __P((void)); static long exp3 __P((void)); static long exp2 __P((void)); static long exppower __P((void)); static long exp1 __P((void)); static long exp0 __P((void)); /* A structure defining a single expression context. */ typedef struct { int curtok, lasttok; char *expression, *tp, *lasttp; long tokval; char *tokstr; int noeval; } EXPR_CONTEXT; #ifdef INCLUDE_UNUSED /* Not used yet. */ typedef struct { char *tokstr; long tokval; } LVALUE; #endif /* Global var which contains the stack of expression contexts. */ static EXPR_CONTEXT **expr_stack; static int expr_depth; /* Location in the stack. */ static int expr_stack_size; /* Number of slots already allocated. */ extern char *this_command_name; #define SAVETOK(X) \ do { \ (X)->curtok = curtok; \ (X)->lasttok = lasttok; \ (X)->tp = tp; \ (X)->lasttp = lasttp; \ (X)->tokval = tokval; \ (X)->tokstr = tokstr; \ (X)->noeval = noeval; \ } while (0) #define RESTORETOK(X) \ do { \ curtok = (X)->curtok; \ lasttok = (X)->lasttok; \ tp = (X)->tp; \ lasttp = (X)->lasttp; \ tokval = (X)->tokval; \ tokstr = (X)->tokstr; \ noeval = (X)->noeval; \ } while (0) /* Push and save away the contents of the globals describing the current expression context. */ static void pushexp () { EXPR_CONTEXT *context; if (expr_depth >= MAX_EXPR_RECURSION_LEVEL) evalerror ("expression recursion level exceeded"); if (expr_depth >= expr_stack_size) { expr_stack_size += EXPR_STACK_GROW_SIZE; expr_stack = (EXPR_CONTEXT **)xrealloc (expr_stack, expr_stack_size * sizeof (EXPR_CONTEXT *)); } context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT)); context->expression = expression; SAVETOK(context); expr_stack[expr_depth++] = context; } /* Pop the the contents of the expression context stack into the globals describing the current expression context. */ static void popexp () { EXPR_CONTEXT *context; if (expr_depth == 0) evalerror ("recursion stack underflow"); context = expr_stack[--expr_depth]; expression = context->expression; RESTORETOK (context); free (context); } /* Evaluate EXPR, and return the arithmetic result. If VALIDP is non-null, a zero is stored into the location to which it points if the expression is invalid, non-zero otherwise. If a non-zero value is returned in *VALIDP, the return value of evalexp() may be used. The `while' loop after the longjmp is caught relies on the above implementation of pushexp and popexp leaving in expr_stack[0] the values that the variables had when the program started. That is, the first things saved are the initial values of the variables that were assigned at program startup or by the compiler. Therefore, it is safe to let the loop terminate when expr_depth == 0, without freeing up any of the expr_depth[0] stuff. */ long evalexp (expr, validp) char *expr; int *validp; { long val; #if 0 procenv_t old_evalbuf; #endif val = 0; #if 0 /* Save the value of evalbuf to protect it around possible recursive calls to evalexp (). */ COPY_PROCENV (evalbuf, old_evalbuf); #endif if (setjmp (evalbuf)) { FREE (tokstr); FREE (expression); tokstr = expression = (char *)NULL; while (--expr_depth > 0) { if (expr_stack[expr_depth]->tokstr) free (expr_stack[expr_depth]->tokstr); if (expr_stack[expr_depth]->expression) free (expr_stack[expr_depth]->expression); free (expr_stack[expr_depth]); } free (expr_stack[expr_depth]); /* free the allocated EXPR_CONTEXT */ if (validp) *validp = 0; return (0); } val = subexpr (expr); #if 0 /* Restore the value of evalbuf so that any subsequent longjmp calls will have a valid location to jump to. */ COPY_PROCENV (old_evalbuf, evalbuf); #endif if (validp) *validp = 1; return (val); } static long subexpr (expr) char *expr; { long val; char *p; for (p = expr; p && *p && cr_whitespace (*p); p++) ; if (p == NULL || *p == '\0') return (0); pushexp (); curtok = lasttok = 0; expression = savestring (expr); tp = expression; tokstr = (char *)NULL; tokval = 0; readtok (); val = EXP_HIGHEST (); if (curtok != 0) evalerror ("syntax error in expression"); FREE (tokstr); FREE (expression); popexp (); return val; } static long expcomma () { register long value; value = expassign (); while (curtok == COMMA) { readtok (); value = expassign (); } return value; } static long expassign () { register long value; char *lhs, *rhs; value = expcond (); if (curtok == EQ || curtok == OP_ASSIGN) { int special, op; long lvalue; special = curtok == OP_ASSIGN; if (lasttok != STR) evalerror ("attempted assignment to non-variable"); if (special) { op = assigntok; /* a OP= b */ lvalue = value; } lhs = savestring (tokstr); readtok (); value = expassign (); if (special) { switch (op) { case MUL: lvalue *= value; break; case DIV: lvalue /= value; break; case MOD: lvalue %= value; break; case PLUS: lvalue += value; break; case MINUS: lvalue -= value; break; case LSH: lvalue <<= value; break; case RSH: lvalue >>= value; break; case BAND: lvalue &= value; break; case BOR: lvalue |= value; break; case BXOR: lvalue ^= value; break; default: free (lhs); evalerror ("bug: bad expassign token"); break; } value = lvalue; } rhs = itos (value); if (noeval == 0) (void)bind_int_variable (lhs, rhs); free (rhs); free (lhs); FREE (tokstr); tokstr = (char *)NULL; /* For freeing on errors. */ } return (value); } /* Conditional expression (expr?expr:expr) */ static long expcond () { long cval, val1, val2, rval; int set_noeval; set_noeval = 0; rval = cval = explor (); if (curtok == QUES) /* found conditional expr */ { readtok (); if (curtok == 0 || curtok == COL) evalerror ("expression expected"); if (cval == 0) { set_noeval = 1; noeval++; } val1 = EXP_HIGHEST (); if (set_noeval) noeval--; if (curtok != COL) evalerror ("`:' expected for conditional expression"); readtok (); if (curtok == 0) evalerror ("expression expected"); set_noeval = 0; if (cval) { set_noeval = 1; noeval++; } val2 = explor (); if (set_noeval) noeval--; rval = cval ? val1 : val2; lasttok = COND; } return rval; } /* Logical OR. */ static long explor () { register long val1, val2; int set_noeval; val1 = expland (); while (curtok == LOR) { set_noeval = 0; if (val1 != 0) { noeval++; set_noeval = 1; } readtok (); val2 = expland (); if (set_noeval) noeval--; val1 = val1 || val2; lasttok = LOR; } return (val1); } /* Logical AND. */ static long expland () { register long val1, val2; int set_noeval; val1 = expbor (); while (curtok == LAND) { set_noeval = 0; if (val1 == 0) { set_noeval = 1; noeval++; } readtok (); val2 = expbor (); if (set_noeval) noeval--; val1 = val1 && val2; lasttok = LAND; } return (val1); } /* Bitwise OR. */ static long expbor () { register long val1, val2; val1 = expbxor (); while (curtok == BOR) { readtok (); val2 = expbxor (); val1 = val1 | val2; } return (val1); } /* Bitwise XOR. */ static long expbxor () { register long val1, val2; val1 = expband (); while (curtok == BXOR) { readtok (); val2 = expband (); val1 = val1 ^ val2; } return (val1); } /* Bitwise AND. */ static long expband () { register long val1, val2; val1 = exp5 (); while (curtok == BAND) { readtok (); val2 = exp5 (); val1 = val1 & val2; } return (val1); } static long exp5 () { register long val1, val2; val1 = exp4 (); while ((curtok == EQEQ) || (curtok == NEQ)) { int op = curtok; readtok (); val2 = exp4 (); if (op == EQEQ) val1 = (val1 == val2); else if (op == NEQ) val1 = (val1 != val2); } return (val1); } static long exp4 () { register long val1, val2; val1 = expshift (); while ((curtok == LEQ) || (curtok == GEQ) || (curtok == LT) || (curtok == GT)) { int op = curtok; readtok (); val2 = expshift (); if (op == LEQ) val1 = val1 <= val2; else if (op == GEQ) val1 = val1 >= val2; else if (op == LT) val1 = val1 < val2; else /* (op == GT) */ val1 = val1 > val2; } return (val1); } /* Left and right shifts. */ static long expshift () { register long val1, val2; val1 = exp3 (); while ((curtok == LSH) || (curtok == RSH)) { int op = curtok; readtok (); val2 = exp3 (); if (op == LSH) val1 = val1 << val2; else val1 = val1 >> val2; } return (val1); } static long exp3 () { register long val1, val2; val1 = exp2 (); while ((curtok == PLUS) || (curtok == MINUS)) { int op = curtok; readtok (); val2 = exp2 (); if (op == PLUS) val1 += val2; else if (op == MINUS) val1 -= val2; } return (val1); } static long exp2 () { register long val1, val2; val1 = exppower (); while ((curtok == MUL) || (curtok == DIV) || (curtok == MOD)) { int op = curtok; readtok (); val2 = exppower (); if (((op == DIV) || (op == MOD)) && (val2 == 0)) evalerror ("division by 0"); if (op == MUL) val1 *= val2; else if (op == DIV) val1 /= val2; else if (op == MOD) val1 %= val2; } return (val1); } static long exppower () { register long val1, val2, c; val1 = exp1 (); if (curtok == POWER) { readtok (); val2 = exp1 (); if (val2 == 0) return (1); if (val2 < 0) evalerror ("exponent less than 0"); for (c = 1; val2--; c *= val1) ; val1 = c; } return (val1); } static long exp1 () { register long val; if (curtok == NOT) { readtok (); val = !exp1 (); } else if (curtok == BNOT) { readtok (); val = ~exp1 (); } else val = exp0 (); return (val); } static long exp0 () { register long val = 0, v2; char *vincdec; int stok; /* XXX - might need additional logic here to decide whether or not pre-increment or pre-decrement is legal at this point. */ if (curtok == PREINC || curtok == PREDEC) { stok = lasttok = curtok; readtok (); if (curtok != STR) /* readtok() catches this */ evalerror ("identifier expected after pre-increment or pre-decrement"); v2 = tokval + ((stok == PREINC) ? 1 : -1); vincdec = itos (v2); if (noeval == 0) (void)bind_int_variable (tokstr, vincdec); free (vincdec); val = v2; curtok = NUM; /* make sure --x=7 is flagged as an error */ readtok (); } else if (curtok == MINUS) { readtok (); val = - exp0 (); } else if (curtok == PLUS) { readtok (); val = exp0 (); } else if (curtok == LPAR) { readtok (); val = EXP_HIGHEST (); if (curtok != RPAR) evalerror ("missing `)'"); /* Skip over closing paren. */ readtok (); } else if ((curtok == NUM) || (curtok == STR)) { val = tokval; if (curtok == STR && (*tp == '+' || *tp == '-') && tp[1] == *tp && (tp[2] == '\0' || (ISALNUM ((unsigned char)tp[2]) == 0))) { /* post-increment or post-decrement */ v2 = val + ((*tp == '+') ? 1 : -1); vincdec = itos (v2); if (noeval == 0) (void)bind_int_variable (tokstr, vincdec); free (vincdec); tp += 2; curtok = NUM; /* make sure x++=7 is flagged as an error */ } readtok (); } else evalerror ("syntax error: operand expected"); return (val); } /* Lexical analyzer/token reader for the expression evaluator. Reads the next token and puts its value into curtok, while advancing past it. Updates value of tp. May also set tokval (for number) or tokstr (for string). */ static void readtok () { register char *cp; register unsigned char c, c1; register int e; /* Skip leading whitespace. */ cp = tp; c = e = 0; while (cp && (c = *cp) && (cr_whitespace (c))) cp++; if (c) cp++; lasttp = tp = cp - 1; if (c == '\0') { lasttok = curtok; curtok = 0; tp = cp; return; } if (legal_variable_starter (c)) { /* variable names not preceded with a dollar sign are shell variables. */ char *value, *savecp; EXPR_CONTEXT ec; int peektok; while (legal_variable_char (c)) c = *cp++; c = *--cp; #if defined (ARRAY_VARS) if (c == '[') { e = skipsubscript (cp, 0); if (cp[e] == ']') { cp += e + 1; c = *cp; e = ']'; } else evalerror ("bad array subscript"); } #endif /* ARRAY_VARS */ *cp = '\0'; FREE (tokstr); tokstr = savestring (tp); *cp = c; SAVETOK (&ec); tokstr = (char *)NULL; /* keep it from being freed */ tp = savecp = cp; noeval = 1; readtok (); peektok = curtok; if (peektok == STR) /* free new tokstr before old one is restored */ FREE (tokstr); RESTORETOK (&ec); cp = savecp; /* The tests for PREINC and PREDEC aren't strictly correct, but they preserve old behavior if a construct like --x=9 is given. */ if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ) { #if defined (ARRAY_VARS) value = (e == ']') ? get_array_value (tokstr, 0) : get_string_value (tokstr); #else value = get_string_value (tokstr); #endif tokval = (value && *value) ? subexpr (value) : 0; #if defined (ARRAY_VARS) if (e == ']') FREE (value); /* get_array_value returns newly-allocated memory */ #endif } else tokval = 0; lasttok = curtok; curtok = STR; } else if (DIGIT(c)) { while (ISALNUM (c) || c == '#' || c == '@' || c == '_') c = *cp++; c = *--cp; *cp = '\0'; tokval = strlong (tp); *cp = c; lasttok = curtok; curtok = NUM; } else { c1 = *cp++; if ((c == EQ) && (c1 == EQ)) c = EQEQ; else if ((c == NOT) && (c1 == EQ)) c = NEQ; else if ((c == GT) && (c1 == EQ)) c = GEQ; else if ((c == LT) && (c1 == EQ)) c = LEQ; else if ((c == LT) && (c1 == LT)) { if (*cp == '=') /* a <<= b */ { assigntok = LSH; c = OP_ASSIGN; cp++; } else c = LSH; } else if ((c == GT) && (c1 == GT)) { if (*cp == '=') { assigntok = RSH; /* a >>= b */ c = OP_ASSIGN; cp++; } else c = RSH; } else if ((c == BAND) && (c1 == BAND)) c = LAND; else if ((c == BOR) && (c1 == BOR)) c = LOR; else if ((c == '*') && (c1 == '*')) c = POWER; else if ((c == '-') && (c1 == '-') && legal_variable_starter ((unsigned char)*cp)) c = PREDEC; else if ((c == '+') && (c1 == '+') && legal_variable_starter ((unsigned char)*cp)) c = PREINC; else if (c1 == EQ && member (c, "*/%+-&^|")) { assigntok = c; /* a OP= b */ c = OP_ASSIGN; } else cp--; /* `unget' the character */ lasttok = curtok; curtok = c; } tp = cp; } static void evalerror (msg) char *msg; { char *name, *t; name = this_command_name; for (t = expression; whitespace (*t); t++) ; internal_error ("%s%s%s: %s (error token is \"%s\")", name ? name : "", name ? ": " : "", t, msg, (lasttp && *lasttp) ? lasttp : ""); longjmp (evalbuf, 1); } /* Convert a string to a long integer, with an arbitrary base. 0nnn -> base 8 0[Xx]nn -> base 16 Anything else: [base#]number (this is implemented to match ksh93) Base may be >=2 and <=64. If base is <= 36, the numbers are drawn from [0-9][a-zA-Z], and lowercase and uppercase letters may be used interchangably. If base is > 36 and <= 64, the numbers are drawn from [0-9][a-z][A-Z]_@ (a = 10, z = 35, A = 36, Z = 61, _ = 62, @ = 63 -- you get the picture). */ static long strlong (num) char *num; { register char *s; register unsigned char c; int base, foundbase; long val; s = num; base = 10; foundbase = 0; if (*s == '0') { s++; if (*s == '\0') return 0; /* Base 16? */ if (*s == 'x' || *s == 'X') { base = 16; s++; } else base = 8; foundbase++; } val = 0; for (c = *s++; c; c = *s++) { if (c == '#') { if (foundbase) evalerror ("bad number"); /* Illegal base specifications raise an evaluation error. */ if (val < 2 || val > 64) evalerror ("illegal arithmetic base"); base = val; val = 0; foundbase++; } else if (ISALNUM(c) || (c == '_') || (c == '@')) { if (DIGIT(c)) c = TODIGIT(c); else if (c >= 'a' && c <= 'z') c -= 'a' - 10; else if (c >= 'A' && c <= 'Z') c -= 'A' - ((base <= 36) ? 10 : 36); else if (c == '@') c = 62; else if (c == '_') c = 63; if (c >= base) evalerror ("value too great for base"); val = (val * base) + c; } else break; } return (val); } #if defined (EXPR_TEST) void * xmalloc (n) int n; { return (malloc (n)); } void * xrealloc (s, n) char *s; int n; { return (realloc (s, n)); } SHELL_VAR *find_variable () { return 0;} SHELL_VAR *bind_variable () { return 0; } char *get_string_value () { return 0; } procenv_t top_level; main (argc, argv) int argc; char **argv; { register int i; long v; int expok; if (setjmp (top_level)) exit (0); for (i = 1; i < argc; i++) { v = evalexp (argv[i], &expok); if (expok == 0) fprintf (stderr, "%s: expression error\n", argv[i]); else printf ("'%s' -> %ld\n", argv[i], v); } exit (0); } int builtin_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "expr: "); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); return 0; } char * itos (n) long n; { return ("42"); } #endif /* EXPR_TEST */ /* findcmd.c -- Functions to search for commands by name. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include "chartypes.h" #include "bashtypes.h" #ifndef _MINIX # include #endif #include "filecntl.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include #endif #include "bashansi.h" #include "memalloc.h" #include "shell.h" #include "flags.h" #include "hashlib.h" #include "pathexp.h" #include "hashcmd.h" #include "findcmd.h" /* matching prototypes and declarations */ extern int posixly_correct; /* Static functions defined and used in this file. */ static char *_find_user_command_internal __P((const char *, int)); static char *find_user_command_internal __P((const char *, int)); static char *find_user_command_in_path __P((const char *, char *, int)); static char *find_in_path_element __P((const char *, char *, int, int, struct stat *)); static char *find_absolute_program __P((const char *, int)); static char *get_next_path_element __P((char *, int *)); /* The file name which we would try to execute, except that it isn't possible to execute it. This is the first file that matches the name that we are looking for while we are searching $PATH for a suitable one to execute. If we cannot find a suitable executable file, then we use this one. */ static char *file_to_lose_on; /* Non-zero if we should stat every command found in the hash table to make sure it still exists. */ int check_hashed_filenames; /* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command () encounters a `.' as the directory pathname while scanning the list of possible pathnames; i.e., if `.' comes before the directory containing the file of interest. */ int dot_found_in_search = 0; #define u_mode_bits(x) (((x) & 0000700) >> 6) #define g_mode_bits(x) (((x) & 0000070) >> 3) #define o_mode_bits(x) (((x) & 0000007) >> 0) #define X_BIT(x) ((x) & 1) /* Return some flags based on information about this file. The EXISTS bit is non-zero if the file is found. The EXECABLE bit is non-zero the file is executble. Zero is returned if the file is not found. */ int file_status (name) const char *name; { struct stat finfo; /* Determine whether this file exists or not. */ if (stat (name, &finfo) < 0) return (0); /* If the file is a directory, then it is not "executable" in the sense of the shell. */ if (S_ISDIR (finfo.st_mode)) return (FS_EXISTS|FS_DIRECTORY); #if defined (AFS) /* We have to use access(2) to determine access because AFS does not support Unix file system semantics. This may produce wrong answers for non-AFS files when ruid != euid. I hate AFS. */ if (access (name, X_OK) == 0) return (FS_EXISTS | FS_EXECABLE); else return (FS_EXISTS); #else /* !AFS */ /* Find out if the file is actually executable. By definition, the only other criteria is that the file has an execute bit set that we can use. */ /* Root only requires execute permission for any of owner, group or others to be able to exec a file. */ if (current_user.euid == (uid_t)0) { int bits; bits = (u_mode_bits (finfo.st_mode) | g_mode_bits (finfo.st_mode) | o_mode_bits (finfo.st_mode)); if (X_BIT (bits)) return (FS_EXISTS | FS_EXECABLE); } /* If we are the owner of the file, the owner execute bit applies. */ if (current_user.euid == finfo.st_uid && X_BIT (u_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); /* If we are in the owning group, the group permissions apply. */ if (group_member (finfo.st_gid) && X_BIT (g_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); /* If `others' have execute permission to the file, then so do we, since we are also `others'. */ if (X_BIT (o_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); return (FS_EXISTS); #endif /* !AFS */ } /* Return non-zero if FILE exists and is executable. Note that this function is the definition of what an executable file is; do not change this unless YOU know what an executable file is. */ int executable_file (file) const char *file; { int s; s = file_status (file); return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0)); } int is_directory (file) const char *file; { return (file_status (file) & FS_DIRECTORY); } int executable_or_directory (file) const char *file; { int s; s = file_status (file); return ((s & FS_EXECABLE) || (s & FS_DIRECTORY)); } /* Locate the executable file referenced by NAME, searching along the contents of the shell PATH variable. Return a new string which is the full pathname to the file, or NULL if the file couldn't be found. If a file is found that isn't executable, and that is the only match, then return that. */ char * find_user_command (name) const char *name; { return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS)); } /* Locate the file referenced by NAME, searching along the contents of the shell PATH variable. Return a new string which is the full pathname to the file, or NULL if the file couldn't be found. This returns the first file found. */ char * find_path_file (name) const char *name; { return (find_user_command_internal (name, FS_EXISTS)); } static char * _find_user_command_internal (name, flags) const char *name; int flags; { char *path_list, *cmd; SHELL_VAR *var; /* Search for the value of PATH in both the temporary environment, and in the regular list of variables. */ if (var = find_variable_internal ("PATH", 1)) /* XXX could be array? */ path_list = value_cell (var); else path_list = (char *)NULL; if (path_list == 0 || *path_list == '\0') return (savestring (name)); cmd = find_user_command_in_path (name, path_list, flags); if (var && tempvar_p (var)) dispose_variable (var); return (cmd); } static char * find_user_command_internal (name, flags) const char *name; int flags; { #ifdef __WIN32__ char *res, *dotexe; dotexe = (char *)xmalloc (strlen (name) + 5); strcpy (dotexe, name); strcat (dotexe, ".exe"); res = _find_user_command_internal (dotexe, flags); free (dotexe); if (res == 0) res = _find_user_command_internal (name, flags); return res; #else return (_find_user_command_internal (name, flags)); #endif } /* Return the next element from PATH_LIST, a colon separated list of paths. PATH_INDEX_POINTER is the address of an index into PATH_LIST; the index is modified by this function. Return the next element of PATH_LIST or NULL if there are no more. */ static char * get_next_path_element (path_list, path_index_pointer) char *path_list; int *path_index_pointer; { char *path; path = extract_colon_unit (path_list, path_index_pointer); if (path == 0) return (path); if (*path == '\0') { free (path); path = savestring ("."); } return (path); } /* Look for PATHNAME in $PATH. Returns either the hashed command corresponding to PATHNAME or the first instance of PATHNAME found in $PATH. Returns a newly-allocated string. */ char * search_for_command (pathname) const char *pathname; { char *hashed_file, *command; int temp_path, st; SHELL_VAR *path; hashed_file = command = (char *)NULL; /* If PATH is in the temporary environment for this command, don't use the hash table to search for the full pathname. */ path = find_tempenv_variable ("PATH"); temp_path = path != 0; /* Don't waste time trying to find hashed data for a pathname that is already completely specified or if we're using a command- specific value for PATH. */ if (path == 0 && absolute_program (pathname) == 0) hashed_file = find_hashed_filename (pathname); /* If a command found in the hash table no longer exists, we need to look for it in $PATH. Thank you Posix.2. This forces us to stat every command found in the hash table. */ if (hashed_file && (posixly_correct || check_hashed_filenames)) { st = file_status (hashed_file); if ((st ^ (FS_EXISTS | FS_EXECABLE)) != 0) { remove_hashed_filename (pathname); free (hashed_file); hashed_file = (char *)NULL; } } if (hashed_file) command = hashed_file; else if (absolute_program (pathname)) /* A command containing a slash is not looked up in PATH or saved in the hash table. */ command = savestring (pathname); else { /* If $PATH is in the temporary environment, we've already retrieved it, so don't bother trying again. */ if (temp_path) { command = find_user_command_in_path (pathname, value_cell (path), FS_EXEC_PREFERRED|FS_NODIRS); if (tempvar_p (path)) dispose_variable (path); } else command = find_user_command (pathname); if (command && hashing_enabled && temp_path == 0) remember_filename ((char *)pathname, command, dot_found_in_search, 1); /* XXX fix const later */ } return (command); } char * user_command_matches (name, flags, state) const char *name; int flags, state; { register int i; int path_index, name_len; char *path_list, *path_element, *match; struct stat dotinfo; static char **match_list = NULL; static int match_list_size = 0; static int match_index = 0; if (state == 0) { /* Create the list of matches. */ if (match_list == 0) { match_list_size = 5; match_list = alloc_array (match_list_size); } /* Clear out the old match list. */ for (i = 0; i < match_list_size; i++) match_list[i] = 0; /* We haven't found any files yet. */ match_index = 0; if (absolute_program (name)) { match_list[0] = find_absolute_program (name, flags); match_list[1] = (char *)NULL; path_list = (char *)NULL; } else { name_len = strlen (name); file_to_lose_on = (char *)NULL; dot_found_in_search = 0; stat (".", &dotinfo); path_list = get_string_value ("PATH"); path_index = 0; } while (path_list && path_list[path_index]) { path_element = get_next_path_element (path_list, &path_index); if (path_element == 0) break; match = find_in_path_element (name, path_element, flags, name_len, &dotinfo); free (path_element); if (match == 0) continue; if (match_index + 1 == match_list_size) { match_list_size += 10; match_list = (char **)xrealloc (match_list, (match_list_size + 1) * sizeof (char *)); } match_list[match_index++] = match; match_list[match_index] = (char *)NULL; FREE (file_to_lose_on); file_to_lose_on = (char *)NULL; } /* We haven't returned any strings yet. */ match_index = 0; } match = match_list[match_index]; if (match) match_index++; return (match); } static char * find_absolute_program (name, flags) const char *name; int flags; { int st; st = file_status (name); /* If the file doesn't exist, quit now. */ if ((st & FS_EXISTS) == 0) return ((char *)NULL); /* If we only care about whether the file exists or not, return this filename. Otherwise, maybe we care about whether this file is executable. If it is, and that is what we want, return it. */ if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE))) return (savestring (name)); return (NULL); } static char * find_in_path_element (name, path, flags, name_len, dotinfop) const char *name; char *path; int flags, name_len; struct stat *dotinfop; { int status; char *full_path, *xpath; xpath = (*path == '~') ? bash_tilde_expand (path) : path; /* Remember the location of "." in the path, in all its forms (as long as they begin with a `.', e.g. `./.') */ if (dot_found_in_search == 0 && *xpath == '.') dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL); full_path = sh_makepath (xpath, name, 0); status = file_status (full_path); if (xpath != path) free (xpath); if ((status & FS_EXISTS) == 0) { free (full_path); return ((char *)NULL); } /* The file exists. If the caller simply wants the first file, here it is. */ if (flags & FS_EXISTS) return (full_path); /* If the file is executable, then it satisfies the cases of EXEC_ONLY and EXEC_PREFERRED. Return this file unconditionally. */ if ((status & FS_EXECABLE) && (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0))) { FREE (file_to_lose_on); file_to_lose_on = (char *)NULL; return (full_path); } /* The file is not executable, but it does exist. If we prefer an executable, then remember this one if it is the first one we have found. */ if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0) file_to_lose_on = savestring (full_path); /* If we want only executable files, or we don't want directories and this file is a directory, fail. */ if ((flags & FS_EXEC_ONLY) || (flags & FS_EXEC_PREFERRED) || ((flags & FS_NODIRS) && (status & FS_DIRECTORY))) { free (full_path); return ((char *)NULL); } else return (full_path); } /* This does the dirty work for find_user_command_internal () and user_command_matches (). NAME is the name of the file to search for. PATH_LIST is a colon separated list of directories to search. FLAGS contains bit fields which control the files which are eligible. Some values are: FS_EXEC_ONLY: The file must be an executable to be found. FS_EXEC_PREFERRED: If we can't find an executable, then the the first file matching NAME will do. FS_EXISTS: The first file found will do. FS_NODIRS: Don't find any directories. */ static char * find_user_command_in_path (name, path_list, flags) const char *name; char *path_list; int flags; { char *full_path, *path; int path_index, name_len; struct stat dotinfo; /* We haven't started looking, so we certainly haven't seen a `.' as the directory path yet. */ dot_found_in_search = 0; if (absolute_program (name)) { full_path = find_absolute_program (name, flags); return (full_path); } if (path_list == 0 || *path_list == '\0') return (savestring (name)); /* XXX */ file_to_lose_on = (char *)NULL; name_len = strlen (name); stat (".", &dotinfo); path_index = 0; while (path_list[path_index]) { /* Allow the user to interrupt out of a lengthy path search. */ QUIT; path = get_next_path_element (path_list, &path_index); if (path == 0) break; /* Side effects: sets dot_found_in_search, possibly sets file_to_lose_on. */ full_path = find_in_path_element (name, path, flags, name_len, &dotinfo); free (path); /* This should really be in find_in_path_element, but there isn't the right combination of flags. */ if (full_path && is_directory (full_path)) { free (full_path); continue; } if (full_path) { FREE (file_to_lose_on); return (full_path); } } /* We didn't find exactly what the user was looking for. Return the contents of FILE_TO_LOSE_ON which is NULL when the search required an executable, or non-NULL if a file was found and the search would accept a non-executable as a last resort. If the caller specified FS_NODIRS, and file_to_lose_on is a directory, return NULL. */ if (file_to_lose_on && (flags & FS_NODIRS) && is_directory (file_to_lose_on)) { free (file_to_lose_on); file_to_lose_on = (char *)NULL; } return (file_to_lose_on); } /* flags.c -- Everything about flags except the `set' command. That is in builtins.c */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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. */ /* Flags hacking. */ #include "config.h" #if defined (HAVE_UNISTD_H) # include #endif #include "shell.h" #include "flags.h" #if defined (BANG_HISTORY) # include "bashhist.h" #endif #if defined (JOB_CONTROL) extern int set_job_control __P((int)); #endif #if defined (RESTRICTED_SHELL) extern char *shell_name; #endif /* -c, -s invocation options -- not really flags, but they show up in $- */ extern int want_pending_command, read_from_stdin; /* **************************************************************** */ /* */ /* The Standard Sh Flags. */ /* */ /* **************************************************************** */ /* Non-zero means automatically mark variables which are modified or created as auto export variables. */ int mark_modified_vars = 0; /* Non-zero causes asynchronous job notification. Otherwise, job state notification only takes place just before a primary prompt is printed. */ int asynchronous_notification = 0; /* Non-zero means exit immediately if a command exits with a non-zero exit status. */ int exit_immediately_on_error = 0; /* Non-zero means disable filename globbing. */ int disallow_filename_globbing = 0; /* Non-zero means that all keyword arguments are placed into the environment for a command, not just those that appear on the line before the command name. */ int place_keywords_in_env = 0; /* Non-zero means read commands, but don't execute them. This is useful for debugging shell scripts that should do something hairy and possibly destructive. */ int read_but_dont_execute = 0; /* Non-zero means end of file is after one command. */ int just_one_command = 0; /* Non-zero means don't overwrite existing files while doing redirections. */ int noclobber = 0; /* Non-zero means trying to get the value of $i where $i is undefined causes an error, instead of a null substitution. */ int unbound_vars_is_error = 0; /* Non-zero means type out input lines after you read them. */ int echo_input_at_read = 0; /* Non-zero means type out the command definition after reading, but before executing. */ int echo_command_at_execute = 0; /* Non-zero means turn on the job control features. */ int jobs_m_flag = 0; /* Non-zero means this shell is interactive, even if running under a pipe. */ int forced_interactive = 0; /* By default, follow the symbolic links as if they were real directories while hacking the `cd' command. This means that `cd ..' moves up in the string of symbolic links that make up the current directory, instead of the absolute directory. The shell variable `nolinks' also controls this flag. */ int no_symbolic_links = 0; /* **************************************************************** */ /* */ /* Non-Standard Flags Follow Here. */ /* */ /* **************************************************************** */ #if 0 /* Non-zero means do lexical scoping in the body of a FOR command. */ int lexical_scoping = 0; #endif /* Non-zero means no such thing as invisible variables. */ int no_invisible_vars = 0; /* Non-zero means look up and remember command names in a hash table, */ int hashing_enabled = 1; #if defined (BANG_HISTORY) /* Non-zero means that we are doing history expansion. The default. This means !22 gets the 22nd line of history. */ int history_expansion = 1; #endif /* BANG_HISTORY */ /* Non-zero means that we allow comments to appear in interactive commands. */ int interactive_comments = 1; #if defined (RESTRICTED_SHELL) /* Non-zero means that this shell is `restricted'. A restricted shell disallows: changing directories, command or path names containing `/', unsetting or resetting the values of $PATH and $SHELL, and any type of output redirection. */ int restricted = 0; /* currently restricted */ int restricted_shell = 0; /* shell was started in restricted mode. */ #endif /* RESTRICTED_SHELL */ /* Non-zero means that this shell is running in `privileged' mode. This is required if the shell is to run setuid. If the `-p' option is not supplied at startup, and the real and effective uids or gids differ, disable_priv_mode is called to relinquish setuid status. */ int privileged_mode = 0; #if defined (BRACE_EXPANSION) /* Zero means to disable brace expansion: foo{a,b} -> fooa foob */ int brace_expansion = 1; #endif /* **************************************************************** */ /* */ /* The Flags ALIST. */ /* */ /* **************************************************************** */ struct flags_alist shell_flags[] = { /* Standard sh flags. */ { 'a', &mark_modified_vars }, #if defined (JOB_CONTROL) { 'b', &asynchronous_notification }, #endif /* JOB_CONTROL */ { 'e', &exit_immediately_on_error }, { 'f', &disallow_filename_globbing }, { 'h', &hashing_enabled }, { 'i', &forced_interactive }, { 'k', &place_keywords_in_env }, #if defined (JOB_CONTROL) { 'm', &jobs_m_flag }, #endif /* JOB_CONTROL */ { 'n', &read_but_dont_execute }, { 'p', &privileged_mode }, #if defined (RESTRICTED_SHELL) { 'r', &restricted }, #endif /* RESTRICTED_SHELL */ { 't', &just_one_command }, { 'u', &unbound_vars_is_error }, { 'v', &echo_input_at_read }, { 'x', &echo_command_at_execute }, { 'C', &noclobber }, /* New flags that control non-standard things. */ #if 0 { 'l', &lexical_scoping }, #endif { 'I', &no_invisible_vars }, { 'P', &no_symbolic_links }, #if defined (BRACE_EXPANSION) { 'B', &brace_expansion }, #endif #if defined (BANG_HISTORY) { 'H', &history_expansion }, #endif /* BANG_HISTORY */ {0, (int *)NULL} }; #define NUM_SHELL_FLAGS (sizeof (shell_flags) / sizeof (struct flags_alist)) int * find_flag (name) int name; { int i; for (i = 0; shell_flags[i].name; i++) { if (shell_flags[i].name == name) return (shell_flags[i].value); } return (FLAG_UNKNOWN); } /* Change the state of a flag, and return it's original value, or return FLAG_ERROR if there is no flag FLAG. ON_OR_OFF must be either FLAG_ON or FLAG_OFF. */ int change_flag (flag, on_or_off) int flag; int on_or_off; { int *value, old_value; #if defined (RESTRICTED_SHELL) /* Don't allow "set +r" in a shell which is `restricted'. */ if (restricted && flag == 'r' && on_or_off == FLAG_OFF) return (FLAG_ERROR); #endif /* RESTRICTED_SHELL */ value = find_flag (flag); if ((value == (int *)FLAG_UNKNOWN) || (on_or_off != FLAG_ON && on_or_off != FLAG_OFF)) return (FLAG_ERROR); old_value = *value; *value = (on_or_off == FLAG_ON) ? 1 : 0; /* Special cases for a few flags. */ switch (flag) { #if defined (BANG_HISTORY) case 'H': if (on_or_off == FLAG_ON) bash_initialize_history (); break; #endif #if defined (JOB_CONTROL) case 'm': set_job_control (on_or_off == FLAG_ON); break; #endif /* JOB_CONTROL */ case 'n': if (interactive_shell) read_but_dont_execute = 0; break; case 'p': if (on_or_off == FLAG_OFF) disable_priv_mode (); break; #if defined (RESTRICTED_SHELL) case 'r': if (on_or_off == FLAG_ON) maybe_make_restricted (shell_name); break; #endif } return (old_value); } /* Return a string which is the names of all the currently set shell flags. */ char * which_set_flags () { char *temp; int i, string_index; temp = (char *)xmalloc (1 + NUM_SHELL_FLAGS + read_from_stdin + want_pending_command); for (i = string_index = 0; shell_flags[i].name; i++) if (*(shell_flags[i].value)) temp[string_index++] = shell_flags[i].name; if (want_pending_command) temp[string_index++] = 'c'; if (read_from_stdin) temp[string_index++] = 's'; temp[string_index] = '\0'; return (temp); } void reset_shell_flags () { mark_modified_vars = exit_immediately_on_error = disallow_filename_globbing = 0; place_keywords_in_env = read_but_dont_execute = just_one_command = 0; noclobber = unbound_vars_is_error = echo_input_at_read = 0; echo_command_at_execute = jobs_m_flag = forced_interactive = 0; no_symbolic_links = no_invisible_vars = privileged_mode = 0; hashing_enabled = interactive_comments = 1; #if defined (JOB_CONTROL) asynchronous_notification = 0; #endif #if defined (BANG_HISTORY) history_expansion = 1; #endif #if defined (BRACE_EXPANSION) brace_expansion = 1; #endif #if defined (RESTRICTED_SHELL) restricted = 0; #endif } /* general.c -- Stuff that is used by all files. */ /* Copyright (C) 1987-1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 "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; /* A standard error message to use when getcwd() returns NULL. */ char *bash_getcwd_errstr = "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; long *result; { long value; char *ep; if (result) *result = 0; errno = 0; value = strtol (string, &ep, 10); if (errno) return 0; /* errno is set on overflow or underflow */ /* Skip any trailing whitespace, since strtol 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); } /* **************************************************************** */ /* */ /* 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; } /* 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 > 256) nfds = 256; } else nfds = maxfd; for (nfds--; check_new && nfds > 3; nfds--) if (fcntl (nfds, F_GETFD, &ignore) == -1) break; if (nfds && fd != nfds && (script_fd = dup2 (fd, nfds)) != -1) { if (check_new == 0 || fd != fileno (stderr)) /* don't close stderr */ close (fd); return (script_fd); } 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 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)) result = savestring (string); 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 *)strchr (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) : 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 /* Reserved for post-bash-2.05a */ 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 = alloc_array (3); bash_tilde_prefixes[0] = "=~"; bash_tilde_prefixes[1] = ":~"; bash_tilde_prefixes[2] = (char *)NULL; tilde_additional_prefixes = bash_tilde_prefixes; bash_tilde_suffixes = alloc_array (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. */ char * bash_tilde_expand (s) const char *s; { int old_immed, r; char *ret; old_immed = interrupt_immediately; interrupt_immediately = 1; 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 #if defined (HAVE_SYSCONF) && defined (_SC_NGROUPS_MAX) # define getmaxgroups() sysconf(_SC_NGROUPS_MAX) #else # if defined (NGROUPS_MAX) # define getmaxgroups() NGROUPS_MAX # else /* !NGROUPS_MAX */ # if defined (NGROUPS) # define getmaxgroups() NGROUPS # else /* !NGROUPS */ # define getmaxgroups() 64 # endif /* !NGROUPS */ # endif /* !NGROUPS_MAX */ #endif /* !HAVE_SYSCONF || !SC_NGROUPS_MAX */ 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; char *nbuf; 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 = alloc_array (ngroups); for (i = 0; i < ngroups; i++) { nbuf = itos (group_array[i]); group_vector[i] = nbuf; } 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; } /* hashcmd.c - functions for managing a hash table mapping command names to full pathnames. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 #include "bashtypes.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include #endif #include "bashansi.h" #include "shell.h" #include "findcmd.h" #include "hashcmd.h" extern int hashing_enabled; static int hashing_initialized = 0; HASH_TABLE *hashed_filenames; void initialize_filename_hashing () { if (hashing_initialized == 0) { hashed_filenames = make_hash_table (FILENAME_HASH_BUCKETS); hashing_initialized = 1; } } static void free_filename_data (data) PTR_T data; { free (((PATH_DATA *)data)->path); free (data); } void flush_hashed_filenames () { if (hashed_filenames) flush_hash_table (hashed_filenames, free_filename_data); } /* Remove FILENAME from the table of hashed commands. */ void remove_hashed_filename (filename) const char *filename; { register BUCKET_CONTENTS *item; if (hashing_enabled == 0 || hashed_filenames == 0) return; item = remove_hash_item (filename, hashed_filenames); if (item) { if (item->data) free_filename_data (item->data); free (item->key); free (item); } } /* Place FILENAME (key) and FULL_PATH (data->path) into the hash table. CHECK_DOT if non-null is for future calls to find_hashed_filename (); it means that this file was found in a directory in $PATH that is not an absolute pathname. FOUND is the initial value for times_found. */ void remember_filename (filename, full_path, check_dot, found) char *filename, *full_path; int check_dot, found; { register BUCKET_CONTENTS *item; if (hashing_enabled == 0) return; if (hashed_filenames == 0 || hashing_initialized == 0) initialize_filename_hashing (); item = add_hash_item (filename, hashed_filenames); if (item->data) free (pathdata(item)->path); else { item->key = savestring (filename); item->data = (char *)xmalloc (sizeof (PATH_DATA)); } pathdata(item)->path = savestring (full_path); pathdata(item)->flags = 0; if (check_dot) pathdata(item)->flags |= HASH_CHKDOT; if (*full_path != '/') pathdata(item)->flags |= HASH_RELPATH; item->times_found = found; } /* Return the full pathname that FILENAME hashes to. If FILENAME is hashed, but (data->flags & HASH_CHKDOT) is non-zero, check ./FILENAME and return that if it is executable. This always returns a newly-allocated string; the caller is responsible for freeing it. */ char * find_hashed_filename (filename) const char *filename; { register BUCKET_CONTENTS *item; char *path, *dotted_filename, *tail; int same; if (hashing_enabled == 0 || hashed_filenames == 0) return ((char *)NULL); item = find_hash_item (filename, hashed_filenames); if (item == NULL) return ((char *)NULL); /* If this filename is hashed, but `.' comes before it in the path, see if ./filename is executable. If the hashed value is not an absolute pathname, see if ./`hashed-value' exists. */ path = pathdata(item)->path; if (pathdata(item)->flags & (HASH_CHKDOT|HASH_RELPATH)) { tail = (pathdata(item)->flags & HASH_RELPATH) ? path : (char *)filename; /* XXX - fix const later */ /* If the pathname does not start with a `./', add a `./' to it. */ if (tail[0] != '.' || tail[1] != '/') { dotted_filename = (char *)xmalloc (3 + strlen (tail)); dotted_filename[0] = '.'; dotted_filename[1] = '/'; strcpy (dotted_filename + 2, tail); } else dotted_filename = savestring (tail); if (executable_file (dotted_filename)) return (dotted_filename); free (dotted_filename); #if 0 if (pathdata(item)->flags & HASH_RELPATH) return ((char *)NULL); #endif /* Watch out. If this file was hashed to "./filename", and "./filename" is not executable, then return NULL. */ /* Since we already know "./filename" is not executable, what we're really interested in is whether or not the `path' portion of the hashed filename is equivalent to the current directory, but only if it starts with a `.'. (This catches ./. and so on.) same_file () tests general Unix file equivalence -- same device and inode. */ if (*path == '.') { same = 0; tail = (char *)strrchr (path, '/'); if (tail) { *tail = '\0'; same = same_file (".", path, (struct stat *)NULL, (struct stat *)NULL); *tail = '/'; } return same ? (char *)NULL : savestring (path); } } return (savestring (path)); } /* hashlib.c -- functions to manage and access hash tables for bash. */ /* Copyright (C) 1987, 1989, 1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 #include "bashansi.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include #include "shell.h" #include "hashlib.h" static void initialize_hash_table __P((HASH_TABLE *)); static BUCKET_CONTENTS *copy_bucket_array __P((BUCKET_CONTENTS *, sh_string_func_t *)); /* Zero the buckets in TABLE. */ static void initialize_hash_table (table) HASH_TABLE *table; { register int i; for (i = 0; i < table->nbuckets; i++) table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; } /* Make a new hash table with BUCKETS number of buckets. Initialize each slot in the table to NULL. */ HASH_TABLE * make_hash_table (buckets) int buckets; { HASH_TABLE *new_table; new_table = (HASH_TABLE *)xmalloc (sizeof (HASH_TABLE)); if (buckets == 0) buckets = DEFAULT_HASH_BUCKETS; new_table->bucket_array = (BUCKET_CONTENTS **)xmalloc (buckets * sizeof (BUCKET_CONTENTS *)); new_table->nbuckets = buckets; new_table->nentries = 0; initialize_hash_table (new_table); return (new_table); } int hash_table_nentries (table) HASH_TABLE *table; { return (HASH_ENTRIES(table)); } static BUCKET_CONTENTS * copy_bucket_array (ba, cpdata) BUCKET_CONTENTS *ba; sh_string_func_t *cpdata; /* data copy function */ { BUCKET_CONTENTS *new_bucket, *n, *e; if (ba == 0) return ((BUCKET_CONTENTS *)0); for (n = (BUCKET_CONTENTS *)0, e = ba; e; e = e->next) { if (n == 0) { new_bucket = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); n = new_bucket; } else { n->next = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); n = n->next; } n->key = savestring (e->key); n->data = e->data ? (cpdata ? (*cpdata) (e->data) : savestring (e->data)) : (char *)NULL; n->times_found = e->times_found; n->next = (BUCKET_CONTENTS *)NULL; } return new_bucket; } HASH_TABLE * copy_hash_table (table, cpdata) HASH_TABLE *table; sh_string_func_t *cpdata; { HASH_TABLE *new_table; int i; if (table == 0) return ((HASH_TABLE *)NULL); new_table = make_hash_table (table->nbuckets); for (i = 0; i < table->nbuckets; i++) new_table->bucket_array[i] = copy_bucket_array (table->bucket_array[i], cpdata); new_table->nentries = table->nentries; return new_table; } /* Return the location of the bucket which should contain the data for STRING. TABLE is a pointer to a HASH_TABLE. */ #if 0 /* A possibly better distribution may be obtained by initializing i to ~0UL and using i = (i * 31) + *string++ as the step */ #define ALL_ONES (~((unsigned long) 0)) #define BITS(h, n) ((unsigned long)(h) & ~(ALL_ONES << (n))) #endif int hash_string (string, table) const char *string; HASH_TABLE *table; { register unsigned int i = 0; while (*string) i = (i << 2) + *string++; #if 0 return (BITS (i, 31) % table->nbuckets); #else /* Rely on properties of unsigned division (unsigned/int -> unsigned) and don't discard the upper 32 bits of the value, if present. */ return (i % table->nbuckets); #endif } /* Return a pointer to the hashed item, or NULL if the item can't be found. */ BUCKET_CONTENTS * find_hash_item (string, table) const char *string; HASH_TABLE *table; { BUCKET_CONTENTS *list; int which_bucket; if (table == 0) return (BUCKET_CONTENTS *)NULL; which_bucket = hash_string (string, table); for (list = table->bucket_array[which_bucket]; list; list = list->next) { if (STREQ (list->key, string)) { list->times_found++; return (list); } } return (BUCKET_CONTENTS *)NULL; } /* Remove the item specified by STRING from the hash table TABLE. The item removed is returned, so you can free its contents. If the item isn't in this table NULL is returned. */ BUCKET_CONTENTS * remove_hash_item (string, table) const char *string; HASH_TABLE *table; { int the_bucket; BUCKET_CONTENTS *prev, *temp; if (table == 0) return (BUCKET_CONTENTS *)NULL; the_bucket = hash_string (string, table); prev = (BUCKET_CONTENTS *)NULL; for (temp = table->bucket_array[the_bucket]; temp; temp = temp->next) { if (STREQ (temp->key, string)) { if (prev) prev->next = temp->next; else table->bucket_array[the_bucket] = temp->next; table->nentries--; return (temp); } prev = temp; } return ((BUCKET_CONTENTS *) NULL); } /* Create an entry for STRING, in TABLE. If the entry already exists, then return it. */ BUCKET_CONTENTS * add_hash_item (string, table) char *string; HASH_TABLE *table; { BUCKET_CONTENTS *item; int bucket; if (table == 0) table = make_hash_table (0); if ((item = find_hash_item (string, table)) == 0) { bucket = hash_string (string, table); item = table->bucket_array[bucket]; while (item && item->next) item = item->next; if (item) { item->next = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); item = item->next; } else { table->bucket_array[bucket] = (BUCKET_CONTENTS *)xmalloc (sizeof (BUCKET_CONTENTS)); item = table->bucket_array[bucket]; } item->data = (char *)NULL; item->next = (BUCKET_CONTENTS *)NULL; item->key = string; table->nentries++; item->times_found = 0; } return (item); } /* Remove and discard all entries in TABLE. If FREE_DATA is non-null, it is a function to call to dispose of a hash item's data. Otherwise, free() is called. */ void flush_hash_table (table, free_data) HASH_TABLE *table; sh_free_func_t *free_data; { int i; register BUCKET_CONTENTS *bucket, *item; if (table == 0) return; for (i = 0; i < table->nbuckets; i++) { bucket = table->bucket_array[i]; while (bucket) { item = bucket; bucket = bucket->next; if (free_data) (*free_data) (item->data); else free (item->data); free (item->key); free (item); } table->bucket_array[i] = (BUCKET_CONTENTS *)NULL; } } /* Free the hash table pointed to by TABLE. */ void dispose_hash_table (table) HASH_TABLE *table; { free (table->bucket_array); free (table); } /* No longer necessary; everything uses the macro */ #if 0 /* Return the bucket_contents list of bucket BUCKET in TABLE. If TABLE doesn't have BUCKET buckets, return NULL. */ #undef get_hash_bucket BUCKET_CONTENTS * get_hash_bucket (bucket, table) int bucket; HASH_TABLE *table; { if (table && bucket < table->nbuckets) return (table->bucket_array[bucket]); else return (BUCKET_CONTENTS *)NULL; } #endif #ifdef DEBUG void print_table_stats (table, name) HASH_TABLE *table; char *name; { register int slot, bcount; register BUCKET_CONTENTS *bc; if (name == 0) name = "unknown hash table"; fprintf (stderr, "%s: %d buckets; %d items\n", name, table->nbuckets, table->nentries); /* Print out a count of how many strings hashed to each bucket, so we can see how even the distribution is. */ for (slot = 0; slot < table->nbuckets; slot++) { bc = get_hash_bucket (slot, table); fprintf (stderr, "\tslot %3d: ", slot); for (bcount = 0; bc; bc = bc->next) bcount++; fprintf (stderr, "%d\n", bcount); } } #endif #ifdef TEST_HASHING #undef NULL #include #ifndef NULL #define NULL 0 #endif HASH_TABLE *table, *ntable; #define NBUCKETS 107 void * xmalloc (bytes) size_t bytes; { void *result = malloc (bytes); if (!result) { fprintf (stderr, "hash: out of virtual memory\n"); abort (); } return (result); } main () { char string[256]; int count = 0; BUCKET_CONTENTS *tt; table = make_hash_table (NBUCKETS); for (;;) { char *temp_string; if (fgets (string, sizeof (string), stdin) == 0) break; if (!*string) break; temp_string = savestring (string); tt = add_hash_item (temp_string, table); if (tt->times_found) { fprintf (stderr, "You have already added item `%s'\n", string); free (temp_string); } else { count++; } } print_table_stats (table, "hash test"); ntable = copy_hash_table (table, (sh_string_func_t *)NULL); flush_hash_table (table, (sh_free_func_t *)NULL); print_table_stats (ntable, "hash copy test"); exit (0); } #endif /* TEST_HASHING */ /* input.c -- functions to perform buffered input with synchronization. */ /* Copyright (C) 1992 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 "filecntl.h" #include "posixstat.h" #include #include #if defined (HAVE_UNISTD_H) # include #endif #include "bashansi.h" #include "command.h" #include "general.h" #include "input.h" #include "error.h" #include "externs.h" #if !defined (errno) extern int errno; #endif /* !errno */ /* Functions to handle reading input on systems that don't restart read(2) if a signal is received. */ static char localbuf[128]; static int local_index, local_bufused; /* Posix and USG systems do not guarantee to restart read () if it is interrupted by a signal. We do the read ourselves, and restart it if it returns EINTR. */ int getc_with_restart (stream) FILE *stream; { unsigned char uc; /* Try local buffering to reduce the number of read(2) calls. */ if (local_index == local_bufused || local_bufused == 0) { while (1) { local_bufused = read (fileno (stream), localbuf, sizeof(localbuf)); if (local_bufused > 0) break; else if (local_bufused == 0 || errno != EINTR) { local_index = 0; return EOF; } } local_index = 0; } uc = localbuf[local_index++]; return uc; } int ungetc_with_restart (c, stream) int c; FILE *stream; { if (local_index == 0 || c == EOF) return EOF; localbuf[--local_index] = c; return c; } #if defined (BUFFERED_INPUT) /* A facility similar to stdio, but input-only. */ #if defined (USING_BASH_MALLOC) # define MAX_INPUT_BUFFER_SIZE 8176 #else # define MAX_INPUT_BUFFER_SIZE 8192 #endif #if !defined (SEEK_CUR) # define SEEK_CUR 1 #endif /* !SEEK_CUR */ #ifdef max # undef max #endif #define max(a, b) (((a) > (b)) ? (a) : (b)) #ifdef min # undef min #endif #define min(a, b) ((a) > (b) ? (b) : (a)) extern int interactive_shell; int bash_input_fd_changed; /* This provides a way to map from a file descriptor to the buffer associated with that file descriptor, rather than just the other way around. This is needed so that buffers are managed properly in constructs like 3<&4. buffers[x]->b_fd == x -- that is how the correspondence is maintained. */ static BUFFERED_STREAM **buffers = (BUFFERED_STREAM **)NULL; static int nbuffers; #define ALLOCATE_BUFFERS(n) \ do { if ((n) >= nbuffers) allocate_buffers (n); } while (0) /* Make sure `buffers' has at least N elements. */ static void allocate_buffers (n) int n; { register int i, orig_nbuffers; orig_nbuffers = nbuffers; nbuffers = n + 20; buffers = (BUFFERED_STREAM **)xrealloc (buffers, nbuffers * sizeof (BUFFERED_STREAM *)); /* Zero out the new buffers. */ for (i = orig_nbuffers; i < nbuffers; i++) buffers[i] = (BUFFERED_STREAM *)NULL; } /* Construct and return a BUFFERED_STREAM corresponding to file descriptor FD, using BUFFER. */ static BUFFERED_STREAM * make_buffered_stream (fd, buffer, bufsize) int fd; char *buffer; size_t bufsize; { BUFFERED_STREAM *bp; bp = (BUFFERED_STREAM *)xmalloc (sizeof (BUFFERED_STREAM)); ALLOCATE_BUFFERS (fd); buffers[fd] = bp; bp->b_fd = fd; bp->b_buffer = buffer; bp->b_size = bufsize; bp->b_used = bp->b_inputp = bp->b_flag = 0; if (bufsize == 1) bp->b_flag |= B_UNBUFF; return (bp); } /* Allocate a new BUFFERED_STREAM, copy BP to it, and return the new copy. */ static BUFFERED_STREAM * copy_buffered_stream (bp) BUFFERED_STREAM *bp; { BUFFERED_STREAM *nbp; if (!bp) return ((BUFFERED_STREAM *)NULL); nbp = (BUFFERED_STREAM *)xmalloc (sizeof (BUFFERED_STREAM)); xbcopy ((char *)bp, (char *)nbp, sizeof (BUFFERED_STREAM)); return (nbp); } int set_bash_input_fd (fd) int fd; { if (bash_input.type == st_bstream) bash_input.location.buffered_fd = fd; else if (interactive_shell == 0) default_buffered_input = fd; return 0; } int fd_is_bash_input (fd) int fd; { if (bash_input.type == st_bstream && bash_input.location.buffered_fd == fd) return 1; else if (interactive_shell == 0 && default_buffered_input == fd) return 1; return 0; } /* Save the buffered stream corresponding to file descriptor FD (which bash is using to read input) to a buffered stream associated with NEW_FD. If NEW_FD is -1, a new file descriptor is allocated with fcntl. The new file descriptor is returned on success, -1 on error. */ int save_bash_input (fd, new_fd) int fd, new_fd; { int nfd; /* Sync the stream so we can re-read from the new file descriptor. We might be able to avoid this by copying the buffered stream verbatim to the new file descriptor. */ if (buffers[fd]) sync_buffered_stream (fd); /* Now take care of duplicating the file descriptor that bash is using for input, so we can reinitialize it later. */ nfd = (new_fd == -1) ? fcntl (fd, F_DUPFD, 10) : new_fd; if (nfd == -1) { if (fcntl (fd, F_GETFD, 0) == 0) sys_error ("cannot allocate new file descriptor for bash input from fd %d", fd); return -1; } if (buffers[nfd]) { /* What's this? A stray buffer without an associated open file descriptor? Free up the buffer and report the error. */ internal_error ("check_bash_input: buffer already exists for new fd %d", nfd); free_buffered_stream (buffers[nfd]); } /* Reinitialize bash_input.location. */ if (bash_input.type == st_bstream) { bash_input.location.buffered_fd = nfd; fd_to_buffered_stream (nfd); close_buffered_fd (fd); /* XXX */ } else /* If the current input type is not a buffered stream, but the shell is not interactive and therefore using a buffered stream to read input (e.g. with an `eval exec 3>output' inside a script), note that the input fd has been changed. pop_stream() looks at this value and adjusts the input fd to the new value of default_buffered_input accordingly. */ bash_input_fd_changed++; if (default_buffered_input == fd) default_buffered_input = nfd; SET_CLOSE_ON_EXEC (nfd); return nfd; } /* Check that file descriptor FD is not the one that bash is currently using to read input from a script. FD is about to be duplicated onto, which means that the kernel will close it for us. If FD is the bash input file descriptor, we need to seek backwards in the script (if possible and necessary -- scripts read from stdin are still unbuffered), allocate a new file descriptor to use for bash input, and re-initialize the buffered stream. Make sure the file descriptor used to save bash input is set close-on-exec. Returns 0 on success, -1 on failure. This works only if fd is > 0 -- if fd == 0 and bash is reading input from fd 0, save_bash_input is used instead, to cooperate with input redirection (look at redir.c:add_undo_redirect()). */ int check_bash_input (fd) int fd; { if (fd > 0 && fd_is_bash_input (fd)) return ((save_bash_input (fd, -1) == -1) ? -1 : 0); return 0; } /* This is the buffered stream analogue of dup2(fd1, fd2). The BUFFERED_STREAM corresponding to fd2 is deallocated, if one exists. BUFFERS[fd1] is copied to BUFFERS[fd2]. This is called by the redirect code for constructs like 4<&0 and 3b_fd = fd2; if (is_bash_input) { if (!buffers[fd2]) fd_to_buffered_stream (fd2); buffers[fd2]->b_flag |= B_WASBASHINPUT; } return (fd2); } /* Return 1 if a seek on FD will succeed. */ #ifndef __CYGWIN__ # define fd_is_seekable(fd) (lseek ((fd), 0L, SEEK_CUR) >= 0) #else # define fd_is_seekable(fd) 0 #endif /* __CYGWIN__ */ /* Take FD, a file descriptor, and create and return a buffered stream corresponding to it. If something is wrong and the file descriptor is invalid, return a NULL stream. */ BUFFERED_STREAM * fd_to_buffered_stream (fd) int fd; { char *buffer; size_t size; struct stat sb; if (fstat (fd, &sb) < 0) { close (fd); return ((BUFFERED_STREAM *)NULL); } size = (fd_is_seekable (fd)) ? min (sb.st_size, MAX_INPUT_BUFFER_SIZE) : 1; if (size == 0) size = 1; buffer = (char *)xmalloc (size); return (make_buffered_stream (fd, buffer, size)); } /* Return a buffered stream corresponding to FILE, a file name. */ BUFFERED_STREAM * open_buffered_stream (file) char *file; { int fd; fd = open (file, O_RDONLY); return ((fd >= 0) ? fd_to_buffered_stream (fd) : (BUFFERED_STREAM *)NULL); } /* Deallocate a buffered stream and free up its resources. Make sure we zero out the slot in BUFFERS that points to BP. */ void free_buffered_stream (bp) BUFFERED_STREAM *bp; { int n; if (!bp) return; n = bp->b_fd; if (bp->b_buffer) free (bp->b_buffer); free (bp); buffers[n] = (BUFFERED_STREAM *)NULL; } /* Close the file descriptor associated with BP, a buffered stream, and free up the stream. Return the status of closing BP's file descriptor. */ int close_buffered_stream (bp) BUFFERED_STREAM *bp; { int fd; if (!bp) return (0); fd = bp->b_fd; free_buffered_stream (bp); return (close (fd)); } /* Deallocate the buffered stream associated with file descriptor FD, and close FD. Return the status of the close on FD. */ int close_buffered_fd (fd) int fd; { if (fd < 0) { errno = EBADF; return -1; } if (fd >= nbuffers || !buffers || !buffers[fd]) return (close (fd)); return (close_buffered_stream (buffers[fd])); } /* Make the BUFFERED_STREAM associcated with buffers[FD] be BP, and return the old BUFFERED_STREAM. */ BUFFERED_STREAM * set_buffered_stream (fd, bp) int fd; BUFFERED_STREAM *bp; { BUFFERED_STREAM *ret; ret = buffers[fd]; buffers[fd] = bp; return ret; } /* Read a buffer full of characters from BP, a buffered stream. */ static int b_fill_buffer (bp) BUFFERED_STREAM *bp; { ssize_t nr; nr = zread (bp->b_fd, bp->b_buffer, bp->b_size); if (nr <= 0) { bp->b_used = 0; bp->b_buffer[0] = 0; if (nr == 0) bp->b_flag |= B_EOF; else bp->b_flag |= B_ERROR; return (EOF); } #if defined (__CYGWIN__) /* If on cygwin, translate \r\n to \n. */ if (nr >= 2 && bp->b_buffer[nr - 2] == '\r' && bp->b_buffer[nr - 1] == '\n') { bp->b_buffer[nr - 2] = '\n'; nr--; } #endif bp->b_used = nr; bp->b_inputp = 0; return (bp->b_buffer[bp->b_inputp++] & 0xFF); } /* Get a character from buffered stream BP. */ #define bufstream_getc(bp) \ (bp->b_inputp == bp->b_used || !bp->b_used) \ ? b_fill_buffer (bp) \ : bp->b_buffer[bp->b_inputp++] & 0xFF /* Push C back onto buffered stream BP. */ static int bufstream_ungetc(c, bp) int c; BUFFERED_STREAM *bp; { if (c == EOF || bp->b_inputp == 0) return (EOF); bp->b_buffer[--bp->b_inputp] = c; return (c); } /* Seek backwards on file BFD to synchronize what we've read so far with the underlying file pointer. */ int sync_buffered_stream (bfd) int bfd; { BUFFERED_STREAM *bp; off_t chars_left; if (buffers == 0 || (bp = buffers[bfd]) == 0) return (-1); chars_left = bp->b_used - bp->b_inputp; if (chars_left) lseek (bp->b_fd, -chars_left, SEEK_CUR); bp->b_used = bp->b_inputp = 0; return (0); } int buffered_getchar () { #if !defined (DJGPP) return (bufstream_getc (buffers[bash_input.location.buffered_fd])); #else /* On DJGPP, ignore \r. */ int ch; while ((ch = bufstream_getc (buffers[bash_input.location.buffered_fd])) == '\r') ; return ch; #endif } int buffered_ungetchar (c) int c; { return (bufstream_ungetc (c, buffers[bash_input.location.buffered_fd])); } /* Make input come from file descriptor BFD through a buffered stream. */ void with_input_from_buffered_stream (bfd, name) int bfd; char *name; { INPUT_STREAM location; BUFFERED_STREAM *bp; location.buffered_fd = bfd; /* Make sure the buffered stream exists. */ bp = fd_to_buffered_stream (bfd); init_yy_io (bp == 0 ? return_EOF : buffered_getchar, buffered_ungetchar, st_bstream, name, location); } #if defined (TEST) void * xmalloc(s) int s; { return (malloc (s)); } void * xrealloc(s, size) char *s; int size; { if (!s) return(malloc (size)); else return(realloc (s, size)); } void init_yy_io () { } process(bp) BUFFERED_STREAM *bp; { int c; while ((c = bufstream_getc(bp)) != EOF) putchar(c); } BASH_INPUT bash_input; struct stat dsb; /* can be used from gdb */ /* imitate /bin/cat */ main(argc, argv) int argc; char **argv; { register int i; BUFFERED_STREAM *bp; if (argc == 1) { bp = fd_to_buffered_stream (0); process(bp); exit(0); } for (i = 1; i < argc; i++) { if (argv[i][0] == '-' && argv[i][1] == '\0') { bp = fd_to_buffered_stream (0); if (!bp) continue; process(bp); free_buffered_stream (bp); } else { bp = open_buffered_stream (argv[i]); if (!bp) continue; process(bp); close_buffered_stream (bp); } } exit(0); } #endif /* TEST */ #endif /* BUFFERED_INPUT */ /* 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, 1992 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 */ #include #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 "shell.h" #include "jobs.h" #include "flags.h" #include "builtins/builtext.h" #include "builtins/common.h" #if !defined (errno) extern int errno; #endif /* !errno */ #if !defined (CHILD_MAX) # define CHILD_MAX 32 #endif /* 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 # 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 startup_state, subshell_environment, line_number; extern int posixly_correct, shell_level; extern int interrupt_immediately, last_command_exit_value; 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 WORD_LIST *subst_assign_varlist; #if defined (ARRAY_VARS) static int *pstatuses; /* list of pipeline statuses */ static int statsize; #endif /* 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)); static char *current_working_directory __P((void)); static char *job_working_directory __P((void)); static char *printable_job_status __P((int, PROCESS *, int)); static pid_t find_last_pid __P((int)); static pid_t last_pid __P((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)); static int print_job __P((JOB *, int, int, int)); static int process_exit_status __P((WAIT)); static int job_exit_status __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 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 /* Used to synchronize between wait_for and the SIGCHLD signal handler. */ static int sigchld; static int waiting_for_job; /* 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]; #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? */ 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; sigset_t set, oset; if (job_slots == 0 || jobs_list_frozen) return; BLOCK_CHILD (set, oset); for (i = 0; i < job_slots; i++) if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i)) delete_job (i, 0); UNBLOCK_CHILD (oset); } /* 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 = 1; 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) pid_t pid; { int job; register PROCESS *p; /* See if this process is in the pipeline that we are building. */ if (the_pipeline) { p = the_pipeline; do { /* Return it if we found it. */ if (p->pid == pid) return (p); p = p->next; } while (p != the_pipeline); } job = find_job (pid); 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) pid_t pid; { 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) 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); 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); 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 * 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 = strsignal (WSTOPSIG (p->status)); else if (WIFSIGNALED (p->status)) temp = 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); } static void pretty_print_job (job_index, format, stream) int job_index, format; FILE *stream; { register PROCESS *p; sigset_t set, oset; BLOCK_CHILD (set, oset); /* Format only pid information about the process group leader? */ if (format == JLIST_PID_ONLY) { fprintf (stream, "%ld\n", (long)jobs[job_index]->pipe->pid); UNBLOCK_CHILD (oset); return; } if (format == JLIST_CHANGED_ONLY) { if (IS_NOTIFIED (job_index)) { UNBLOCK_CHILD (oset); 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; UNBLOCK_CHILD (oset); } 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; { print_job (job, format, -1, job_index); } 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); #if defined (PGRP_PIPE) if (pipeline_pgrp == mypid) { #endif /* 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) 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); } 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. */ static pid_t find_last_pid (job) int job; { register PROCESS *p; p = jobs[job]->pipe; while (p->next != jobs[job]->pipe) p = p->next; return (p->pid); } static pid_t last_pid (job) int job; { pid_t pid; sigset_t set, oset; BLOCK_CHILD (set, oset); pid = find_last_pid (job); UNBLOCK_CHILD (oset); return (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 0 if not (whatever wait_for returns). 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); 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); 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, count, r, waited_for; sigset_t set, oset; pid_t pid; for (waited_for = 0;;) { BLOCK_CHILD (set, oset); count = 0; for (i = 0; i < job_slots; i++) if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0) { count++; break; } if (count == 0) { UNBLOCK_CHILD (oset); break; } for (i = 0; i < job_slots; i++) if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0) { pid = last_pid (i); UNBLOCK_CHILD (oset); QUIT; errno = 0; /* XXX */ r = wait_for_single_pid (pid); if (r == -1) { if (errno == ECHILD) mark_all_jobs_as_dead (); } else waited_for++; break; } } /* 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] */ 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_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; 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))); } #define FIND_CHILD(pid, child) \ do \ { \ child = find_pipeline (pid); \ 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); /* 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 waiting_for_job = 1; r = waitchld (pid, 1); # if defined (MUST_UNBLOCK_CHLD) sigaction (SIGCHLD, &oact, (struct sigaction *)NULL); sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL); # endif waiting_for_job = 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 = 0; child->status = 0; /* XXX */ 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 (job != NO_JOB) termination_state = job_exit_status (job); else termination_state = process_exit_status (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) 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); } } } /* 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); UNBLOCK_CHILD(oset); pid = last_pid (job); 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 = 1; 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: bg background job?", this_command_name); 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); } UNBLOCK_CHILD (oset); if (foreground) { pid_t pid; int s; pid = last_pid (job); s = wait_for (pid); shell_tty_info = save_stty; set_tty_state (); return (s); } else { BLOCK_CHILD (set, oset); 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); job = find_job (pid); 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 == 0 && (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 (waiting_for_job == 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); /* 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) ? 1 : 0; job = find_job (pid); 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 == 0 && (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 (IS_FOREGROUND (job)) setjstatus (job); /* 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)) { wait_sigint_received = 0; last_command_exit_value = process_exit_status (child->status); jobs_list_frozen = 1; tstatus = maybe_call_trap_handler (SIGINT); jobs_list_frozen = 0; } /* 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) { 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. */ jobs_list_frozen = 1; tstatus = maybe_call_trap_handler (SIGINT); jobs_list_frozen = 0; 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); #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_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 ((Function *)xfree, trap_command); add_unwind_protect ((Function *)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); } 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; sigemptyset (&set); sigaddset (&set, SIGCHLD); sigaddset (&set, SIGTTOU); sigemptyset (&oset); sigprocmask (SIG_BLOCK, &set, &oset); 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 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) { /* 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) != 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) { fprintf (stderr, "%s: line %d: ", get_name_for_error (), 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", 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"); } } } sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); } /* 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 *old_ttin; old_ttin = set_signal_handler(SIGTTIN, SIG_DFL); kill (0, SIGTTIN); set_signal_handler (SIGTTIN, old_ttin); 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 } 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; 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_ttou = set_signal_handler (SIGTTOU, sigstop_sighandler); old_ttin = set_signal_handler (SIGTTIN, 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) { 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) { BLOCK_CHILD (set, oset); /* Count the number of dead jobs */ for (i = ndead = 0; force == 0 && i < job_slots; i++) { if (jobs[i] && DEADJOB (i)) ndead++; } /* Don't do anything if the number of jobs is less than CHILD_MAX and we're not forcing a cleanup. */ if (force == 0 && 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) != last_asynchronous_pid))) { jobs[i]->flags |= J_NOTIFIED; if (force == 0 && --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; 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 */ /* list.c - Functions for manipulating linked lists of objects. */ /* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "shell.h" /* A global variable which acts as a sentinel for an `error' list return. */ GENERIC_LIST global_error_list; #ifdef INCLUDE_UNUSED /* Call FUNCTION on every member of LIST, a generic list. */ void map_over_list (list, function) GENERIC_LIST *list; sh_glist_func_t *function; { for ( ; list; list = list->next) (*function) (list); } /* Call FUNCTION on every string in WORDS. */ void map_over_words (words, function) WORD_LIST *words; sh_icpfunc_t *function; { for ( ; words; words = words->next) (*function) (words->word->word); } #endif /* INCLUDE_UNUSED */ /* Reverse the chain of structures in LIST. Output the new head of the chain. You should always assign the output value of this function to something, or you will lose the chain. */ GENERIC_LIST * reverse_list (list) GENERIC_LIST *list; { register GENERIC_LIST *next, *prev; for (prev = (GENERIC_LIST *)NULL; list; ) { next = list->next; list->next = prev; prev = list; list = next; } return (prev); } /* Return the number of elements in LIST, a generic list. */ int list_length (list) GENERIC_LIST *list; { register int i; for (i = 0; list; list = list->next, i++); return (i); } /* Append TAIL to HEAD. Return the header of the list. */ GENERIC_LIST * list_append (head, tail) GENERIC_LIST *head, *tail; { register GENERIC_LIST *t_head; if (head == 0) return (tail); for (t_head = head; t_head->next; t_head = t_head->next) ; t_head->next = tail; return (head); } #ifdef INCLUDE_UNUSED /* Delete the element of LIST which satisfies the predicate function COMPARER. Returns the element that was deleted, so you can dispose of it, or -1 if the element wasn't found. COMPARER is called with the list element and then ARG. Note that LIST contains the address of a variable which points to the list. You might call this function like this: SHELL_VAR *elt = delete_element (&variable_list, check_var_has_name, "foo"); dispose_variable (elt); */ GENERIC_LIST * delete_element (list, comparer, arg) GENERIC_LIST **list; Function *comparer; char *arg; { register GENERIC_LIST *prev, *temp; for (prev = (GENERIC_LIST *)NULL, temp = *list; temp; prev = temp, temp = temp->next) { if ((*comparer) (temp, arg)) { if (prev) prev->next = temp->next; else *list = temp->next; return (temp); } } return ((GENERIC_LIST *)&global_error_list); } #endif /* locale.c - Miscellaneous internationalization functions. */ /* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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" #if defined (HAVE_UNISTD_H) # include #endif #include "bashintl.h" #include "bashansi.h" #include #include "chartypes.h" #include "shell.h" /* The current locale when the program begins */ static char *default_locale; /* The current domain for textdomain(3). */ static char *default_domain; static char *default_dir; /* tracks the value of LC_ALL; used to override values for other locale categories */ static char *lc_all; /* Set the value of default_locale and make the current locale the system default locale. This should be called very early in main(). */ void set_default_locale () { #if defined (HAVE_SETLOCALE) default_locale = setlocale (LC_ALL, ""); if (default_locale) default_locale = savestring (default_locale); #endif /* HAVE_SETLOCALE */ } /* Set default values for LC_CTYPE, LC_COLLATE, and LC_MESSAGES if they are not specified in the environment, but LANG or LC_ALL is. This should be called from main() after parsing the environment. */ void set_default_locale_vars () { char *val; #if defined (HAVE_SETLOCALE) val = get_string_value ("LC_CTYPE"); if (val == 0 && lc_all && *lc_all) setlocale (LC_CTYPE, lc_all); # if defined (LC_COLLATE) val = get_string_value ("LC_COLLATE"); if (val == 0 && lc_all && *lc_all) setlocale (LC_COLLATE, lc_all); # endif /* LC_COLLATE */ # if defined (LC_MESSAGES) val = get_string_value ("LC_MESSAGES"); if (val == 0 && lc_all && *lc_all) setlocale (LC_MESSAGES, lc_all); # endif /* LC_MESSAGES */ # if defined (LC_NUMERIC) val = get_string_value ("LC_NUMERIC"); if (val == 0 && lc_all && *lc_all) setlocale (LC_NUMERIC, lc_all); # endif /* LC_NUMERIC */ #endif /* HAVE_SETLOCALE */ val = get_string_value ("TEXTDOMAIN"); if (val && *val) { FREE (default_domain); default_domain = savestring (val); textdomain (default_domain); } val = get_string_value ("TEXTDOMAINDIR"); if (val && *val) { FREE (default_dir); default_dir = savestring (val); bindtextdomain (default_domain, default_dir); } } /* Set one of the locale categories (specified by VAR) to VALUE. Returns 1 if successful, 0 otherwise. */ int set_locale_var (var, value) char *var, *value; { if (var[0] == 'T' && var[10] == 0) /* TEXTDOMAIN */ { FREE (default_domain); default_domain = value ? savestring (value) : (char *)NULL; textdomain (default_domain); return (1); } else if (var[0] == 'T') /* TEXTDOMAINDIR */ { FREE (default_dir); default_dir = value ? savestring (value) : (char *)NULL; bindtextdomain (default_domain, default_dir); return (1); } /* var[0] == 'L' && var[1] == 'C' && var[2] == '_' */ else if (var[3] == 'A') /* LC_ALL */ { FREE (lc_all); if (value) lc_all = savestring (value); else if (default_locale) lc_all = savestring (default_locale); else { lc_all = (char *)xmalloc (1); lc_all[0] = '\0'; } #if defined (HAVE_SETLOCALE) return (setlocale (LC_ALL, lc_all) != 0); #else return (1); #endif } #if defined (HAVE_SETLOCALE) else if (var[3] == 'C' && var[4] == 'T') /* LC_CTYPE */ { if (lc_all == 0 || *lc_all == '\0') return (setlocale (LC_CTYPE, value ? value : "") != 0); } else if (var[3] == 'C' && var[4] == 'O') /* LC_COLLATE */ { # if defined (LC_COLLATE) if (lc_all == 0 || *lc_all == '\0') return (setlocale (LC_COLLATE, value ? value : "") != 0); # endif /* LC_COLLATE */ } else if (var[3] == 'M' && var[4] == 'E') /* LC_MESSAGES */ { # if defined (LC_MESSAGES) if (lc_all == 0 || *lc_all == '\0') return (setlocale (LC_MESSAGES, value ? value : "") != 0); # endif /* LC_MESSAGES */ } else if (var[3] == 'N' && var[4] == 'U') /* LC_NUMERIC */ { # if defined (LC_NUMERIC) if (lc_all == 0 || *lc_all == '\0') return (setlocale (LC_NUMERIC, value ? value : "") != 0); # endif /* LC_NUMERIC */ } #endif /* HAVE_SETLOCALE */ return (0); } #if 0 /* Called when LANG is assigned a value. Sets LC_ALL if that has not already been set. */ #else /* This no longer does anything; we rely on the C library for correct behavior. */ #endif int set_lang (var, value) char *var, *value; { #if 0 return ((lc_all == 0) ? set_locale_var ("LC_ALL", value) : 0); #else return 0; #endif } /* Get the value of one of the locale variables (LC_MESSAGES, LC_CTYPE) */ char * get_locale_var (var) char *var; { char *locale; locale = lc_all; if (locale == 0) locale = get_string_value (var); if (locale == 0) locale = default_locale; return (locale); } /* Translate the contents of STRING, a $"..." quoted string, according to the current locale. In the `C' or `POSIX' locale, or if gettext() is not available, the passed string is returned unchanged. The length of the translated string is returned in LENP, if non-null. */ char * localetrans (string, len, lenp) char *string; int len, *lenp; { char *locale, *t; #if defined (HAVE_GETTEXT) char *translated; int tlen; #endif /* Don't try to translate null strings. */ if (string == 0 || *string == 0) { if (lenp) *lenp = 0; return ((char *)NULL); } locale = get_locale_var ("LC_MESSAGES"); /* If we don't have setlocale() or the current locale is `C' or `POSIX', just return the string. If we don't have gettext(), there's no use doing anything else. */ #if defined (HAVE_GETTEXT) if (locale == 0 || locale[0] == '\0' || (locale[0] == 'C' && locale[1] == '\0') || STREQ (locale, "POSIX")) #endif { t = (char *)xmalloc (len + 1); strcpy (t, string); if (lenp) *lenp = len; return (t); } #if defined (HAVE_GETTEXT) /* Now try to translate it. */ translated = gettext (string); if (translated == string) /* gettext returns its argument if untranslatable */ { t = (char *)xmalloc (len + 1); strcpy (t, string); if (lenp) *lenp = len; } else { tlen = strlen (translated); t = (char *)xmalloc (tlen + 1); strcpy (t, translated); if (lenp) *lenp = tlen; } return (t); #endif /* HAVE_GETTEXT */ } /* mailcheck.c -- The check is in the mail... */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 #include "bashtypes.h" #include "posixstat.h" #ifndef _MINIX # include #endif #if defined (HAVE_UNISTD_H) # include #endif #include "posixtime.h" #include "bashansi.h" #include "shell.h" #include "execute_cmd.h" #include "mailcheck.h" #include extern int mailstat __P((const char *, struct stat *)); typedef struct { char *name; char *msg; time_t access_time; time_t mod_time; off_t file_size; } FILEINFO; /* The list of remembered mail files. */ static FILEINFO **mailfiles = (FILEINFO **)NULL; /* Number of mail files that we have. */ static int mailfiles_count; /* The last known time that mail was checked. */ static time_t last_time_mail_checked; /* Non-zero means warn if a mail file has been read since last checked. */ int mail_warning; static int find_mail_file __P((char *)); static void update_mail_file __P((int)); static int add_mail_file __P((char *, char *)); static int file_mod_date_changed __P((int)); static int file_access_date_changed __P((int)); static int file_has_grown __P((int)); static char *parse_mailpath_spec __P((char *)); /* Returns non-zero if it is time to check mail. */ int time_to_check_mail () { char *temp; time_t now; long seconds; temp = get_string_value ("MAILCHECK"); /* Negative number, or non-numbers (such as empty string) cause no checking to take place. */ if (temp == 0 || legal_number (temp, &seconds) == 0 || seconds < 0) return (0); now = NOW; /* Time to check if MAILCHECK is explicitly set to zero, or if enough time has passed since the last check. */ return (seconds == 0 || ((now - last_time_mail_checked) >= seconds)); } /* Okay, we have checked the mail. Perhaps I should make this function go away. */ void reset_mail_timer () { last_time_mail_checked = NOW; } /* Locate a file in the list. Return index of entry, or -1 if not found. */ static int find_mail_file (file) char *file; { register int i; for (i = 0; i < mailfiles_count; i++) if (STREQ (mailfiles[i]->name, file)) return i; return -1; } #define RESET_MAIL_FILE(i) \ do \ { \ mailfiles[i]->access_time = mailfiles[i]->mod_time = 0; \ mailfiles[i]->file_size = 0; \ } \ while (0) static void update_mail_file (i) int i; { char *file; struct stat finfo; file = mailfiles[i]->name; if (mailstat (file, &finfo) == 0) { mailfiles[i]->access_time = finfo.st_atime; mailfiles[i]->mod_time = finfo.st_mtime; mailfiles[i]->file_size = finfo.st_size; } else RESET_MAIL_FILE (i); } /* Add this file to the list of remembered files and return its index in the list of mail files. */ static int add_mail_file (file, msg) char *file, *msg; { struct stat finfo; char *filename; int i; filename = full_pathname (file); i = find_mail_file (filename); if (i >= 0) { if (mailstat (filename, &finfo) == 0) { mailfiles[i]->mod_time = finfo.st_mtime; mailfiles[i]->access_time = finfo.st_atime; mailfiles[i]->file_size = finfo.st_size; } free (filename); return i; } i = mailfiles_count++; mailfiles = (FILEINFO **)xrealloc (mailfiles, mailfiles_count * sizeof (FILEINFO *)); mailfiles[i] = (FILEINFO *)xmalloc (sizeof (FILEINFO)); mailfiles[i]->name = filename; mailfiles[i]->msg = msg ? savestring (msg) : (char *)NULL; update_mail_file (i); return i; } /* Reset the existing mail files access and modification times to zero. */ void reset_mail_files () { register int i; for (i = 0; i < mailfiles_count; i++) { RESET_MAIL_FILE (i); } } /* Free the information that we have about the remembered mail files. */ void free_mail_files () { register int i; for (i = 0; i < mailfiles_count; i++) { free (mailfiles[i]->name); FREE (mailfiles[i]->msg); free (mailfiles[i]); } if (mailfiles) free (mailfiles); mailfiles_count = 0; mailfiles = (FILEINFO **)NULL; } /* Return non-zero if FILE's mod date has changed and it has not been accessed since modified. */ static int file_mod_date_changed (i) int i; { time_t mtime; struct stat finfo; char *file; file = mailfiles[i]->name; mtime = mailfiles[i]->mod_time; if ((mailstat (file, &finfo) == 0) && (finfo.st_size > 0)) return (mtime != finfo.st_mtime); return (0); } /* Return non-zero if FILE's access date has changed. */ static int file_access_date_changed (i) int i; { time_t atime; struct stat finfo; char *file; file = mailfiles[i]->name; atime = mailfiles[i]->access_time; if ((mailstat (file, &finfo) == 0) && (finfo.st_size > 0)) return (atime != finfo.st_atime); return (0); } /* Return non-zero if FILE's size has increased. */ static int file_has_grown (i) int i; { off_t size; struct stat finfo; char *file; file = mailfiles[i]->name; size = mailfiles[i]->file_size; return ((mailstat (file, &finfo) == 0) && (finfo.st_size > size)); } /* Take an element from $MAILPATH and return the portion from the first unquoted `?' or `%' to the end of the string. This is the message to be printed when the file contents change. */ static char * parse_mailpath_spec (str) char *str; { char *s; int pass_next; for (s = str, pass_next = 0; s && *s; s++) { if (pass_next) { pass_next = 0; continue; } if (*s == '\\') { pass_next++; continue; } if (*s == '?' || *s == '%') return s; } return ((char *)NULL); } char * make_default_mailpath () { char *mp; get_current_user_info (); mp = (char *)xmalloc (2 + sizeof (DEFAULT_MAIL_DIRECTORY) + strlen (current_user.user_name)); strcpy (mp, DEFAULT_MAIL_DIRECTORY); mp[sizeof(DEFAULT_MAIL_DIRECTORY) - 1] = '/'; strcpy (mp + sizeof (DEFAULT_MAIL_DIRECTORY), current_user.user_name); return (mp); } /* Remember the dates of the files specified by MAILPATH, or if there is no MAILPATH, by the file specified in MAIL. If neither exists, use a default value, which we randomly concoct from using Unix. */ void remember_mail_dates () { char *mailpaths; char *mailfile, *mp; int i = 0; mailpaths = get_string_value ("MAILPATH"); /* If no $MAILPATH, but $MAIL, use that as a single filename to check. */ if (mailpaths == 0 && (mailpaths = get_string_value ("MAIL"))) { add_mail_file (mailpaths, (char *)NULL); return; } if (mailpaths == 0) { mailpaths = make_default_mailpath (); add_mail_file (mailpaths, (char *)NULL); free (mailpaths); return; } while (mailfile = extract_colon_unit (mailpaths, &i)) { mp = parse_mailpath_spec (mailfile); if (mp && *mp) *mp++ = '\0'; add_mail_file (mailfile, mp); free (mailfile); } } /* check_mail () is useful for more than just checking mail. Since it has the paranoids dream ability of telling you when someone has read your mail, it can just as easily be used to tell you when someones .profile file has been read, thus letting one know when someone else has logged in. Pretty good, huh? */ /* Check for mail in some files. If the modification date of any of the files in MAILPATH has changed since we last did a remember_mail_dates () then mention that the user has mail. Special hack: If the variable MAIL_WARNING is non-zero and the mail file has been accessed since the last time we remembered, then the message "The mail in has been read" is printed. */ void check_mail () { char *current_mail_file, *message; int i, use_user_notification; char *dollar_underscore, *temp; dollar_underscore = get_string_value ("_"); if (dollar_underscore) dollar_underscore = savestring (dollar_underscore); for (i = 0; i < mailfiles_count; i++) { current_mail_file = mailfiles[i]->name; if (*current_mail_file == '\0') continue; if (file_mod_date_changed (i)) { int file_is_bigger; use_user_notification = mailfiles[i]->msg != (char *)NULL; message = mailfiles[i]->msg ? mailfiles[i]->msg : "You have mail in $_"; bind_variable ("_", current_mail_file); #define atime mailfiles[i]->access_time #define mtime mailfiles[i]->mod_time /* Have to compute this before the call to update_mail_file, which resets all the information. */ file_is_bigger = file_has_grown (i); update_mail_file (i); /* If the user has just run a program which manipulates the mail file, then don't bother explaining that the mail file has been manipulated. Since some systems don't change the access time to be equal to the modification time when the mail in the file is manipulated, check the size also. If the file has not grown, continue. */ if ((atime >= mtime) && !file_is_bigger) continue; /* If the mod time is later than the access time and the file has grown, note the fact that this is *new* mail. */ if (use_user_notification == 0 && (atime < mtime) && file_is_bigger) message = "You have new mail in $_"; #undef atime #undef mtime if (temp = expand_string_to_string (message, Q_DOUBLE_QUOTES)) { puts (temp); free (temp); } else putchar ('\n'); } if (mail_warning && file_access_date_changed (i)) { update_mail_file (i); printf ("The mail in %s has been read\n", current_mail_file); } } if (dollar_underscore) { bind_variable ("_", dollar_underscore); free (dollar_underscore); } else unbind_variable ("_"); } /* make_cmd.c -- Functions for making instances of the various parser constructs. */ /* Copyright (C) 1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 #include "bashtypes.h" #ifndef _MINIX # include #endif #include "filecntl.h" #include "bashansi.h" #if defined (HAVE_UNISTD_H) # include #endif #include "syntax.h" #include "command.h" #include "general.h" #include "error.h" #include "flags.h" #include "make_cmd.h" #include "variables.h" #include "subst.h" #include "input.h" #include "externs.h" #if defined (JOB_CONTROL) #include "jobs.h" #endif extern int line_number, current_command_line_count; extern int last_command_exit_value; static COMMAND *make_for_or_select __P((enum command_type, WORD_DESC *, WORD_LIST *, COMMAND *)); #if defined (ARITH_FOR_COMMAND) static WORD_LIST *make_arith_for_expr __P((char *)); #endif static COMMAND *make_until_or_while __P((enum command_type, COMMAND *, COMMAND *)); WORD_DESC * make_bare_word (string) const char *string; { WORD_DESC *temp; temp = (WORD_DESC *)xmalloc (sizeof (WORD_DESC)); if (*string) temp->word = savestring (string); else { temp->word = (char *)xmalloc (1); temp->word[0] = '\0'; } temp->flags = 0; return (temp); } WORD_DESC * make_word_flags (w, string) WORD_DESC *w; const char *string; { register const char *s; for (s = string; *s; s++) switch (*s) { case '$': w->flags |= W_HASDOLLAR; break; case '\\': break; /* continue the loop */ case '\'': case '`': case '"': w->flags |= W_QUOTED; break; } return (w); } WORD_DESC * make_word (string) const char *string; { WORD_DESC *temp; temp = make_bare_word (string); return (make_word_flags (temp, string)); } WORD_DESC * make_word_from_token (token) int token; { char tokenizer[2]; tokenizer[0] = token; tokenizer[1] = '\0'; return (make_word (tokenizer)); } WORD_LIST * make_word_list (word, wlink) WORD_DESC *word; WORD_LIST *wlink; { WORD_LIST *temp; temp = (WORD_LIST *)xmalloc (sizeof (WORD_LIST)); temp->word = word; temp->next = wlink; return (temp); } WORD_LIST * add_string_to_list (string, list) char *string; WORD_LIST *list; { WORD_LIST *temp; temp = (WORD_LIST *)xmalloc (sizeof (WORD_LIST)); temp->word = make_word (string); temp->next = list; return (temp); } COMMAND * make_command (type, pointer) enum command_type type; SIMPLE_COM *pointer; { COMMAND *temp; temp = (COMMAND *)xmalloc (sizeof (COMMAND)); temp->type = type; temp->value.Simple = pointer; temp->value.Simple->flags = temp->flags = 0; temp->redirects = (REDIRECT *)NULL; return (temp); } COMMAND * command_connect (com1, com2, connector) COMMAND *com1, *com2; int connector; { CONNECTION *temp; temp = (CONNECTION *)xmalloc (sizeof (CONNECTION)); temp->connector = connector; temp->first = com1; temp->second = com2; return (make_command (cm_connection, (SIMPLE_COM *)temp)); } static COMMAND * make_for_or_select (type, name, map_list, action) enum command_type type; WORD_DESC *name; WORD_LIST *map_list; COMMAND *action; { FOR_COM *temp; temp = (FOR_COM *)xmalloc (sizeof (FOR_COM)); temp->flags = 0; temp->name = name; temp->map_list = map_list; temp->action = action; return (make_command (type, (SIMPLE_COM *)temp)); } COMMAND * make_for_command (name, map_list, action) WORD_DESC *name; WORD_LIST *map_list; COMMAND *action; { return (make_for_or_select (cm_for, name, map_list, action)); } COMMAND * make_select_command (name, map_list, action) WORD_DESC *name; WORD_LIST *map_list; COMMAND *action; { #if defined (SELECT_COMMAND) return (make_for_or_select (cm_select, name, map_list, action)); #else last_command_exit_value = 2; return ((COMMAND *)NULL); #endif } #if defined (ARITH_FOR_COMMAND) static WORD_LIST * make_arith_for_expr (s) char *s; { WORD_LIST *result; WORD_DESC *w; if (s == 0 || *s == '\0') return ((WORD_LIST *)NULL); w = make_word (s); result = make_word_list (w, (WORD_LIST *)NULL); return result; } #endif COMMAND * make_arith_for_command (exprs, action, lineno) WORD_LIST *exprs; COMMAND *action; int lineno; { #if defined (ARITH_FOR_COMMAND) ARITH_FOR_COM *temp; WORD_LIST *init, *test, *step; char *s, *t, *start; int nsemi; init = test = step = (WORD_LIST *)NULL; /* Parse the string into the three component sub-expressions. */ start = t = s = exprs->word->word; for (nsemi = 0; ;) { /* skip whitespace at the start of each sub-expression. */ while (whitespace (*s)) s++; start = s; /* skip to the semicolon or EOS */ while (*s && *s != ';') s++; t = (s > start) ? substring (start, 0, s - start) : (char *)NULL; nsemi++; switch (nsemi) { case 1: init = make_arith_for_expr (t); break; case 2: test = make_arith_for_expr (t); break; case 3: step = make_arith_for_expr (t); break; } FREE (t); if (*s == '\0') break; s++; /* skip over semicolon */ } if (nsemi != 3) { if (nsemi < 3) parser_error (lineno, "syntax error: arithmetic expression required"); else parser_error (lineno, "syntax error: `;' unexpected"); parser_error (lineno, "syntax error: `((%s))'", exprs->word->word); last_command_exit_value = 2; return ((COMMAND *)NULL); } temp = (ARITH_FOR_COM *)xmalloc (sizeof (ARITH_FOR_COM)); temp->flags = 0; temp->line = lineno; temp->init = init ? init : make_arith_for_expr ("1"); temp->test = test ? test : make_arith_for_expr ("1"); temp->step = step ? step : make_arith_for_expr ("1"); temp->action = action; return (make_command (cm_arith_for, (SIMPLE_COM *)temp)); #else last_command_exit_value = 2; return ((COMMAND *)NULL); #endif /* ARITH_FOR_COMMAND */ } COMMAND * make_group_command (command) COMMAND *command; { GROUP_COM *temp; temp = (GROUP_COM *)xmalloc (sizeof (GROUP_COM)); temp->command = command; return (make_command (cm_group, (SIMPLE_COM *)temp)); } COMMAND * make_case_command (word, clauses) WORD_DESC *word; PATTERN_LIST *clauses; { CASE_COM *temp; temp = (CASE_COM *)xmalloc (sizeof (CASE_COM)); temp->flags = 0; temp->word = word; temp->clauses = REVERSE_LIST (clauses, PATTERN_LIST *); return (make_command (cm_case, (SIMPLE_COM *)temp)); } PATTERN_LIST * make_pattern_list (patterns, action) WORD_LIST *patterns; COMMAND *action; { PATTERN_LIST *temp; temp = (PATTERN_LIST *)xmalloc (sizeof (PATTERN_LIST)); temp->patterns = REVERSE_LIST (patterns, WORD_LIST *); temp->action = action; temp->next = NULL; return (temp); } COMMAND * make_if_command (test, true_case, false_case) COMMAND *test, *true_case, *false_case; { IF_COM *temp; temp = (IF_COM *)xmalloc (sizeof (IF_COM)); temp->flags = 0; temp->test = test; temp->true_case = true_case; temp->false_case = false_case; return (make_command (cm_if, (SIMPLE_COM *)temp)); } static COMMAND * make_until_or_while (which, test, action) enum command_type which; COMMAND *test, *action; { WHILE_COM *temp; temp = (WHILE_COM *)xmalloc (sizeof (WHILE_COM)); temp->flags = 0; temp->test = test; temp->action = action; return (make_command (which, (SIMPLE_COM *)temp)); } COMMAND * make_while_command (test, action) COMMAND *test, *action; { return (make_until_or_while (cm_while, test, action)); } COMMAND * make_until_command (test, action) COMMAND *test, *action; { return (make_until_or_while (cm_until, test, action)); } COMMAND * make_arith_command (exp) WORD_LIST *exp; { #if defined (DPAREN_ARITHMETIC) COMMAND *command; ARITH_COM *temp; command = (COMMAND *)xmalloc (sizeof (COMMAND)); command->value.Arith = temp = (ARITH_COM *)xmalloc (sizeof (ARITH_COM)); temp->flags = 0; temp->line = line_number; temp->exp = exp; command->type = cm_arith; command->redirects = (REDIRECT *)NULL; command->flags = 0; return (command); #else last_command_exit_value = 2; return ((COMMAND *)NULL); #endif } #if defined (COND_COMMAND) struct cond_com * make_cond_node (type, op, left, right) int type; WORD_DESC *op; struct cond_com *left, *right; { COND_COM *temp; temp = (COND_COM *)xmalloc (sizeof (COND_COM)); temp->flags = 0; temp->line = line_number; temp->type = type; temp->op = op; temp->left = left; temp->right = right; return (temp); } #endif COMMAND * make_cond_command (cond_node) COND_COM *cond_node; { #if defined (COND_COMMAND) COMMAND *command; command = (COMMAND *)xmalloc (sizeof (COMMAND)); command->value.Cond = cond_node; command->type = cm_cond; command->redirects = (REDIRECT *)NULL; command->flags = 0; command->line = cond_node ? cond_node->line : 0; return (command); #else last_command_exit_value = 2; return ((COMMAND *)NULL); #endif } COMMAND * make_bare_simple_command () { COMMAND *command; SIMPLE_COM *temp; command = (COMMAND *)xmalloc (sizeof (COMMAND)); command->value.Simple = temp = (SIMPLE_COM *)xmalloc (sizeof (SIMPLE_COM)); temp->flags = 0; temp->line = line_number; temp->words = (WORD_LIST *)NULL; temp->redirects = (REDIRECT *)NULL; command->type = cm_simple; command->redirects = (REDIRECT *)NULL; command->flags = 0; return (command); } /* Return a command which is the connection of the word or redirection in ELEMENT, and the command * or NULL in COMMAND. */ COMMAND * make_simple_command (element, command) ELEMENT element; COMMAND *command; { /* If we are starting from scratch, then make the initial command structure. Also note that we have to fill in all the slots, since malloc doesn't return zeroed space. */ if (!command) command = make_bare_simple_command (); if (element.word) { WORD_LIST *tw = (WORD_LIST *)xmalloc (sizeof (WORD_LIST)); tw->word = element.word; tw->next = command->value.Simple->words; command->value.Simple->words = tw; } else { REDIRECT *r = element.redirect; /* Due to the way <> is implemented, there may be more than a single redirection in element.redirect. We just follow the chain as far as it goes, and hook onto the end. */ while (r->next) r = r->next; r->next = command->value.Simple->redirects; command->value.Simple->redirects = element.redirect; } return (command); } /* Because we are Bourne compatible, we read the input for this << or <<- redirection now, from wherever input is coming from. We store the input read into a WORD_DESC. Replace the text of the redirectee.word with the new input text. If <<- is on, then remove leading TABS from each line. */ void make_here_document (temp) REDIRECT *temp; { int kill_leading, redir_len; char *redir_word, *document, *full_line; int document_index, document_size, delim_unquoted; if (temp->instruction != r_deblank_reading_until && temp->instruction != r_reading_until) { internal_error ("make_here_document: bad instruction type %d", temp->instruction); return; } kill_leading = temp->instruction == r_deblank_reading_until; document = (char *)NULL; document_index = document_size = 0; /* Quote removal is the only expansion performed on the delimiter for here documents, making it an extremely special case. */ redir_word = string_quote_removal (temp->redirectee.filename->word, 0); /* redirection_expand will return NULL if the expansion results in multiple words or no words. Check for that here, and just abort this here document if it does. */ if (redir_word) redir_len = strlen (redir_word); else { temp->here_doc_eof = (char *)xmalloc (1); temp->here_doc_eof[0] = '\0'; goto document_done; } free (temp->redirectee.filename->word); temp->here_doc_eof = redir_word; /* Read lines from wherever lines are coming from. For each line read, if kill_leading, then kill the leading tab characters. If the line matches redir_word exactly, then we have manufactured the document. Otherwise, add the line to the list of lines in the document. */ /* If the here-document delimiter was quoted, the lines should be read verbatim from the input. If it was not quoted, we need to perform backslash-quoted newline removal. */ delim_unquoted = (temp->redirectee.filename->flags & W_QUOTED) == 0; while (full_line = read_secondary_line (delim_unquoted)) { register char *line; int len; line = full_line; line_number++; if (kill_leading && *line) { /* Hack: To be compatible with some Bourne shells, we check the word before stripping the whitespace. This is a hack, though. */ if (STREQN (line, redir_word, redir_len) && line[redir_len] == '\n') goto document_done; while (*line == '\t') line++; } if (*line == 0) continue; if (STREQN (line, redir_word, redir_len) && line[redir_len] == '\n') goto document_done; len = strlen (line); if (len + document_index >= document_size) { document_size = document_size ? 2 * (document_size + len) : len + 2; document = (char *)xrealloc (document, document_size); } /* len is guaranteed to be > 0 because of the check for line being an empty string before the call to strlen. */ FASTCOPY (line, document + document_index, len); document_index += len; } document_done: if (document) document[document_index] = '\0'; else { document = (char *)xmalloc (1); document[0] = '\0'; } temp->redirectee.filename->word = document; } /* Generate a REDIRECT from SOURCE, DEST, and INSTRUCTION. INSTRUCTION is the instruction type, SOURCE is a file descriptor, and DEST is a file descriptor or a WORD_DESC *. */ REDIRECT * make_redirection (source, instruction, dest_and_filename) int source; enum r_instruction instruction; REDIRECTEE dest_and_filename; { REDIRECT *temp = (REDIRECT *)xmalloc (sizeof (REDIRECT)); /* First do the common cases. */ temp->redirector = source; temp->redirectee = dest_and_filename; temp->instruction = instruction; temp->flags = 0; temp->next = (REDIRECT *)NULL; switch (instruction) { case r_output_direction: /* >foo */ case r_output_force: /* >| foo */ case r_err_and_out: /* command &>filename */ temp->flags = O_TRUNC | O_WRONLY | O_CREAT; break; case r_appending_to: /* >>foo */ temp->flags = O_APPEND | O_WRONLY | O_CREAT; break; case r_input_direction: /* flags = O_RDONLY; break; case r_input_output: /* <>foo */ temp->flags = O_RDWR | O_CREAT; break; case r_deblank_reading_until: /* <<-foo */ case r_reading_until: /* << foo */ case r_close_this: /* <&- */ case r_duplicating_input: /* 1<&2 */ case r_duplicating_output: /* 1>&2 */ case r_duplicating_input_word: /* 1<&$foo */ case r_duplicating_output_word: /* 1>&$foo */ break; default: programming_error ("make_redirection: redirection instruction `%d' out of range", instruction); abort (); break; } return (temp); } COMMAND * make_function_def (name, command, lineno, lstart) WORD_DESC *name; COMMAND *command; int lineno, lstart; { FUNCTION_DEF *temp; temp = (FUNCTION_DEF *)xmalloc (sizeof (FUNCTION_DEF)); temp->command = command; temp->name = name; temp->line = lineno; temp->flags = 0; command->line = lstart; return (make_command (cm_function_def, (SIMPLE_COM *)temp)); } COMMAND * make_subshell_command (command) COMMAND *command; { SUBSHELL_COM *temp; temp = (SUBSHELL_COM *)xmalloc (sizeof (SUBSHELL_COM)); temp->command = command; temp->flags = CMD_WANT_SUBSHELL; return (make_command (cm_subshell, (SIMPLE_COM *)temp)); } /* Reverse the word list and redirection list in the simple command has just been parsed. It seems simpler to do this here the one time then by any other method that I can think of. */ COMMAND * clean_simple_command (command) COMMAND *command; { if (command->type != cm_simple) command_error ("clean_simple_command", CMDERR_BADTYPE, command->type, 0); else { command->value.Simple->words = REVERSE_LIST (command->value.Simple->words, WORD_LIST *); command->value.Simple->redirects = REVERSE_LIST (command->value.Simple->redirects, REDIRECT *); } return (command); } /* The Yacc grammar productions have a problem, in that they take a list followed by an ampersand (`&') and do a simple command connection, making the entire list effectively asynchronous, instead of just the last command. This means that when the list is executed, all the commands have stdin set to /dev/null when job control is not active, instead of just the last. This is wrong, and needs fixing up. This function takes the `&' and applies it to the last command in the list. This is done only for lists connected by `;'; it makes `;' bind `tighter' than `&'. */ COMMAND * connect_async_list (command, command2, connector) COMMAND *command, *command2; int connector; { COMMAND *t, *t1, *t2; t1 = command; t = command->value.Connection->second; if (!t || (command->flags & CMD_WANT_SUBSHELL) || command->value.Connection->connector != ';') { t = command_connect (command, command2, connector); return t; } /* This is just defensive programming. The Yacc precedence rules will generally hand this function a command where t points directly to the command we want (e.g. given a ; b ; c ; d &, t1 will point to the `a ; b ; c' list and t will be the `d'). We only want to do this if the list is not being executed as a unit in the background with `( ... )', so we have to check for CMD_WANT_SUBSHELL. That's the only way to tell. */ while (((t->flags & CMD_WANT_SUBSHELL) == 0) && t->type == cm_connection && t->value.Connection->connector == ';') { t1 = t; t = t->value.Connection->second; } /* Now we have t pointing to the last command in the list, and t1->value.Connection->second == t. */ t2 = command_connect (t, command2, connector); t1->value.Connection->second = t2; return command; } /* * mksyntax.c - construct shell syntax table for fast char attribute lookup. */ /* 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. */ #include "config.h" #include #include "bashansi.h" #include "chartypes.h" #include #ifdef HAVE_UNISTD_H # include #endif #include "syntax.h" extern int optind; extern char *optarg; #ifndef errno extern int errno; #endif #ifndef HAVE_STRERROR extern char *strerror(); #endif struct wordflag { int flag; char *fstr; } wordflags[] = { { CWORD, "CWORD" }, { CSHMETA, "CSHMETA" }, { CSHBRK, "CSHBRK" }, { CBACKQ, "CBACKQ" }, { CQUOTE, "CQUOTE" }, { CSPECL, "CSPECL" }, { CEXP, "CEXP" }, { CBSDQUOTE, "CBSDQUOTE" }, { CBSHDOC, "CBSHDOC" }, { CGLOB, "CGLOB" }, { CXGLOB, "CXGLOB" }, { CXQUOTE, "CXQUOTE" }, { CSPECVAR, "CSPECVAR" } }; #define N_WFLAGS (sizeof (wordflags) / sizeof (wordflags[0])) #define SYNSIZE 256 int lsyntax[SYNSIZE]; int debug; char *progname; char preamble[] = "\ /*\n\ * This file was generated by mksyntax. DO NOT EDIT.\n\ */\n\ \n"; char includes[] = "\ #include \"stdc.h\"\n\ #include \"syntax.h\"\n\n"; static void usage() { fprintf (stderr, "%s: usage: %s [-d] [-o filename]\n", progname, progname); exit (2); } #ifdef INCLUDE_UNUSED static int getcflag (s) char *s; { int i; for (i = 0; i < N_WFLAGS; i++) if (strcmp (s, wordflags[i].fstr) == 0) return wordflags[i].flag; return -1; } #endif static char * cdesc (i) int i; { static char xbuf[16]; if (i == ' ') return "SPC"; else if (ISPRINT (i)) { xbuf[0] = i; xbuf[1] = '\0'; return (xbuf); } else if (i == CTLESC) return "CTLESC"; else if (i == CTLNUL) return "CTLNUL"; else if (i == '\033') /* ASCII */ return "ESC"; xbuf[0] = '\\'; xbuf[2] = '\0'; switch (i) { case '\a': xbuf[1] = 'a'; break; case '\v': xbuf[1] = 'v'; break; case '\b': xbuf[1] = 'b'; break; case '\f': xbuf[1] = 'f'; break; case '\n': xbuf[1] = 'n'; break; case '\r': xbuf[1] = 'r'; break; case '\t': xbuf[1] = 't'; break; default: sprintf (xbuf, "%d", i); break; } return xbuf; } static char * getcstr (f) int f; { int i; for (i = 0; i < N_WFLAGS; i++) if (f == wordflags[i].flag) return (wordflags[i].fstr); return ((char *)NULL); } static void addcstr (str, flag) char *str; int flag; { char *s, *fstr; unsigned char uc; for (s = str; s && *s; s++) { uc = *s; if (debug) { fstr = getcstr (flag); fprintf(stderr, "added %s for character %s\n", fstr, cdesc(uc)); } lsyntax[uc] |= flag; } } static void addcchar (c, flag) unsigned char c; int flag; { char *fstr; if (debug) { fstr = getcstr (flag); fprintf (stderr, "added %s for character %s\n", fstr, cdesc(c)); } lsyntax[c] |= flag; } /* load up the correct flag values in lsyntax */ static void load_lsyntax () { /* shell metacharacters */ addcstr (shell_meta_chars, CSHMETA); /* shell word break characters */ addcstr (shell_break_chars, CSHBRK); addcchar ('`', CBACKQ); addcstr (shell_quote_chars, CQUOTE); addcchar (CTLESC, CSPECL); addcchar (CTLNUL, CSPECL); addcstr (shell_exp_chars, CEXP); addcstr (slashify_in_quotes, CBSDQUOTE); addcstr (slashify_in_here_document, CBSHDOC); addcstr (shell_glob_chars, CGLOB); #if defined (EXTENDED_GLOB) addcstr (ext_glob_chars, CXGLOB); #endif addcstr (shell_quote_chars, CXQUOTE); addcchar ('\\', CXQUOTE); addcstr ("@*#?-$!", CSPECVAR); /* omits $0...$9 and $_ */ } static void dump_lflags (fp, ind) FILE *fp; int ind; { int xflags, first, i; xflags = lsyntax[ind]; first = 1; if (xflags == 0) fputs (wordflags[0].fstr, fp); else { for (i = 1; i < N_WFLAGS; i++) if (xflags & wordflags[i].flag) { if (first) first = 0; else putc ('|', fp); fputs (wordflags[i].fstr, fp); } } } static void wcomment (fp, i) FILE *fp; int i; { fputs ("\t\t/* ", fp); fprintf (fp, "%s", cdesc(i)); fputs (" */", fp); } static void dump_lsyntax (fp) FILE *fp; { int i; fprintf (fp, "const int sh_syntaxtab[%d] = {\n", SYNSIZE); for (i = 0; i < SYNSIZE; i++) { putc ('\t', fp); dump_lflags (fp, i); putc (',', fp); wcomment (fp, i); putc ('\n', fp); } fprintf (fp, "};\n"); } int main(argc, argv) int argc; char **argv; { int opt, i; char *filename; FILE *fp; if ((progname = strrchr (argv[0], '/')) == 0) progname = argv[0]; else progname++; filename = (char *)NULL; debug = 0; while ((opt = getopt (argc, argv, "do:")) != EOF) { switch (opt) { case 'd': debug = 1; break; case 'o': filename = optarg; break; default: usage(); } } argc -= optind; argv += optind; if (filename) { fp = fopen (filename, "w"); if (fp == 0) { fprintf (stderr, "%s: %s: cannot open: %s\n", progname, filename, strerror(errno)); exit (1); } } else { filename = "stdout"; fp = stdout; } for (i = 0; i < SYNSIZE; i++) lsyntax[i] = CWORD; load_lsyntax (); fprintf (fp, "%s\n", preamble); fprintf (fp, "%s\n", includes); dump_lsyntax (fp); if (fp != stdout) fclose (fp); exit (0); } #if !defined (HAVE_STRERROR) #include #ifndef _MINIX # include #endif #if defined (HAVE_UNISTD_H) # include #endif /* Return a string corresponding to the error number E. From the ANSI C spec. */ #if defined (strerror) # undef strerror #endif char * strerror (e) int e; { static char emsg[40]; #if defined (HAVE_SYS_ERRLIST) extern int sys_nerr; extern char *sys_errlist[]; if (e > 0 && e < sys_nerr) return (sys_errlist[e]); else #endif /* HAVE_SYS_ERRLIST */ { sprintf (emsg, "Unknown system error %d", e); return (&emsg[0]); } } #endif /* HAVE_STRERROR */ /* The thing that makes children, remembers them, and contains wait loops. */ /* This file works under BSD, System V, minix, and Posix systems. It does not implement job control. */ /* Copyright (C) 1987, 1989, 1992 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 "filecntl.h" #if defined (HAVE_UNISTD_H) # include #endif #include #include #include #if defined (BUFFERED_INPUT) # include "input.h" #endif /* Need to include this up here for *_TTY_DRIVER definitions. */ #include "shtty.h" #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 "shell.h" #include "jobs.h" #include "builtins/builtext.h" /* for wait_builtin */ #if !defined (CHILD_MAX) # define CHILD_MAX 32 #endif #if defined (_POSIX_VERSION) || !defined (HAVE_KILLPG) # define killpg(pg, sig) kill(-(pg),(sig)) #endif /* USG || _POSIX_VERSION */ #if !defined (HAVE_SIGINTERRUPT) # define siginterrupt(sig, code) #endif /* !HAVE_SIGINTERRUPT */ #if defined (HAVE_WAITPID) # define WAITPID(pid, statusp, options) waitpid (pid, statusp, options) #else # define WAITPID(pid, statusp, options) wait (statusp) #endif /* !HAVE_WAITPID */ /* Return the fd from which we are actually getting input. */ #define input_tty() (shell_tty != -1) ? shell_tty : fileno (stderr) #if !defined (errno) extern int errno; #endif /* !errno */ #if defined (READLINE) extern void rl_set_screen_size __P((int, int)); #endif extern int interactive, interactive_shell, login_shell; extern int subshell_environment; extern int last_command_exit_value; extern int interrupt_immediately; extern sh_builtin_func_t *this_shell_builtin; #if defined (HAVE_POSIX_SIGNALS) extern sigset_t top_level_mask; #endif extern procenv_t wait_intr_buf; pid_t last_made_pid = NO_PID; pid_t last_asynchronous_pid = NO_PID; /* Call this when you start making children. */ int already_making_children = 0; /* The controlling tty for this shell. */ int shell_tty = -1; /* If this is non-zero, $LINES and $COLUMNS are reset after every process exits from get_tty_state(). */ int check_window_size; /* STATUS and FLAGS are only valid if pid != NO_PID STATUS is only valid if (flags & PROC_RUNNING) == 0 */ struct proc_status { pid_t pid; int status; /* Exit status of PID or 128 + fatal signal number */ int flags; }; /* Values for proc_status.flags */ #define PROC_RUNNING 0x01 #define PROC_NOTIFIED 0x02 #define PROC_ASYNC 0x04 /* Return values from find_status_by_pid */ #define PROC_BAD -1 #define PROC_STILL_ALIVE -2 static struct proc_status *pid_list = (struct proc_status *)NULL; static int pid_list_size; static int wait_sigint_received; static void alloc_pid_list __P((void)); static int find_proc_slot __P((void)); static int find_index_by_pid __P((pid_t)); static int find_status_by_pid __P((pid_t)); static int process_exit_status __P((WAIT)); static void set_pid_status __P((pid_t, WAIT)); static void set_pid_flags __P((pid_t, int)); static void unset_pid_flags __P((pid_t, int)); static void add_pid __P((pid_t, int)); static void mark_dead_jobs_as_notified __P((int)); static void get_new_window_size __P((int)); static sighandler sigwinch_sighandler __P((int)); static sighandler wait_signal_handler __P((int)); #if defined (HAVE_WAITPID) static void reap_zombie_children __P((void)); #endif static void restore_sigint_handler __P((void)); /* Allocate new, or grow existing PID_LIST. */ static void alloc_pid_list () { register int i; int old = pid_list_size; pid_list_size += 10; pid_list = (struct proc_status *)xrealloc (pid_list, pid_list_size * sizeof (struct proc_status)); /* None of the newly allocated slots have process id's yet. */ for (i = old; i < pid_list_size; i++) pid_list[i].pid = NO_PID; } /* Return the offset within the PID_LIST array of an empty slot. This can create new slots if all of the existing slots are taken. */ static int find_proc_slot () { register int i; for (i = 0; i < pid_list_size; i++) if (pid_list[i].pid == NO_PID) return (i); if (i == pid_list_size) alloc_pid_list (); return (i); } /* Return the offset within the PID_LIST array of a slot containing PID, or the value NO_PID if the pid wasn't found. */ static int find_index_by_pid (pid) pid_t pid; { register int i; for (i = 0; i < pid_list_size; i++) if (pid_list[i].pid == pid) return (i); return (NO_PID); } /* Return the status of PID as looked up in the PID_LIST array. A return value of PROC_BAD indicates that PID wasn't found. */ static int find_status_by_pid (pid) pid_t pid; { int i; i = find_index_by_pid (pid); if (i == NO_PID) return (PROC_BAD); if (pid_list[i].flags & PROC_RUNNING) return (PROC_STILL_ALIVE); return (pid_list[i].status); } static int process_exit_status (status) WAIT status; { if (WIFSIGNALED (status)) return (128 + WTERMSIG (status)); else return (WEXITSTATUS (status)); } /* Give PID the status value STATUS in the PID_LIST array. */ static void set_pid_status (pid, status) pid_t pid; WAIT status; { int slot; slot = find_index_by_pid (pid); if (slot == NO_PID) return; pid_list[slot].status = process_exit_status (status); pid_list[slot].flags &= ~PROC_RUNNING; /* If it's not a background process, mark it as notified so it gets cleaned up. */ if ((pid_list[slot].flags & PROC_ASYNC) == 0) pid_list[slot].flags |= PROC_NOTIFIED; } /* Give PID the flags FLAGS in the PID_LIST array. */ static void set_pid_flags (pid, flags) pid_t pid; int flags; { int slot; slot = find_index_by_pid (pid); if (slot == NO_PID) return; pid_list[slot].flags |= flags; } /* Unset FLAGS for PID in the pid list */ static void unset_pid_flags (pid, flags) pid_t pid; int flags; { int slot; slot = find_index_by_pid (pid); if (slot == NO_PID) return; pid_list[slot].flags &= ~flags; } static void add_pid (pid, async) pid_t pid; int async; { int slot; slot = find_proc_slot (); pid_list[slot].pid = pid; pid_list[slot].status = -1; pid_list[slot].flags = PROC_RUNNING; if (async) pid_list[slot].flags |= PROC_ASYNC; } static void mark_dead_jobs_as_notified (force) int force; { register int i, ndead; /* first, count the number of non-running async jobs if FORCE == 0 */ for (i = ndead = 0; force == 0 && i < pid_list_size; i++) { if (pid_list[i].pid == NO_PID) continue; if (((pid_list[i].flags & PROC_RUNNING) == 0) && (pid_list[i].flags & PROC_ASYNC)) ndead++; } if (force == 0 && ndead <= CHILD_MAX) return; /* If FORCE == 0, we just mark as many non-running async jobs as notified to bring us under the CHILD_MAX limit. */ for (i = 0; i < pid_list_size; i++) { if (pid_list[i].pid == NO_PID) continue; if (((pid_list[i].flags & PROC_RUNNING) == 0) && pid_list[i].pid != last_asynchronous_pid) { pid_list[i].flags |= PROC_NOTIFIED; if (force == 0 && (pid_list[i].flags & PROC_ASYNC) && --ndead <= CHILD_MAX) break; } } } /* Remove all dead, notified jobs from the pid_list. */ int cleanup_dead_jobs () { register int i; #if defined (HAVE_WAITPID) reap_zombie_children (); #endif for (i = 0; i < pid_list_size; i++) { if ((pid_list[i].flags & PROC_RUNNING) == 0 && (pid_list[i].flags & PROC_NOTIFIED)) pid_list[i].pid = NO_PID; } return 0; } void reap_dead_jobs () { mark_dead_jobs_as_notified (0); cleanup_dead_jobs (); } /* Initialize the job control mechanism, and set up the tty stuff. */ initialize_job_control (force) int force; { shell_tty = fileno (stderr); if (interactive) get_tty_state (); } #if defined (TIOCGWINSZ) && defined (SIGWINCH) static SigHandler *old_winch = (SigHandler *)SIG_DFL; static void get_new_window_size (from_sig) int from_sig; { struct winsize win; int tty; tty = input_tty (); if (tty >= 0 && (ioctl (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); } #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 () { set_signal_handler (SIGINT, sigint_sighandler); set_sigwinch_handler (); /* If this is a login shell we don't wish to be disturbed by stop signals. */ if (login_shell) ignore_tty_job_signals (); } #if defined (HAVE_WAITPID) /* Collect the status of all zombie children so that their system resources can be deallocated. */ static void reap_zombie_children () { #if defined (WNOHANG) pid_t pid; WAIT status; while ((pid = waitpid (-1, (int *)&status, WNOHANG)) > 0) set_pid_status (pid, status); #endif } #endif /* WAITPID && WNOHANG */ /* 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; { pid_t pid; #if defined (HAVE_WAITPID) int retry = 1; #endif /* HAVE_WAITPID */ /* Discard saved memory. */ if (command) free (command); start_pipeline (); #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 defined (HAVE_WAITPID) retry_fork: #endif /* HAVE_WAITPID */ if ((pid = fork ()) < 0) { #if defined (HAVE_WAITPID) /* Posix systems with a non-blocking waitpid () system call available get another chance after zombies are reaped. */ if (errno == EAGAIN && retry) { reap_zombie_children (); retry = 0; goto retry_fork; } #endif /* HAVE_WAITPID */ sys_error ("fork"); throw_to_top_level (); } if (pid == 0) { #if defined (BUFFERED_INPUT) unset_bash_input (0); #endif /* BUFFERED_INPUT */ #if defined (HAVE_POSIX_SIGNALS) /* Restore top-level signal mask. */ sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); #endif /* Ignore INT and QUIT in asynchronous children. */ if (async_p) last_asynchronous_pid = getpid (); default_tty_job_signals (); } else { /* In the parent. */ last_made_pid = pid; if (async_p) last_asynchronous_pid = pid; add_pid (pid, async_p); } return (pid); } void ignore_tty_job_signals () { #if defined (SIGTSTP) set_signal_handler (SIGTSTP, SIG_IGN); set_signal_handler (SIGTTIN, SIG_IGN); set_signal_handler (SIGTTOU, SIG_IGN); #endif } void default_tty_job_signals () { #if defined (SIGTSTP) set_signal_handler (SIGTSTP, SIG_DFL); set_signal_handler (SIGTTIN, SIG_DFL); set_signal_handler (SIGTTOU, SIG_DFL); #endif } /* Wait for a single pid (PID) and return its exit status. Called by the wait builtin. */ int wait_for_single_pid (pid) pid_t pid; { pid_t got_pid; WAIT status; int pstatus; pstatus = find_status_by_pid (pid); if (pstatus == PROC_BAD) { internal_error ("wait: pid %ld is not a child of this shell", (long)pid); return (127); } if (pstatus != PROC_STILL_ALIVE) return (pstatus); siginterrupt (SIGINT, 1); while ((got_pid = WAITPID (pid, &status, 0)) != pid) { if (got_pid < 0) { if (errno != EINTR && errno != ECHILD) { siginterrupt (SIGINT, 0); sys_error ("wait"); } break; } else if (got_pid > 0) set_pid_status (got_pid, status); } set_pid_status (got_pid, status); set_pid_flags (got_pid, PROC_NOTIFIED); siginterrupt (SIGINT, 0); QUIT; return (process_exit_status (status)); } /* Wait for all of the shell's children to exit. Called by the `wait' builtin. */ void wait_for_background_pids () { pid_t got_pid; WAIT status; /* If we aren't using job control, we let the kernel take care of the bookkeeping for us. wait () will return -1 and set errno to ECHILD when there are no more unwaited-for child processes on both 4.2 BSD-based and System V-based systems. */ siginterrupt (SIGINT, 1); /* Wait for ECHILD */ while ((got_pid = WAITPID (-1, &status, 0)) != -1) set_pid_status (got_pid, status); if (errno != EINTR && errno != ECHILD) { siginterrupt (SIGINT, 0); sys_error("wait"); } siginterrupt (SIGINT, 0); QUIT; 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; } } /* Handle SIGINT while we are waiting for children in a script to exit. All interrupts are effectively ignored by the shell, but allowed to kill a running job. */ static sighandler wait_sigint_handler (sig) int sig; { SigHandler *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)) { last_command_exit_value = EXECUTION_FAILURE; restore_sigint_handler (); interrupt_immediately = 0; trap_handler (SIGINT); /* set pending_traps[SIGINT] */ longjmp (wait_intr_buf, 1); } if (interrupt_immediately) { last_command_exit_value = EXECUTION_FAILURE; restore_sigint_handler (); ADDINTERRUPT; QUIT; } wait_sigint_received = 1; SIGRETURN (0); } /* Wait for pid (one of our children) to terminate. This is called only by the execution code in execute_cmd.c. */ int wait_for (pid) pid_t pid; { int return_val, pstatus; pid_t got_pid; WAIT status; pstatus = find_status_by_pid (pid); if (pstatus == PROC_BAD) return (0); if (pstatus != PROC_STILL_ALIVE) return (pstatus); /* If we are running a script, ignore SIGINT while we're waiting for a child to exit. The loop below does some of this, but not all. */ wait_sigint_received = 0; if (interactive_shell == 0) old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler); while ((got_pid = WAITPID (-1, &status, 0)) != pid) /* XXX was pid now -1 */ { if (got_pid < 0 && errno == ECHILD) { #if !defined (_POSIX_VERSION) status.w_termsig = status.w_retcode = 0; #else status = 0; #endif /* _POSIX_VERSION */ break; } else if (got_pid < 0 && errno != EINTR) programming_error ("wait_for(%ld): %s", (long)pid, strerror(errno)); else if (got_pid > 0) set_pid_status (got_pid, status); } set_pid_status (got_pid, status); #if defined (HAVE_WAITPID) if (got_pid >= 0) reap_zombie_children (); #endif /* HAVE_WAITPID */ if (interactive_shell == 0) { SigHandler *temp_handler; temp_handler = old_sigint_handler; restore_sigint_handler (); /* If the job exited because of SIGINT, make sure the shell acts as if it had received one also. */ if (WIFSIGNALED (status) && (WTERMSIG (status) == SIGINT)) { if (maybe_call_trap_handler (SIGINT) == 0) { if (temp_handler == SIG_DFL) termination_unwind_protect (SIGINT); else if (temp_handler != INVALID_SIGNAL_HANDLER && temp_handler != SIG_IGN) (*temp_handler) (SIGINT); } } } /* Default return value. */ /* ``a full 8 bits of status is returned'' */ return_val = process_exit_status (status); #if !defined (DONT_REPORT_SIGPIPE) if ((WIFSTOPPED (status) == 0) && WIFSIGNALED (status) && (WTERMSIG (status) != SIGINT)) #else if ((WIFSTOPPED (status) == 0) && WIFSIGNALED (status) && (WTERMSIG (status) != SIGINT) && (WTERMSIG (status) != SIGPIPE)) #endif { fprintf (stderr, "%s", strsignal (WTERMSIG (status))); if (WIFCORED (status)) fprintf (stderr, " (core dumped)"); fprintf (stderr, "\n"); } if (interactive_shell && subshell_environment == 0) { if (WIFSIGNALED (status) || WIFSTOPPED (status)) set_tty_state (); else get_tty_state (); } return (return_val); } /* 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, signal, group) pid_t pid; int signal, group; { int result; result = group ? killpg (pid, signal) : kill (pid, signal); return (result); } static TTYSTRUCT shell_tty_info; static int got_tty_state; /* Fill the contents of shell_tty_info with the current tty info. */ get_tty_state () { int tty; tty = input_tty (); if (tty != -1) { ttgetattr (tty, &shell_tty_info); got_tty_state = 1; if (check_window_size) get_new_window_size (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 (got_tty_state == 0) return 0; ttsetattr (tty, &shell_tty_info); } return 0; } /* Give the terminal to PGRP. */ give_terminal_to (pgrp, force) pid_t pgrp; int force; { } /* Stop a pipeline. */ int stop_pipeline (async, ignore) int async; COMMAND *ignore; { already_making_children = 0; return 0; } void start_pipeline () { already_making_children = 1; } void stop_making_children () { already_making_children = 0; } int get_job_by_pid (pid, block) pid_t pid; int block; { int i; i = find_index_by_pid (pid); return ((i == NO_PID) ? PROC_BAD : i); } /* Print descriptive information about the job with leader pid PID. */ void describe_pid (pid) pid_t pid; { fprintf (stderr, "%ld\n", (long) pid); } void unfreeze_jobs_list () { } int count_all_jobs () { return 0; } /* pathexp.c -- The shell interface to the globbing library. */ /* Copyright (C) 1995 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 #if defined (HAVE_UNISTD_H) # include #endif #include "bashansi.h" #include "shell.h" #include "pathexp.h" #include "flags.h" #include #if defined (USE_POSIX_GLOB_LIBRARY) # include typedef int posix_glob_errfunc_t __P((const char *, int)); #else # include #endif /* Control whether * matches .files in globbing. */ int glob_dot_filenames; /* Control whether the extended globbing features are enabled. */ int extended_glob = 0; /* Return nonzero if STRING has any unquoted special globbing chars in it. */ int unquoted_glob_pattern_p (string) register char *string; { register int c; int open; open = 0; while (c = *string++) { switch (c) { case '?': case '*': return (1); case '[': open++; continue; case ']': if (open) return (1); continue; case '+': case '@': case '!': if (*string == '(') /*)*/ return (1); continue; case CTLESC: case '\\': if (*string++ == '\0') return (0); } } return (0); } /* PATHNAME can contain characters prefixed by CTLESC; this indicates that the character is to be quoted. We quote it here in the style that the glob library recognizes. If flags includes QGLOB_CVTNULL, we change quoted null strings (pathname[0] == CTLNUL) into empty strings (pathname[0] == 0). If this is called after quote removal is performed, (flags & QGLOB_CVTNULL) should be 0; if called when quote removal has not been done (for example, before attempting to match a pattern while executing a case statement), flags should include QGLOB_CVTNULL. If flags includes QGLOB_FILENAME, appropriate quoting to match a filename should be performed. */ char * quote_string_for_globbing (pathname, qflags) const char *pathname; int qflags; { char *temp; register int i, j; temp = (char *)xmalloc (strlen (pathname) + 1); if ((qflags & QGLOB_CVTNULL) && QUOTED_NULL (pathname)) { temp[0] = '\0'; return temp; } for (i = j = 0; pathname[i]; i++) { if (pathname[i] == CTLESC) { if ((qflags & QGLOB_FILENAME) && pathname[i+1] == '/') continue; temp[j++] = '\\'; } else temp[j++] = pathname[i]; } temp[j] = '\0'; return (temp); } char * quote_globbing_chars (string) char *string; { char *temp, *s, *t; temp = (char *)xmalloc (strlen (string) * 2 + 1); for (t = temp, s = string; *s; ) { switch (*s) { case '*': case '[': case ']': case '?': case '\\': *t++ = '\\'; break; case '+': case '@': case '!': if (s[1] == '(') /*(*/ *t++ = '\\'; break; } *t++ = *s++; } *t = '\0'; return temp; } /* Call the glob library to do globbing on PATHNAME. */ char ** shell_glob_filename (pathname) const char *pathname; { #if defined (USE_POSIX_GLOB_LIBRARY) register int i; char *temp, **results; glob_t filenames; int glob_flags; temp = quote_string_for_globbing (pathname, QGLOB_FILENAME); filenames.gl_offs = 0; # if defined (GLOB_PERIOD) glob_flags = glob_dot_filenames ? GLOB_PERIOD : 0; # else glob_flags = 0; # endif /* !GLOB_PERIOD */ glob_flags |= (GLOB_ERR | GLOB_DOOFFS); i = glob (temp, glob_flags, (posix_glob_errfunc_t *)NULL, &filenames); free (temp); if (i == GLOB_NOSPACE || i == GLOB_ABORTED) return ((char **)NULL); else if (i == GLOB_NOMATCH) filenames.gl_pathv = (char **)NULL; else if (i != 0) /* other error codes not in POSIX.2 */ filenames.gl_pathv = (char **)NULL; results = filenames.gl_pathv; if (results && ((GLOB_FAILED (results)) == 0)) { if (should_ignore_glob_matches ()) ignore_glob_matches (results); if (results && results[0]) sort_char_array (results); else { FREE (results); results = (char **)NULL; } } return (results); #else /* !USE_POSIX_GLOB_LIBRARY */ char *temp, **results; noglob_dot_filenames = glob_dot_filenames == 0; temp = quote_string_for_globbing (pathname, QGLOB_FILENAME); results = glob_filename (temp); free (temp); if (results && ((GLOB_FAILED (results)) == 0)) { if (should_ignore_glob_matches ()) ignore_glob_matches (results); if (results && results[0]) sort_char_array (results); else { FREE (results); results = (char **)&glob_error_return; } } return (results); #endif /* !USE_POSIX_GLOB_LIBRARY */ } /* Stuff for GLOBIGNORE. */ static struct ignorevar globignore = { "GLOBIGNORE", (struct ign *)0, 0, (char *)0, (sh_iv_item_func_t *)0, }; /* Set up to ignore some glob matches because the value of GLOBIGNORE has changed. If GLOBIGNORE is being unset, we also need to disable the globbing of filenames beginning with a `.'. */ void setup_glob_ignore (name) char *name; { char *v; v = get_string_value (name); setup_ignore_patterns (&globignore); if (globignore.num_ignores) glob_dot_filenames = 1; else if (v == 0) glob_dot_filenames = 0; } int should_ignore_glob_matches () { return globignore.num_ignores; } /* Return 0 if NAME matches a pattern in the globignore.ignores list. */ static int glob_name_is_acceptable (name) const char *name; { struct ign *p; int flags; /* . and .. are never matched */ if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) return (0); flags = FNM_PATHNAME | FNMATCH_EXTFLAG; for (p = globignore.ignores; p->val; p++) { if (strmatch (p->val, (char *)name, flags) != FNM_NOMATCH) return (0); } return (1); } /* Internal function to test whether filenames in NAMES should be ignored. NAME_FUNC is a pointer to a function to call with each name. It returns non-zero if the name is acceptable to the particular ignore function which called _ignore_names; zero if the name should be removed from NAMES. */ static void ignore_globbed_names (names, name_func) char **names; sh_ignore_func_t *name_func; { char **newnames; int n, i; for (i = 0; names[i]; i++) ; newnames = alloc_array (i + 1); for (n = i = 0; names[i]; i++) { if ((*name_func) (names[i])) newnames[n++] = names[i]; else free (names[i]); } newnames[n] = (char *)NULL; if (n == 0) { names[0] = (char *)NULL; free (newnames); return; } /* Copy the acceptable names from NEWNAMES back to NAMES and set the new array end. */ for (n = 0; newnames[n]; n++) names[n] = newnames[n]; names[n] = (char *)NULL; free (newnames); } void ignore_glob_matches (names) char **names; { if (globignore.num_ignores == 0) return; ignore_globbed_names (names, glob_name_is_acceptable); } void setup_ignore_patterns (ivp) struct ignorevar *ivp; { int numitems, maxitems, ptr; char *colon_bit, *this_ignoreval; struct ign *p; this_ignoreval = get_string_value (ivp->varname); /* If nothing has changed then just exit now. */ if ((this_ignoreval && ivp->last_ignoreval && STREQ (this_ignoreval, ivp->last_ignoreval)) || (!this_ignoreval && !ivp->last_ignoreval)) return; /* Oops. The ignore variable has changed. Re-parse it. */ ivp->num_ignores = 0; if (ivp->ignores) { for (p = ivp->ignores; p->val; p++) free(p->val); free (ivp->ignores); ivp->ignores = (struct ign *)NULL; } if (ivp->last_ignoreval) { free (ivp->last_ignoreval); ivp->last_ignoreval = (char *)NULL; } if (this_ignoreval == 0 || *this_ignoreval == '\0') return; ivp->last_ignoreval = savestring (this_ignoreval); numitems = maxitems = ptr = 0; while (colon_bit = extract_colon_unit (this_ignoreval, &ptr)) { if (numitems + 1 >= maxitems) { maxitems += 10; ivp->ignores = (struct ign *)xrealloc (ivp->ignores, maxitems * sizeof (struct ign)); } ivp->ignores[numitems].val = colon_bit; ivp->ignores[numitems].len = strlen (colon_bit); ivp->ignores[numitems].flags = 0; if (ivp->item_func) (*ivp->item_func) (&ivp->ignores[numitems]); numitems++; } ivp->ignores[numitems].val = (char *)NULL; ivp->num_ignores = numitems; } /* pcomplete.c - functions to generate lists of matches for programmable completion. */ /* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 #if defined (PROGRAMMABLE_COMPLETION) #include "bashtypes.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include #endif #include #if defined (PREFER_STDARG) # include #else # if defined (PREFER_VARARGS) # include # endif #endif #include #include "bashansi.h" #include "shell.h" #include "pcomplete.h" #include "alias.h" #include "bashline.h" #include "execute_cmd.h" #include "pathexp.h" #if defined (JOB_CONTROL) # include "jobs.h" #endif #if !defined (NSIG) # include "trap.h" #endif #include "builtins.h" #include "builtins/common.h" #include #include #include #include #include #ifdef STRDUP # undef STRDUP #endif #define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) typedef SHELL_VAR **SVFUNC (); #ifndef HAVE_STRPBRK extern char *strpbrk __P((char *, char *)); #endif extern int array_needs_making; extern STRING_INT_ALIST word_token_alist[]; extern char *signal_names[]; #if defined(PREFER_STDARG) static void debug_printf (const char *, ...) __attribute__((__format__ (printf, 1, 2))); #endif static int it_init_joblist __P((ITEMLIST *, int)); static int it_init_aliases __P((ITEMLIST *)); static int it_init_arrayvars __P((ITEMLIST *)); static int it_init_bindings __P((ITEMLIST *)); static int it_init_builtins __P((ITEMLIST *)); static int it_init_disabled __P((ITEMLIST *)); static int it_init_enabled __P((ITEMLIST *)); static int it_init_exported __P((ITEMLIST *)); static int it_init_functions __P((ITEMLIST *)); static int it_init_hostnames __P((ITEMLIST *)); static int it_init_jobs __P((ITEMLIST *)); static int it_init_running __P((ITEMLIST *)); static int it_init_stopped __P((ITEMLIST *)); static int it_init_keywords __P((ITEMLIST *)); static int it_init_signals __P((ITEMLIST *)); static int it_init_variables __P((ITEMLIST *)); static int it_init_setopts __P((ITEMLIST *)); static int it_init_shopts __P((ITEMLIST *)); static int shouldexp_filterpat __P((char *)); static char *preproc_filterpat __P((char *, char *)); static void init_itemlist_from_varlist __P((ITEMLIST *, SVFUNC *)); static STRINGLIST *gen_matches_from_itemlist __P((ITEMLIST *, const char *)); static STRINGLIST *gen_action_completions __P((COMPSPEC *, const char *)); static STRINGLIST *gen_globpat_matches __P((COMPSPEC *, const char *)); static STRINGLIST *gen_wordlist_matches __P((COMPSPEC *, const char *)); static STRINGLIST *gen_shell_function_matches __P((COMPSPEC *, const char *, char *, int, WORD_LIST *, int, int)); static STRINGLIST *gen_command_matches __P((COMPSPEC *, const char *, char *, int, WORD_LIST *, int, int)); static char *pcomp_filename_completion_function __P((const char *, int)); #if defined (ARRAY_VARS) static SHELL_VAR *bind_comp_words __P((WORD_LIST *)); #endif static void bind_compfunc_variables __P((char *, int, WORD_LIST *, int, int)); static void unbind_compfunc_variables __P((int)); static WORD_LIST *build_arg_list __P((char *, const char *, WORD_LIST *, int)); static WORD_LIST *command_line_to_word_list __P((char *, int, int, int *, int *)); static int progcomp_debug = 0; int prog_completion_enabled = 1; /* These are used to manage the arrays of strings for possible completions. */ ITEMLIST it_aliases = { 0, it_init_aliases, (STRINGLIST *)0 }; ITEMLIST it_arrayvars = { LIST_DYNAMIC, it_init_arrayvars, (STRINGLIST *)0 }; ITEMLIST it_bindings = { 0, it_init_bindings, (STRINGLIST *)0 }; ITEMLIST it_builtins = { 0, it_init_builtins, (STRINGLIST *)0 }; ITEMLIST it_commands = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_directories = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_disabled = { 0, it_init_disabled, (STRINGLIST *)0 }; ITEMLIST it_enabled = { 0, it_init_enabled, (STRINGLIST *)0 }; ITEMLIST it_exports = { LIST_DYNAMIC, it_init_exported, (STRINGLIST *)0 }; ITEMLIST it_files = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_functions = { 0, it_init_functions, (STRINGLIST *)0 }; ITEMLIST it_hostnames = { LIST_DYNAMIC, it_init_hostnames, (STRINGLIST *)0 }; ITEMLIST it_groups = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_jobs = { LIST_DYNAMIC, it_init_jobs, (STRINGLIST *)0 }; ITEMLIST it_keywords = { 0, it_init_keywords, (STRINGLIST *)0 }; ITEMLIST it_running = { LIST_DYNAMIC, it_init_running, (STRINGLIST *)0 }; ITEMLIST it_setopts = { 0, it_init_setopts, (STRINGLIST *)0 }; ITEMLIST it_shopts = { 0, it_init_shopts, (STRINGLIST *)0 }; ITEMLIST it_signals = { 0, it_init_signals, (STRINGLIST *)0 }; ITEMLIST it_stopped = { LIST_DYNAMIC, it_init_stopped, (STRINGLIST *)0 }; ITEMLIST it_users = { LIST_DYNAMIC }; /* unused */ ITEMLIST it_variables = { LIST_DYNAMIC, it_init_variables, (STRINGLIST *)0 }; /* Debugging code */ #if !defined (USE_VARARGS) static void debug_printf (format, arg1, arg2, arg3, arg4, arg5) char *format; { if (progcomp_debug == 0) return; fprintf (stdout, format, arg1, arg2, arg3, arg4, arg5); fprintf (stdout, "\n"); rl_on_new_line (); } #else static void #if defined (PREFER_STDARG) debug_printf (const char *format, ...) #else debug_printf (format, va_alist) const char *format; va_dcl #endif { va_list args; if (progcomp_debug == 0) return; #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif fprintf (stdout, "DEBUG: "); vfprintf (stdout, format, args); fprintf (stdout, "\n"); rl_on_new_line (); va_end (args); } #endif /* USE_VARARGS */ /* Functions to manage the item lists */ void set_itemlist_dirty (it) ITEMLIST *it; { it->flags |= LIST_DIRTY; } void initialize_itemlist (itp) ITEMLIST *itp; { (*itp->list_getter) (itp); itp->flags |= LIST_INITIALIZED; itp->flags &= ~LIST_DIRTY; } void clean_itemlist (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = itp->slist; if (sl) { if ((itp->flags & (LIST_DONTFREEMEMBERS|LIST_DONTFREE)) == 0) free_array_members (sl->list); if ((itp->flags & LIST_DONTFREE) == 0) free (sl->list); free (sl); } itp->slist = (STRINGLIST *)NULL; itp->flags &= ~(LIST_DONTFREE|LIST_DONTFREEMEMBERS|LIST_INITIALIZED|LIST_DIRTY); } static int shouldexp_filterpat (s) char *s; { register char *p; for (p = s; p && *p; p++) { if (*p == '\\') p++; else if (*p == '&') return 1; } return 0; } /* Replace any instance of `&' in PAT with TEXT. Backslash may be used to quote a `&' and inhibit substitution. Returns a new string. This just calls stringlib.c:strcreplace(). */ static char * preproc_filterpat (pat, text) char *pat; char *text; { char *ret; ret = strcreplace (pat, '&', text, 1); return ret; } /* Remove any match of FILTERPAT from SL. A `&' in FILTERPAT is replaced by TEXT. A leading `!' in FILTERPAT negates the pattern; in this case any member of SL->list that does *not* match will be removed. This returns a new STRINGLIST with the matching members of SL *copied*. Any non-matching members of SL->list are *freed*. */ STRINGLIST * filter_stringlist (sl, filterpat, text) STRINGLIST *sl; char *filterpat, *text; { int i, m, not; STRINGLIST *ret; char *npat, *t; if (sl == 0 || sl->list == 0 || sl->list_len == 0) return sl; npat = shouldexp_filterpat (filterpat) ? preproc_filterpat (filterpat, text) : filterpat; not = (npat[0] == '!'); t = not ? npat + 1 : npat; ret = alloc_stringlist (sl->list_size); for (i = 0; i < sl->list_len; i++) { m = strmatch (t, sl->list[i], FNMATCH_EXTFLAG); if ((not && m == FNM_NOMATCH) || (not == 0 && m != FNM_NOMATCH)) free (sl->list[i]); else ret->list[ret->list_len++] = sl->list[i]; } ret->list[ret->list_len] = (char *)NULL; if (npat != filterpat) free (npat); return ret; } /* Turn an array of strings returned by rl_completion_matches into a STRINGLIST. This understands how rl_completion_matches sets matches[0] (the lcd of the strings in the list, unless it's the only match). */ STRINGLIST * completions_to_stringlist (matches) char **matches; { STRINGLIST *sl; int mlen, i, n; mlen = (matches == 0) ? 0 : array_len (matches); sl = alloc_stringlist (mlen + 1); if (matches == 0 || matches[0] == 0) return sl; if (matches[1] == 0) { sl->list[0] = STRDUP (matches[0]); sl->list[sl->list_len = 1] = (char *)NULL; return sl; } for (i = 1, n = 0; i < mlen; i++, n++) sl->list[n] = STRDUP (matches[i]); sl->list_len = n; sl->list[n] = (char *)NULL; return sl; } /* Functions to manage the various ITEMLISTs that we populate internally. The caller is responsible for setting ITP->flags correctly. */ static int it_init_aliases (itp) ITEMLIST *itp; { #ifdef ALIAS alias_t **alias_list; register int i, n; STRINGLIST *sl; alias_list = all_aliases (); if (alias_list == 0) { itp->slist = (STRINGLIST *)NULL; return 0; } for (n = 0; alias_list[n]; n++) ; sl = alloc_stringlist (n+1); for (i = 0; i < n; i++) sl->list[i] = STRDUP (alias_list[i]->name); sl->list[n] = (char *)NULL; sl->list_size = sl->list_len = n; itp->slist = sl; #else itp->slist = (STRINGLIST *)NULL; #endif return 1; } static void init_itemlist_from_varlist (itp, svfunc) ITEMLIST *itp; SVFUNC *svfunc; { SHELL_VAR **vlist; STRINGLIST *sl; register int i, n; vlist = (*svfunc) (); for (n = 0; vlist[n]; n++) ; sl = alloc_stringlist (n+1); for (i = 0; i < n; i++) sl->list[i] = savestring (vlist[i]->name); sl->list[sl->list_len = n] = (char *)NULL; itp->slist = sl; } static int it_init_arrayvars (itp) ITEMLIST *itp; { #if defined (ARRAY_VARS) init_itemlist_from_varlist (itp, all_array_variables); return 1; #else return 0; #endif } static int it_init_bindings (itp) ITEMLIST *itp; { char **blist; STRINGLIST *sl; /* rl_funmap_names allocates blist, but not its members */ blist = (char **)rl_funmap_names (); /* XXX fix const later */ sl = alloc_stringlist (0); sl->list = blist; sl->list_size = 0; sl->list_len = array_len (sl->list); itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_builtins (itp) ITEMLIST *itp; { STRINGLIST *sl; register int i, n; sl = alloc_stringlist (num_shell_builtins); for (i = n = 0; i < num_shell_builtins; i++) if (shell_builtins[i].function) sl->list[n++] = shell_builtins[i].name; sl->list[sl->list_len = n] = (char *)NULL; itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_enabled (itp) ITEMLIST *itp; { STRINGLIST *sl; register int i, n; sl = alloc_stringlist (num_shell_builtins); for (i = n = 0; i < num_shell_builtins; i++) { if (shell_builtins[i].function && (shell_builtins[i].flags & BUILTIN_ENABLED)) sl->list[n++] = shell_builtins[i].name; } sl->list[sl->list_len = n] = (char *)NULL; itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_disabled (itp) ITEMLIST *itp; { STRINGLIST *sl; register int i, n; sl = alloc_stringlist (num_shell_builtins); for (i = n = 0; i < num_shell_builtins; i++) { if (shell_builtins[i].function && ((shell_builtins[i].flags & BUILTIN_ENABLED) == 0)) sl->list[n++] = shell_builtins[i].name; } sl->list[sl->list_len = n] = (char *)NULL; itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_exported (itp) ITEMLIST *itp; { init_itemlist_from_varlist (itp, all_exported_variables); return 0; } static int it_init_functions (itp) ITEMLIST *itp; { init_itemlist_from_varlist (itp, all_visible_functions); return 0; } static int it_init_hostnames (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = alloc_stringlist (0); sl->list = get_hostname_list (); sl->list_len = sl->list ? array_len (sl->list) : 0; sl->list_size = sl->list_len; itp->slist = sl; itp->flags |= LIST_DONTFREEMEMBERS|LIST_DONTFREE; return 0; } static int it_init_joblist (itp, jstate) ITEMLIST *itp; int jstate; { #if defined (JOB_CONTROL) STRINGLIST *sl; register int i; register PROCESS *p; char *s, *t; JOB_STATE js; if (jstate == 0) js = JRUNNING; else if (jstate == 1) js = JSTOPPED; sl = alloc_stringlist (job_slots); for (i = job_slots - 1; i >= 0; i--) { if (jobs[i] == 0) continue; p = jobs[i]->pipe; if (jstate == -1 || JOBSTATE(i) == js) { s = savestring (p->command); t = strpbrk (s, " \t\n"); if (t) *t = '\0'; sl->list[sl->list_len++] = s; } } itp->slist = sl; #else itp->slist = (STRINGLIST *)NULL; #endif return 0; } static int it_init_jobs (itp) ITEMLIST *itp; { return (it_init_joblist (itp, -1)); } static int it_init_running (itp) ITEMLIST *itp; { return (it_init_joblist (itp, 0)); } static int it_init_stopped (itp) ITEMLIST *itp; { return (it_init_joblist (itp, 1)); } static int it_init_keywords (itp) ITEMLIST *itp; { STRINGLIST *sl; register int i, n; for (n = 0; word_token_alist[n].word; n++) ; sl = alloc_stringlist (n); for (i = 0; i < n; i++) sl->list[i] = word_token_alist[i].word; sl->list[sl->list_len = i] = (char *)NULL; itp->flags |= LIST_DONTFREEMEMBERS; itp->slist = sl; return 0; } static int it_init_signals (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = alloc_stringlist (0); sl->list = signal_names; sl->list_len = array_len (sl->list); itp->flags |= LIST_DONTFREE; itp->slist = sl; return 0; } static int it_init_variables (itp) ITEMLIST *itp; { init_itemlist_from_varlist (itp, all_visible_variables); return 0; } static int it_init_setopts (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = alloc_stringlist (0); sl->list = get_minus_o_opts (); sl->list_len = array_len (sl->list); itp->slist = sl; itp->flags |= LIST_DONTFREEMEMBERS; return 0; } static int it_init_shopts (itp) ITEMLIST *itp; { STRINGLIST *sl; sl = alloc_stringlist (0); sl->list = get_shopt_options (); sl->list_len = array_len (sl->list); itp->slist = sl; itp->flags |= LIST_DONTFREEMEMBERS; return 0; } /* Generate a list of all matches for TEXT using the STRINGLIST in itp->slist as the list of possibilities. If the itemlist has been marked dirty or it should be regenerated every time, destroy the old STRINGLIST and make a new one before trying the match. */ static STRINGLIST * gen_matches_from_itemlist (itp, text) ITEMLIST *itp; const char *text; { STRINGLIST *ret, *sl; int tlen, i, n; if ((itp->flags & (LIST_DIRTY|LIST_DYNAMIC)) || (itp->flags & LIST_INITIALIZED) == 0) { if (itp->flags & (LIST_DIRTY | LIST_DYNAMIC)) clean_itemlist (itp); if ((itp->flags & LIST_INITIALIZED) == 0) initialize_itemlist (itp); } if (itp->slist == 0) return ((STRINGLIST *)NULL); ret = alloc_stringlist (itp->slist->list_len+1); sl = itp->slist; tlen = STRLEN (text); for (i = n = 0; i < sl->list_len; i++) { if (tlen == 0 || STREQN (sl->list[i], text, tlen)) ret->list[n++] = STRDUP (sl->list[i]); } ret->list[ret->list_len = n] = (char *)NULL; return ret; } /* A wrapper for rl_filename_completion_function that dequotes the filename before attempting completions. */ static char * pcomp_filename_completion_function (text, state) const char *text; int state; { static char *dfn; /* dequoted filename */ int qc; if (state == 0) { FREE (dfn); /* remove backslashes quoting special characters in filenames. */ if (rl_filename_dequoting_function) { qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0; dfn = (*rl_filename_dequoting_function) ((char *)text, qc); } else dfn = savestring (text); } return (rl_filename_completion_function (dfn, state)); } #define GEN_COMPS(bmap, flag, it, text, glist, tlist) \ do { \ if (bmap & flag) \ { \ tlist = gen_matches_from_itemlist (it, text); \ if (tlist) \ { \ glist = append_stringlist (glist, tlist); \ free_stringlist (tlist); \ } \ } \ } while (0) #define GEN_XCOMPS(bmap, flag, text, func, cmatches, glist, tlist) \ do { \ if (bmap & flag) \ { \ cmatches = rl_completion_matches (text, func); \ tlist = completions_to_stringlist (cmatches); \ glist = append_stringlist (glist, tlist); \ free_array (cmatches); \ free_stringlist (tlist); \ } \ } while (0) /* Functions to generate lists of matches from the actions member of CS. */ static STRINGLIST * gen_action_completions (cs, text) COMPSPEC *cs; const char *text; { STRINGLIST *ret, *tmatches; char **cmatches; /* from rl_completion_matches ... */ unsigned long flags; ret = tmatches = (STRINGLIST *)NULL; flags = cs->actions; GEN_COMPS (flags, CA_ALIAS, &it_aliases, text, ret, tmatches); GEN_COMPS (flags, CA_ARRAYVAR, &it_arrayvars, text, ret, tmatches); GEN_COMPS (flags, CA_BINDING, &it_bindings, text, ret, tmatches); GEN_COMPS (flags, CA_BUILTIN, &it_builtins, text, ret, tmatches); GEN_COMPS (flags, CA_DISABLED, &it_disabled, text, ret, tmatches); GEN_COMPS (flags, CA_ENABLED, &it_enabled, text, ret, tmatches); GEN_COMPS (flags, CA_EXPORT, &it_exports, text, ret, tmatches); GEN_COMPS (flags, CA_FUNCTION, &it_functions, text, ret, tmatches); GEN_COMPS (flags, CA_HOSTNAME, &it_hostnames, text, ret, tmatches); GEN_COMPS (flags, CA_JOB, &it_jobs, text, ret, tmatches); GEN_COMPS (flags, CA_KEYWORD, &it_keywords, text, ret, tmatches); GEN_COMPS (flags, CA_RUNNING, &it_running, text, ret, tmatches); GEN_COMPS (flags, CA_SETOPT, &it_setopts, text, ret, tmatches); GEN_COMPS (flags, CA_SHOPT, &it_shopts, text, ret, tmatches); GEN_COMPS (flags, CA_SIGNAL, &it_signals, text, ret, tmatches); GEN_COMPS (flags, CA_STOPPED, &it_stopped, text, ret, tmatches); GEN_COMPS (flags, CA_VARIABLE, &it_variables, text, ret, tmatches); GEN_XCOMPS(flags, CA_COMMAND, text, command_word_completion_function, cmatches, ret, tmatches); GEN_XCOMPS(flags, CA_FILE, text, pcomp_filename_completion_function, cmatches, ret, tmatches); GEN_XCOMPS(flags, CA_USER, text, rl_username_completion_function, cmatches, ret, tmatches); GEN_XCOMPS(flags, CA_GROUP, text, bash_groupname_completion_function, cmatches, ret, tmatches); /* And lastly, the special case for directories */ if (flags & CA_DIRECTORY) { cmatches = bash_directory_completion_matches (text); tmatches = completions_to_stringlist (cmatches); ret = append_stringlist (ret, tmatches); free_array (cmatches); free_stringlist (tmatches); } return ret; } /* Generate a list of matches for CS->globpat. Unresolved: should this use TEXT as a match prefix, or just go without? Currently, the code does not use TEXT, just globs CS->globpat and returns the results. If we do decide to use TEXT, we should call quote_string_for_globbing before the call to glob_filename. */ static STRINGLIST * gen_globpat_matches (cs, text) COMPSPEC *cs; const char *text; { STRINGLIST *sl; sl = alloc_stringlist (0); sl->list = glob_filename (cs->globpat); if (GLOB_FAILED (sl->list)) sl->list = (char **)NULL; if (sl->list) sl->list_len = sl->list_size = array_len (sl->list); return sl; } /* Perform the shell word expansions on CS->words and return the results. Again, this ignores TEXT. */ static STRINGLIST * gen_wordlist_matches (cs, text) COMPSPEC *cs; const char *text; { WORD_LIST *l, *l2; STRINGLIST *sl; int nw, tlen; if (cs->words == 0 || cs->words[0] == '\0') return ((STRINGLIST *)NULL); /* This used to be a simple expand_string(cs->words, 0), but that won't do -- there's no way to split a simple list into individual words that way, since the shell semantics say that word splitting is done only on the results of expansion. */ l = split_at_delims (cs->words, strlen (cs->words), (char *)NULL, -1, (int *)NULL, (int *)NULL); if (l == 0) return ((STRINGLIST *)NULL); /* This will jump back to the top level if the expansion fails... */ l2 = expand_words_shellexp (l); dispose_words (l); nw = list_length (l2); sl = alloc_stringlist (nw + 1); tlen = STRLEN (text); for (nw = 0, l = l2; l; l = l->next) { if (tlen == 0 || STREQN (l->word->word, text, tlen)) sl->list[nw++] = STRDUP (l->word->word); } sl->list[sl->list_len = nw] = (char *)NULL; return sl; } #ifdef ARRAY_VARS static SHELL_VAR * bind_comp_words (lwords) WORD_LIST *lwords; { SHELL_VAR *v; v = find_variable ("COMP_WORDS"); if (v == 0) v = make_new_array_variable ("COMP_WORDS"); if (readonly_p (v)) VUNSETATTR (v, att_readonly); if (array_p (v) == 0) v = convert_var_to_array (v); v = assign_array_var_from_word_list (v, lwords); return v; } #endif /* ARRAY_VARS */ static void bind_compfunc_variables (line, ind, lwords, cw, exported) char *line; int ind; WORD_LIST *lwords; int cw, exported; { char ibuf[INT_STRLEN_BOUND(int) + 1]; char *value; SHELL_VAR *v; /* Set the variables that the function expects while it executes. Maybe these should be in the function environment (temporary_env). */ v = bind_variable ("COMP_LINE", line); if (v && exported) VSETATTR(v, att_exported); value = inttostr (ind, ibuf, sizeof(ibuf)); v = bind_int_variable ("COMP_POINT", value); if (v && exported) VSETATTR(v, att_exported); /* Since array variables can't be exported, we don't bother making the array of words. */ if (exported == 0) { #ifdef ARRAY_VARS v = bind_comp_words (lwords); value = inttostr (cw, ibuf, sizeof(ibuf)); bind_int_variable ("COMP_CWORD", value); #endif } else array_needs_making = 1; } static void unbind_compfunc_variables (exported) int exported; { makunbound ("COMP_LINE", shell_variables); makunbound ("COMP_POINT", shell_variables); #ifdef ARRAY_VARS makunbound ("COMP_WORDS", shell_variables); makunbound ("COMP_CWORD", shell_variables); #endif if (exported) array_needs_making = 1; } /* Build the list of words to pass to a function or external command as arguments. When the function or command is invoked, $0 == function or command being invoked $1 == command name $2 = word to be completed (possibly null) $3 = previous word Functions can access all of the words in the current command line with the COMP_WORDS array. External commands cannot. */ static WORD_LIST * build_arg_list (cmd, text, lwords, ind) char *cmd; const char *text; WORD_LIST *lwords; int ind; { WORD_LIST *ret, *cl, *l; WORD_DESC *w; int i; ret = (WORD_LIST *)NULL; w = make_word (cmd); ret = make_word_list (w, (WORD_LIST *)NULL); w = (lwords && lwords->word) ? copy_word (lwords->word) : make_word (""); cl = ret->next = make_word_list (w, (WORD_LIST *)NULL); w = make_word (text); cl->next = make_word_list (w, (WORD_LIST *)NULL); cl = cl->next; /* Search lwords for current word */ for (l = lwords, i = 1; l && i < ind-1; l = l->next, i++) ; w = (l && l->word) ? copy_word (l->word) : make_word (""); cl->next = make_word_list (w, (WORD_LIST *)NULL); return ret; } /* Build a command string with $0 == cs->funcname (function to execute for completion list) $1 == command name (command being completed) $2 = word to be completed (possibly null) $3 = previous word and run in the current shell. The function should put its completion list into the array variable COMPREPLY. We build a STRINGLIST from the results and return it. Since the shell function should return its list of matches in an array variable, this does nothing if arrays are not compiled into the shell. */ static STRINGLIST * gen_shell_function_matches (cs, text, line, ind, lwords, nw, cw) COMPSPEC *cs; const char *text; char *line; int ind; WORD_LIST *lwords; int nw, cw; { char *funcname; STRINGLIST *sl; SHELL_VAR *f, *v; WORD_LIST *cmdlist; int fval; #if defined (ARRAY_VARS) ARRAY *a; #endif funcname = cs->funcname; f = find_function (funcname); if (f == 0) { internal_error ("completion: function `%s' not found", funcname); rl_ding (); rl_on_new_line (); return ((STRINGLIST *)NULL); } #if !defined (ARRAY_VARS) return ((STRINGLIST *)NULL); #else /* We pass cw - 1 because command_line_to_word_list returns indices that are 1-based, while bash arrays are 0-based. */ bind_compfunc_variables (line, ind, lwords, cw - 1, 0); cmdlist = build_arg_list (funcname, text, lwords, cw); fval = execute_shell_function (f, cmdlist); /* Now clean up and destroy everything. */ dispose_words (cmdlist); unbind_compfunc_variables (0); /* The list of completions is returned in the array variable COMPREPLY. */ v = find_variable ("COMPREPLY"); if (v == 0) return ((STRINGLIST *)NULL); if (array_p (v) == 0) v = convert_var_to_array (v); a = array_cell (v); if (a == 0 || array_empty (a)) sl = (STRINGLIST *)NULL; else { /* XXX - should we filter the list of completions so only those matching TEXT are returned? Right now, we do not. */ sl = alloc_stringlist (0); sl->list = array_to_argv (a); sl->list_len = sl->list_size = array_num_elements (a); } /* XXX - should we unbind COMPREPLY here? */ makunbound ("COMPREPLY", shell_variables); return (sl); #endif } /* Build a command string with $0 == cs->command (command to execute for completion list) $1 == command name (command being completed) $2 = word to be completed (possibly null) $3 = previous word and run in with command substitution. Parse the results, one word per line, with backslashes allowed to escape newlines. Build a STRINGLIST from the results and return it. */ static STRINGLIST * gen_command_matches (cs, text, line, ind, lwords, nw, cw) COMPSPEC *cs; const char *text; char *line; int ind; WORD_LIST *lwords; int nw, cw; { char *csbuf, *cscmd, *t; int cmdlen, cmdsize, n, ws, we; WORD_LIST *cmdlist, *cl; STRINGLIST *sl; bind_compfunc_variables (line, ind, lwords, cw, 1); cmdlist = build_arg_list (cs->command, text, lwords, cw); /* Estimate the size needed for the buffer. */ n = strlen (cs->command); cmdsize = n + 1; for (cl = cmdlist->next; cl; cl = cl->next) cmdsize += STRLEN (cl->word->word) + 3; cmdsize += 2; /* allocate the string for the command and fill it in. */ cscmd = (char *)xmalloc (cmdsize + 1); strcpy (cscmd, cs->command); /* $0 */ cmdlen = n; cscmd[cmdlen++] = ' '; for (cl = cmdlist->next; cl; cl = cl->next) /* $1, $2, $3, ... */ { t = sh_single_quote (cl->word->word ? cl->word->word : ""); n = strlen (t); RESIZE_MALLOCED_BUFFER (cscmd, cmdlen, n + 2, cmdsize, 64); strcpy (cscmd + cmdlen, t); cmdlen += n; if (cl->next) cscmd[cmdlen++] = ' '; free (t); } cscmd[cmdlen] = '\0'; csbuf = command_substitute (cscmd, 0); /* Now clean up and destroy everything. */ dispose_words (cmdlist); free (cscmd); unbind_compfunc_variables (1); if (csbuf == 0 || *csbuf == '\0') { FREE (csbuf); return ((STRINGLIST *)NULL); } /* Now break CSBUF up at newlines, with backslash allowed to escape a newline, and put the individual words into a STRINGLIST. */ sl = alloc_stringlist (16); for (ws = 0; csbuf[ws]; ) { we = ws; while (csbuf[we] && csbuf[we] != '\n') { if (csbuf[we] == '\\' && csbuf[we+1] == '\n') we++; we++; } t = substring (csbuf, ws, we); if (sl->list_len >= sl->list_size - 1) realloc_stringlist (sl, sl->list_size + 16); sl->list[sl->list_len++] = t; while (csbuf[we] == '\n') we++; ws = we; } sl->list[sl->list_len] = (char *)NULL; free (csbuf); return (sl); } static WORD_LIST * command_line_to_word_list (line, llen, sentinel, nwp, cwp) char *line; int llen, sentinel, *nwp, *cwp; { WORD_LIST *ret; char *delims; delims = "()<>;&| \t\n"; /* shell metacharacters break words */ ret = split_at_delims (line, llen, delims, sentinel, nwp, cwp); return (ret); } /* Evaluate COMPSPEC *cs and return all matches for WORD. */ STRINGLIST * gen_compspec_completions (cs, cmd, word, start, end) COMPSPEC *cs; const char *cmd; const char *word; int start, end; { STRINGLIST *ret, *tmatches; char *line; int llen, nw, cw; WORD_LIST *lwords; debug_printf ("programmable_completions (%s, %s, %d, %d)", cmd, word, start, end); debug_printf ("programmable_completions: %s -> %p", cmd, cs); ret = gen_action_completions (cs, word); if (ret && progcomp_debug) { debug_printf ("gen_action_completions (%p, %s) -->", cs, word); print_stringlist (ret, "\t"); rl_on_new_line (); } /* Now we start generating completions based on the other members of CS. */ if (cs->globpat) { tmatches = gen_globpat_matches (cs, word); if (tmatches) { if (progcomp_debug) { debug_printf ("gen_globpat_matches (%p, %s) -->", cs, word); print_stringlist (tmatches, "\t"); rl_on_new_line (); } ret = append_stringlist (ret, tmatches); free_stringlist (tmatches); rl_filename_completion_desired = 1; } } if (cs->words) { tmatches = gen_wordlist_matches (cs, word); if (tmatches) { if (progcomp_debug) { debug_printf ("gen_wordlist_matches (%p, %s) -->", cs, word); print_stringlist (tmatches, "\t"); rl_on_new_line (); } ret = append_stringlist (ret, tmatches); free_stringlist (tmatches); } } lwords = (WORD_LIST *)NULL; line = (char *)NULL; if (cs->command || cs->funcname) { /* If we have a command or function to execute, we need to first break the command line into individual words, find the number of words, and find the word in the list containing the word to be completed. */ line = substring (rl_line_buffer, start, end); llen = end - start; debug_printf ("command_line_to_word_list (%s, %d, %d, %p, %p)", line, llen, rl_point - start, &nw, &cw); lwords = command_line_to_word_list (line, llen, rl_point - start, &nw, &cw); if (lwords == 0 && llen > 0) debug_printf ("ERROR: command_line_to_word_list returns NULL"); else if (progcomp_debug) { debug_printf ("command_line_to_word_list -->"); printf ("\t"); print_word_list (lwords, "!"); printf ("\n"); fflush(stdout); rl_on_new_line (); } } if (cs->funcname) { tmatches = gen_shell_function_matches (cs, word, line, rl_point - start, lwords, nw, cw); if (tmatches) { if (progcomp_debug) { debug_printf ("gen_shell_function_matches (%p, %s, %p, %d, %d) -->", cs, word, lwords, nw, cw); print_stringlist (tmatches, "\t"); rl_on_new_line (); } ret = append_stringlist (ret, tmatches); free_stringlist (tmatches); } } if (cs->command) { tmatches = gen_command_matches (cs, word, line, rl_point - start, lwords, nw, cw); if (tmatches) { if (progcomp_debug) { debug_printf ("gen_command_matches (%p, %s, %p, %d, %d) -->", cs, word, lwords, nw, cw); print_stringlist (tmatches, "\t"); rl_on_new_line (); } ret = append_stringlist (ret, tmatches); free_stringlist (tmatches); } } if (cs->command || cs->funcname) { if (lwords) dispose_words (lwords); FREE (line); } if (cs->filterpat) { tmatches = filter_stringlist (ret, cs->filterpat, word); if (progcomp_debug) { debug_printf ("filter_stringlist (%p, %s, %s) -->", ret, cs->filterpat, word); print_stringlist (tmatches, "\t"); rl_on_new_line (); } if (ret && ret != tmatches) { FREE (ret->list); free (ret); } ret = tmatches; } if (cs->prefix || cs->suffix) ret = prefix_suffix_stringlist (ret, cs->prefix, cs->suffix); /* If no matches have been generated and the user has specified that directory completion should be done as a default, call gen_action_completions again to generate a list of matching directory names. */ if ((ret == 0 || ret->list_len == 0) && (cs->options & COPT_DIRNAMES)) { COMPSPEC *dummy; dummy = alloc_compspec (); dummy->actions = CA_DIRECTORY; ret = gen_action_completions (dummy, word); free_compspec (dummy); } return (ret); } /* The driver function for the programmable completion code. Returns a list of matches for WORD, which is an argument to command CMD. START and END bound the command currently being completed in rl_line_buffer. */ char ** programmable_completions (cmd, word, start, end, foundp) const char *cmd; const char *word; int start, end, *foundp; { COMPSPEC *cs; STRINGLIST *ret; char **rmatches, *t; /* We look at the basename of CMD if the full command does not have an associated COMPSPEC. */ cs = find_compspec (cmd); if (cs == 0) { t = strrchr (cmd, '/'); if (t) cs = find_compspec (++t); } if (cs == 0) { if (foundp) *foundp = 0; return ((char **)NULL); } /* Signal the caller that we found a COMPSPEC for this command, and pass back any meta-options associated with the compspec. */ if (foundp) *foundp = 1|cs->options; ret = gen_compspec_completions (cs, cmd, word, start, end); if (ret) { rmatches = ret->list; free (ret); } else rmatches = (char **)NULL; return (rmatches); } #endif /* PROGRAMMABLE_COMPLETION */ /* pcomplib.c - library functions for programmable completion. */ /* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 #if defined (PROGRAMMABLE_COMPLETION) #include "bashansi.h" #include #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "shell.h" #include "pcomplete.h" #define COMPLETE_HASH_BUCKETS 29 /* for testing */ #define STRDUP(x) ((x) ? savestring (x) : (char *)NULL) HASH_TABLE *prog_completes = (HASH_TABLE *)NULL; static void free_progcomp __P((PTR_T)); static int progcomp_initialized = 0; COMPSPEC * alloc_compspec () { COMPSPEC *ret; ret = (COMPSPEC *)xmalloc (sizeof (COMPSPEC)); ret->refcount = 0; ret->actions = (unsigned long)0; ret->options = (unsigned long)0; ret->globpat = (char *)NULL; ret->words = (char *)NULL; ret->prefix = (char *)NULL; ret->suffix = (char *)NULL; ret->funcname = (char *)NULL; ret->command = (char *)NULL; ret->filterpat = (char *)NULL; return ret; } void free_compspec (cs) COMPSPEC *cs; { cs->refcount--; if (cs->refcount == 0) { FREE (cs->globpat); FREE (cs->words); FREE (cs->prefix); FREE (cs->suffix); FREE (cs->funcname); FREE (cs->command); FREE (cs->filterpat); free (cs); } } COMPSPEC * copy_compspec (cs) COMPSPEC *cs; { COMPSPEC *new; new = (COMPSPEC *)xmalloc (sizeof (COMPSPEC)); new->refcount = cs->refcount; new->actions = cs->actions; new->options = cs->options; new->globpat = STRDUP (cs->globpat); new->words = STRDUP (cs->words); new->prefix = STRDUP (cs->prefix); new->suffix = STRDUP (cs->suffix); new->funcname = STRDUP (cs->funcname); new->command = STRDUP (cs->command); new->filterpat = STRDUP (cs->filterpat); return new; } void initialize_progcomp () { if (progcomp_initialized == 0) { prog_completes = make_hash_table (COMPLETE_HASH_BUCKETS); progcomp_initialized = 1; } } int num_progcomps () { if (progcomp_initialized == 0 || prog_completes == 0) return (0); return (prog_completes->nentries); } static void free_progcomp (data) PTR_T data; { COMPSPEC *cs; cs = (COMPSPEC *)data; free_compspec (cs); } void clear_progcomps () { if (prog_completes) flush_hash_table (prog_completes, free_progcomp); } int remove_progcomp (cmd) char *cmd; { register BUCKET_CONTENTS *item; if (prog_completes == 0) return 1; item = remove_hash_item (cmd, prog_completes); if (item) { free_progcomp (item->data); free (item->key); free (item); return (1); } return (0); } int add_progcomp (cmd, cs) char *cmd; COMPSPEC *cs; { register BUCKET_CONTENTS *item; if (progcomp_initialized == 0 || prog_completes == 0) initialize_progcomp (); if (cs == NULL) programming_error ("add_progcomp: %s: NULL COMPSPEC", cmd); item = add_hash_item (cmd, prog_completes); if (item->data) free_progcomp (item->data); else item->key = savestring (cmd); item->data = (char *)cs; cs->refcount++; return 1; } COMPSPEC * find_compspec (cmd) const char *cmd; { register BUCKET_CONTENTS *item; COMPSPEC *cs; if (prog_completes == 0) return ((COMPSPEC *)NULL); item = find_hash_item (cmd, prog_completes); if (item == NULL) return ((COMPSPEC *)NULL); cs = (COMPSPEC *)item->data; return (cs); } void print_all_compspecs (pfunc) sh_csprint_func_t *pfunc; { BUCKET_CONTENTS *item_list; int bucket; COMPSPEC *cs; if (prog_completes == 0 || pfunc == 0) return; for (bucket = 0; bucket < prog_completes->nbuckets; bucket++) { item_list = get_hash_bucket (bucket, prog_completes); if (item_list == 0) continue; for ( ; item_list; item_list = item_list->next) { cs = (COMPSPEC *)item_list->data; (*pfunc) (item_list->key, cs); } } } #endif /* PROGRAMMABLE_COMPLETION */ /* print_command -- A way to make readable commands from a command tree. */ /* Copyright (C) 1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 # if defined (PREFER_VARARGS) # include # endif #endif #include "bashansi.h" #include "shell.h" #include /* use <...> so we pick it up from the build directory */ #include "stdc.h" #include "builtins/common.h" #if !HAVE_DECL_PRINTF extern int printf __P((const char *, ...)); /* Yuck. Double yuck. */ #endif 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 (DPAREN_ARITHMETIC) static void print_arith_command __P((ARITH_COM *)); #endif #if defined (COND_COMMAND) static void print_cond_node __P((COND_COM *)); static void print_cond_command __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; /* 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); 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); } /* A function to print the words of a simple command when set -x is on. */ void xtrace_print_word_list (list) WORD_LIST *list; { WORD_LIST *w; char *t, *x; 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 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); } static void print_for_command (for_command) FOR_COM *for_command; { cprintf ("for %s in ", for_command->name->word); command_print_word_list (for_command->map_list, " "); 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) static void print_select_command (select_command) SELECT_COM *select_command; { cprintf ("select %s in ", select_command->name->word); command_print_word_list (select_command->map_list, " "); 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--; } static void print_case_command (case_command) CASE_COM *case_command; { cprintf ("case %s in ", case_command->word->word); 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) static void print_arith_command (arith_command) ARITH_COM *arith_command; { cprintf ("(( "); command_print_word_list (arith_command->exp, " "); 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 */ } } static 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 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_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_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] == '&') return; cprintf (";"); } #if !defined (USE_VARARGS) /* How to make the string. */ static void cprintf (format, arg1, arg2) const char *format; char *arg1, *arg2; { register const char *s; char char_arg[2], *argp, *args[2], intbuf[INT_STRLEN_BOUND(int) + 1]; int arg_len, c, arg_index, digit_arg; args[arg_index = 0] = arg1; args[1] = arg2; arg_len = strlen (format); the_printed_command_resize (arg_len + 1); char_arg[1] = '\0'; s = format; while (s && *s) { int free_argp = 0; c = *s++; 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 = (char *)args[arg_index++]; 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 = pointer_to_int (args[arg_index]); if (digit_arg < 0) { sprintf (intbuf, "%u", (unsigned)-1); argp = intbuf; } else argp = inttostr (digit_arg, intbuf, sizeof (intbuf)); arg_index++; arg_len = strlen (argp); break; case 'c': char_arg[0] = pointer_to_int (args[arg_index]); arg_index++; argp = char_arg; arg_len = 1; break; default: programming_error ("cprintf: bad `%%' argument (%c)", c); } } if (argp) { the_printed_command_resize (arg_len + 1); FASTCOPY (argp, the_printed_command + command_string_index, arg_len); command_string_index += arg_len; if (free_argp) free (argp); } } the_printed_command[command_string_index] = '\0'; } #else /* We have support for varargs. */ /* 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; #if defined (PREFER_STDARG) va_start (args, control); #else va_start (args); #endif arg_len = strlen (control); the_printed_command_resize (arg_len + 1); char_arg[1] = '\0'; s = control; while (s && *s) { int free_argp; free_argp = 0; 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: bad `%%' argument (%c)", 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; if (free_argp) free (argp); } } the_printed_command[command_string_index] = '\0'; } #endif /* HAVE_VARARGS_H */ /* 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; #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif 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 */ /* redir.c -- Functions to perform input and output redirection. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 "bashtypes.h" #ifndef _MINIX # include #endif #include "filecntl.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include #endif #include #if !defined (errno) extern int errno; #endif #include "bashansi.h" #include "memalloc.h" #include "shell.h" #include "flags.h" #include "execute_cmd.h" #include "redir.h" #if defined (BUFFERED_INPUT) # include "input.h" #endif extern int posixly_correct; extern REDIRECT *redirection_undo_list; extern REDIRECT *exec_redirection_undo_list; /* Static functions defined and used in this file. */ static void add_undo_close_redirect __P((int)); static void add_exec_redirect __P((REDIRECT *)); static int add_undo_redirect __P((int)); static int expandable_redirection_filename __P((REDIRECT *)); static int stdin_redirection __P((enum r_instruction, int)); static int do_redirection_internal __P((REDIRECT *, int, int, int)); static int write_here_document __P((int, WORD_DESC *)); static int here_document_to_fd __P((WORD_DESC *)); static int redir_special_open __P((int, char *, int, int, enum r_instruction)); static int noclobber_open __P((char *, int, int, enum r_instruction)); static int redir_open __P((char *, int, int, enum r_instruction)); /* Spare redirector used when translating [N]>&WORD or [N]<&WORD to a new redirection and when creating the redirection undo list. */ static REDIRECTEE rd; /* Set to errno when a here document cannot be created for some reason. Used to print a reasonable error message. */ static int heredoc_errno; void redirection_error (temp, error) REDIRECT *temp; int error; { char *filename, *allocname; int oflags; allocname = 0; if (temp->redirector < 0) /* This can happen when read_token_word encounters overflow, like in exec 4294967297>x */ filename = "file descriptor out of range"; #ifdef EBADF else if (temp->redirector >= 0 && errno == EBADF) filename = allocname = itos (temp->redirector); #endif else if (expandable_redirection_filename (temp)) { if (posixly_correct && interactive_shell == 0) { oflags = temp->redirectee.filename->flags; temp->redirectee.filename->flags |= W_NOGLOB; } filename = allocname = redirection_expand (temp->redirectee.filename); if (posixly_correct && interactive_shell == 0) temp->redirectee.filename->flags = oflags; if (filename == 0) filename = temp->redirectee.filename->word; } else if (temp->redirectee.dest < 0) filename = "file descriptor out of range"; else filename = allocname = itos (temp->redirectee.dest); switch (error) { case AMBIGUOUS_REDIRECT: internal_error ("%s: ambiguous redirect", filename); break; case NOCLOBBER_REDIRECT: internal_error ("%s: cannot overwrite existing file", filename); break; #if defined (RESTRICTED_SHELL) case RESTRICTED_REDIRECT: internal_error ("%s: restricted: cannot redirect output", filename); break; #endif /* RESTRICTED_SHELL */ case HEREDOC_REDIRECT: internal_error ("cannot create temp file for here document: %s", strerror (heredoc_errno)); break; default: internal_error ("%s: %s", filename, strerror (error)); break; } FREE (allocname); } /* Perform the redirections on LIST. If FOR_REAL, then actually make input and output file descriptors, otherwise just do whatever is neccessary for side effecting. INTERNAL says to remember how to undo the redirections later, if non-zero. If SET_CLEXEC is non-zero, file descriptors opened in do_redirection () have their close-on-exec flag set. */ int do_redirections (list, for_real, internal, set_clexec) REDIRECT *list; int for_real, internal, set_clexec; { int error; REDIRECT *temp; if (internal) { if (redirection_undo_list) { dispose_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; } if (exec_redirection_undo_list) dispose_exec_redirects (); } for (temp = list; temp; temp = temp->next) { error = do_redirection_internal (temp, for_real, internal, set_clexec); if (error) { redirection_error (temp, error); return (error); } } return (0); } /* Return non-zero if the redirection pointed to by REDIRECT has a redirectee.filename that can be expanded. */ static int expandable_redirection_filename (redirect) REDIRECT *redirect; { switch (redirect->instruction) { case r_output_direction: case r_appending_to: case r_input_direction: case r_inputa_direction: case r_err_and_out: case r_input_output: case r_output_force: case r_duplicating_input_word: case r_duplicating_output_word: return 1; default: return 0; } } /* Expand the word in WORD returning a string. If WORD expands to multiple words (or no words), then return NULL. */ char * redirection_expand (word) WORD_DESC *word; { char *result; WORD_LIST *tlist1, *tlist2; WORD_DESC *w; w = copy_word (word); if (posixly_correct) w->flags |= W_NOSPLIT; tlist1 = make_word_list (w, (WORD_LIST *)NULL); tlist2 = expand_words_no_vars (tlist1); dispose_words (tlist1); if (!tlist2 || tlist2->next) { /* We expanded to no words, or to more than a single word. Dispose of the word list and return NULL. */ if (tlist2) dispose_words (tlist2); return ((char *)NULL); } result = string_list (tlist2); /* XXX savestring (tlist2->word->word)? */ dispose_words (tlist2); return (result); } /* Write the text of the here document pointed to by REDIRECTEE to the file descriptor FD, which is already open to a temp file. Return 0 if the write is successful, otherwise return errno. */ static int write_here_document (fd, redirectee) int fd; WORD_DESC *redirectee; { char *document; int document_len, fd2; FILE *fp; register WORD_LIST *t, *tlist; /* Expand the text if the word that was specified had no quoting. The text that we expand is treated exactly as if it were surrounded by double quotes. */ if (redirectee->flags & W_QUOTED) { document = redirectee->word; document_len = strlen (document); /* Set errno to something reasonable if the write fails. */ if (write (fd, document, document_len) < document_len) { if (errno == 0) errno = ENOSPC; return (errno); } else return 0; } tlist = expand_string (redirectee->word, Q_HERE_DOCUMENT); if (tlist) { /* Try using buffered I/O (stdio) and writing a word at a time, letting stdio do the work of buffering for us rather than managing our own strings. Most stdios are not particularly fast, however -- this may need to be reconsidered later. */ if ((fd2 = dup (fd)) < 0 || (fp = fdopen (fd2, "w")) == NULL) { if (fd2 >= 0) close (fd2); return (errno); } errno = 0; for (t = tlist; t; t = t->next) { /* This is essentially the body of string_list_internal expanded inline. */ document = t->word->word; document_len = strlen (document); if (t != tlist) putc (' ', fp); /* separator */ fwrite (document, document_len, 1, fp); if (ferror (fp)) { if (errno == 0) errno = ENOSPC; fd2 = errno; fclose(fp); dispose_words (tlist); return (fd2); } } dispose_words (tlist); if (fclose (fp) != 0) { if (errno == 0) errno = ENOSPC; return (errno); } } return 0; } /* Create a temporary file holding the text of the here document pointed to by REDIRECTEE, and return a file descriptor open for reading to the temp file. Return -1 on any error, and make sure errno is set appropriately. */ static int here_document_to_fd (redirectee) WORD_DESC *redirectee; { char *filename; int r, fd, fd2; fd = sh_mktmpfd ("sh-thd", MT_USERANDOM, &filename); /* If we failed for some reason other than the file existing, abort */ if (fd < 0) { FREE (filename); return (fd); } errno = r = 0; /* XXX */ /* write_here_document returns 0 on success, errno on failure. */ if (redirectee->word) r = write_here_document (fd, redirectee); if (r) { close (fd); unlink (filename); free (filename); errno = r; return (-1); } /* In an attempt to avoid races, we close the first fd only after opening the second. */ /* Make the document really temporary. Also make it the input. */ fd2 = open (filename, O_RDONLY, 0600); if (fd2 < 0) { r = errno; unlink (filename); free (filename); close (fd); errno = r; return -1; } close (fd); if (unlink (filename) < 0) { r = errno; #if defined (__CYGWIN__) /* Under CygWin 1.1.0, the unlink will fail if the file is open. This hack will allow the previous action of silently ignoring the error, but will still leave the file there. This needs some kind of magic. */ if (r == EACCES) return (fd2); #endif /* __CYGWIN__ */ close (fd2); free (filename); errno = r; return (-1); } free (filename); return (fd2); } #define RF_DEVFD 1 #define RF_DEVSTDERR 2 #define RF_DEVSTDIN 3 #define RF_DEVSTDOUT 4 #define RF_DEVTCP 5 #define RF_DEVUDP 6 /* A list of pattern/value pairs for filenames that the redirection code handles specially. */ static STRING_INT_ALIST _redir_special_filenames[] = { #if !defined (HAVE_DEV_FD) { "/dev/fd/[0-9]*", RF_DEVFD }, #endif #if !defined (HAVE_DEV_STDIN) { "/dev/stderr", RF_DEVSTDERR }, { "/dev/stdin", RF_DEVSTDIN }, { "/dev/stdout", RF_DEVSTDOUT }, #endif #if defined (NETWORK_REDIRECTIONS) { "/dev/tcp/*/*", RF_DEVTCP }, { "/dev/udp/*/*", RF_DEVUDP }, #endif { (char *)NULL, -1 } }; static int redir_special_open (spec, filename, flags, mode, ri) int spec; char *filename; int flags, mode; enum r_instruction ri; { int fd; #if !defined (HAVE_DEV_FD) long lfd; #endif fd = -1; switch (spec) { #if !defined (HAVE_DEV_FD) case RF_DEVFD: if (all_digits (filename+8) && legal_number (filename+8, &lfd) && lfd == (int)lfd) { fd = lfd; fd = fcntl (fd, F_DUPFD, 10); } else fd = AMBIGUOUS_REDIRECT; break; #endif #if !defined (HAVE_DEV_STDIN) case RF_DEVSTDIN: fd = fcntl (0, F_DUPFD, 10); break; case RF_DEVSTDOUT: fd = fcntl (1, F_DUPFD, 10); break; case RF_DEVSTDERR: fd = fcntl (2, F_DUPFD, 10); break; #endif #if defined (NETWORK_REDIRECTIONS) case RF_DEVTCP: case RF_DEVUDP: #if defined (HAVE_NETWORK) fd = netopen (filename); #else internal_warning ("/dev/(tcp|udp)/host/port not supported without networking"); fd = open (filename, flags, mode); #endif break; #endif /* NETWORK_REDIRECTIONS */ } return fd; } /* Open FILENAME with FLAGS in noclobber mode, hopefully avoiding most race conditions and avoiding the problem where the file is replaced between the stat(2) and open(2). */ static int noclobber_open (filename, flags, mode, ri) char *filename; int flags, mode; enum r_instruction ri; { int r, fd; struct stat finfo, finfo2; /* If the file exists and is a regular file, return an error immediately. */ r = stat (filename, &finfo); if (r == 0 && (S_ISREG (finfo.st_mode))) return (NOCLOBBER_REDIRECT); /* If the file was not present (r != 0), make sure we open it exclusively so that if it is created before we open it, our open will fail. Make sure that we do not truncate an existing file. Note that we don't turn on O_EXCL unless the stat failed -- if the file was not a regular file, we leave O_EXCL off. */ flags &= ~O_TRUNC; if (r != 0) { fd = open (filename, flags|O_EXCL, mode); return ((fd < 0 && errno == EEXIST) ? NOCLOBBER_REDIRECT : fd); } fd = open (filename, flags, mode); /* If the open failed, return the file descriptor right away. */ if (fd < 0) return (errno == EEXIST ? NOCLOBBER_REDIRECT : fd); /* OK, the open succeeded, but the file may have been changed from a non-regular file to a regular file between the stat and the open. We are assuming that the O_EXCL open handles the case where FILENAME did not exist and is symlinked to an existing file between the stat and open. */ /* If we can open it and fstat the file descriptor, and neither check revealed that it was a regular file, and the file has not been replaced, return the file descriptor. */ if ((fstat (fd, &finfo2) == 0) && (S_ISREG (finfo2.st_mode) == 0) && r == 0 && (S_ISREG (finfo.st_mode) == 0) && same_file (filename, filename, &finfo, &finfo2)) return fd; /* The file has been replaced. badness. */ close (fd); errno = EEXIST; return (NOCLOBBER_REDIRECT); } static int redir_open (filename, flags, mode, ri) char *filename; int flags, mode; enum r_instruction ri; { int fd, r; r = find_string_in_alist (filename, _redir_special_filenames, 1); if (r >= 0) return (redir_special_open (r, filename, flags, mode, ri)); /* If we are in noclobber mode, you are not allowed to overwrite existing files. Check before opening. */ if (noclobber && CLOBBERING_REDIRECT (ri)) { fd = noclobber_open (filename, flags, mode, ri); if (fd == NOCLOBBER_REDIRECT) return (NOCLOBBER_REDIRECT); } else { fd = open (filename, flags, mode); #if defined (AFS) if ((fd < 0) && (errno == EACCES)) fd = open (filename, flags & ~O_CREAT, mode); #endif /* AFS */ } return fd; } /* Do the specific redirection requested. Returns errno or one of the special redirection errors (*_REDIRECT) in case of error, 0 on success. If FOR_REAL is zero, then just do whatever is neccessary to produce the appropriate side effects. REMEMBERING, if non-zero, says to remember how to undo each redirection. If SET_CLEXEC is non-zero, then we set all file descriptors > 2 that we open to be close-on-exec. */ static int do_redirection_internal (redirect, for_real, remembering, set_clexec) REDIRECT *redirect; int for_real, remembering, set_clexec; { WORD_DESC *redirectee; int redir_fd, fd, redirector, r, oflags; long lfd; char *redirectee_word; enum r_instruction ri; REDIRECT *new_redirect; redirectee = redirect->redirectee.filename; redir_fd = redirect->redirectee.dest; redirector = redirect->redirector; ri = redirect->instruction; if (ri == r_duplicating_input_word || ri == r_duplicating_output_word) { /* We have [N]>&WORD or [N]<&WORD. Expand WORD, then translate the redirection into a new one and continue. */ redirectee_word = redirection_expand (redirectee); if (redirectee_word == 0) return (AMBIGUOUS_REDIRECT); else if (redirectee_word[0] == '-' && redirectee_word[1] == '\0') { rd.dest = 0; new_redirect = make_redirection (redirector, r_close_this, rd); } else if (all_digits (redirectee_word)) { if (legal_number (redirectee_word, &lfd) && (int)lfd == lfd) rd.dest = lfd; else rd.dest = -1; /* XXX */ new_redirect = make_redirection (redirector, (ri == r_duplicating_input_word ? r_duplicating_input : r_duplicating_output), rd); } else if (ri == r_duplicating_output_word && redirector == 1) { rd.filename = make_bare_word (redirectee_word); new_redirect = make_redirection (1, r_err_and_out, rd); } else { free (redirectee_word); return (AMBIGUOUS_REDIRECT); } free (redirectee_word); /* Set up the variables needed by the rest of the function from the new redirection. */ if (new_redirect->instruction == r_err_and_out) { char *alloca_hack; /* Copy the word without allocating any memory that must be explicitly freed. */ redirectee = (WORD_DESC *)alloca (sizeof (WORD_DESC)); xbcopy ((char *)new_redirect->redirectee.filename, (char *)redirectee, sizeof (WORD_DESC)); alloca_hack = (char *) alloca (1 + strlen (new_redirect->redirectee.filename->word)); redirectee->word = alloca_hack; strcpy (redirectee->word, new_redirect->redirectee.filename->word); } else /* It's guaranteed to be an integer, and shouldn't be freed. */ redirectee = new_redirect->redirectee.filename; redir_fd = new_redirect->redirectee.dest; redirector = new_redirect->redirector; ri = new_redirect->instruction; /* Overwrite the flags element of the old redirect with the new value. */ redirect->flags = new_redirect->flags; dispose_redirects (new_redirect); } switch (ri) { case r_output_direction: case r_appending_to: case r_input_direction: case r_inputa_direction: case r_err_and_out: /* command &>filename */ case r_input_output: case r_output_force: if (posixly_correct && interactive_shell == 0) { oflags = redirectee->flags; redirectee->flags |= W_NOGLOB; } redirectee_word = redirection_expand (redirectee); if (posixly_correct && interactive_shell == 0) redirectee->flags = oflags; if (redirectee_word == 0) return (AMBIGUOUS_REDIRECT); #if defined (RESTRICTED_SHELL) if (restricted && (WRITE_REDIRECT (ri))) { free (redirectee_word); return (RESTRICTED_REDIRECT); } #endif /* RESTRICTED_SHELL */ fd = redir_open (redirectee_word, redirect->flags, 0666, ri); free (redirectee_word); if (fd == NOCLOBBER_REDIRECT) return (fd); if (fd < 0) return (errno); if (for_real) { if (remembering) { /* Only setup to undo it if the thing to undo is active. */ if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1)) add_undo_redirect (redirector); else add_undo_close_redirect (redirector); } #if defined (BUFFERED_INPUT) check_bash_input (redirector); #endif if ((fd != redirector) && (dup2 (fd, redirector) < 0)) return (errno); #if defined (BUFFERED_INPUT) /* Do not change the buffered stream for an implicit redirection of /dev/null to fd 0 for asynchronous commands without job control (r_inputa_direction). */ if (ri == r_input_direction || ri == r_input_output) duplicate_buffered_stream (fd, redirector); #endif /* BUFFERED_INPUT */ /* * If we're remembering, then this is the result of a while, for * or until loop with a loop redirection, or a function/builtin * executing in the parent shell with a redirection. In the * function/builtin case, we want to set all file descriptors > 2 * to be close-on-exec to duplicate the effect of the old * for i = 3 to NOFILE close(i) loop. In the case of the loops, * both sh and ksh leave the file descriptors open across execs. * The Posix standard mentions only the exec builtin. */ if (set_clexec && (redirector > 2)) SET_CLOSE_ON_EXEC (redirector); } if (fd != redirector) { #if defined (BUFFERED_INPUT) if (INPUT_REDIRECT (ri)) close_buffered_fd (fd); else #endif /* !BUFFERED_INPUT */ close (fd); /* Don't close what we just opened! */ } /* If we are hacking both stdout and stderr, do the stderr redirection here. */ if (ri == r_err_and_out) { if (for_real) { if (remembering) add_undo_redirect (2); if (dup2 (1, 2) < 0) return (errno); } } break; case r_reading_until: case r_deblank_reading_until: /* REDIRECTEE is a pointer to a WORD_DESC containing the text of the new input. Place it in a temporary file. */ if (redirectee) { fd = here_document_to_fd (redirectee); if (fd < 0) { heredoc_errno = errno; return (HEREDOC_REDIRECT); } if (for_real) { if (remembering) { /* Only setup to undo it if the thing to undo is active. */ if ((fd != redirector) && (fcntl (redirector, F_GETFD, 0) != -1)) add_undo_redirect (redirector); else add_undo_close_redirect (redirector); } #if defined (BUFFERED_INPUT) check_bash_input (redirector); #endif if (fd != redirector && dup2 (fd, redirector) < 0) { r = errno; close (fd); return (r); } #if defined (BUFFERED_INPUT) duplicate_buffered_stream (fd, redirector); #endif if (set_clexec && (redirector > 2)) SET_CLOSE_ON_EXEC (redirector); } if (fd != redirector) #if defined (BUFFERED_INPUT) close_buffered_fd (fd); #else close (fd); #endif } break; case r_duplicating_input: case r_duplicating_output: if (for_real && (redir_fd != redirector)) { if (remembering) { /* Only setup to undo it if the thing to undo is active. */ if (fcntl (redirector, F_GETFD, 0) != -1) add_undo_redirect (redirector); else add_undo_close_redirect (redirector); } #if defined (BUFFERED_INPUT) check_bash_input (redirector); #endif /* This is correct. 2>&1 means dup2 (1, 2); */ if (dup2 (redir_fd, redirector) < 0) return (errno); #if defined (BUFFERED_INPUT) if (ri == r_duplicating_input) duplicate_buffered_stream (redir_fd, redirector); #endif /* BUFFERED_INPUT */ /* First duplicate the close-on-exec state of redirectee. dup2 leaves the flag unset on the new descriptor, which means it stays open. Only set the close-on-exec bit for file descriptors greater than 2 in any case, since 0-2 should always be open unless closed by something like `exec 2<&-'. */ /* if ((already_set || set_unconditionally) && (ok_to_set)) set_it () */ if (((fcntl (redir_fd, F_GETFD, 0) == 1) || set_clexec) && (redirector > 2)) SET_CLOSE_ON_EXEC (redirector); } break; case r_close_this: if (for_real) { if (remembering && (fcntl (redirector, F_GETFD, 0) != -1)) add_undo_redirect (redirector); #if defined (BUFFERED_INPUT) check_bash_input (redirector); close_buffered_fd (redirector); #else /* !BUFFERED_INPUT */ close (redirector); #endif /* !BUFFERED_INPUT */ } break; case r_duplicating_input_word: case r_duplicating_output_word: break; } return (0); } #define SHELL_FD_BASE 10 /* Remember the file descriptor associated with the slot FD, on REDIRECTION_UNDO_LIST. Note that the list will be reversed before it is executed. Any redirections that need to be undone even if REDIRECTION_UNDO_LIST is discarded by the exec builtin are also saved on EXEC_REDIRECTION_UNDO_LIST. */ static int add_undo_redirect (fd) int fd; { int new_fd, clexec_flag; REDIRECT *new_redirect, *closer, *dummy_redirect; new_fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE); if (new_fd < 0) { sys_error ("redirection error: cannot duplicate fd"); return (-1); } clexec_flag = fcntl (fd, F_GETFD, 0); rd.dest = 0; closer = make_redirection (new_fd, r_close_this, rd); dummy_redirect = copy_redirects (closer); rd.dest = new_fd; if (fd == 0) new_redirect = make_redirection (fd, r_duplicating_input, rd); else new_redirect = make_redirection (fd, r_duplicating_output, rd); new_redirect->next = closer; closer->next = redirection_undo_list; redirection_undo_list = new_redirect; /* Save redirections that need to be undone even if the undo list is thrown away by the `exec' builtin. */ add_exec_redirect (dummy_redirect); /* File descriptors used only for saving others should always be marked close-on-exec. Unfortunately, we have to preserve the close-on-exec state of the file descriptor we are saving, since fcntl (F_DUPFD) sets the new file descriptor to remain open across execs. If, however, the file descriptor whose state we are saving is <= 2, we can just set the close-on-exec flag, because file descriptors 0-2 should always be open-on-exec, and the restore above in do_redirection() will take care of it. */ if (clexec_flag || fd < 3) SET_CLOSE_ON_EXEC (new_fd); return (0); } /* Set up to close FD when we are finished with the current command and its redirections. */ static void add_undo_close_redirect (fd) int fd; { REDIRECT *closer; rd.dest = 0; closer = make_redirection (fd, r_close_this, rd); closer->next = redirection_undo_list; redirection_undo_list = closer; } static void add_exec_redirect (dummy_redirect) REDIRECT *dummy_redirect; { dummy_redirect->next = exec_redirection_undo_list; exec_redirection_undo_list = dummy_redirect; } /* Return 1 if the redirection specified by RI and REDIRECTOR alters the standard input. */ static int stdin_redirection (ri, redirector) enum r_instruction ri; int redirector; { switch (ri) { case r_input_direction: case r_inputa_direction: case r_input_output: case r_reading_until: case r_deblank_reading_until: return (1); case r_duplicating_input: case r_duplicating_input_word: case r_close_this: return (redirector == 0); case r_output_direction: case r_appending_to: case r_duplicating_output: case r_err_and_out: case r_output_force: case r_duplicating_output_word: return (0); } return (0); } /* Return non-zero if any of the redirections in REDIRS alter the standard input. */ int stdin_redirects (redirs) REDIRECT *redirs; { REDIRECT *rp; int n; for (n = 0, rp = redirs; rp; rp = rp->next) n += stdin_redirection (rp->instruction, rp->redirector); return n; } /* shell.c -- GNU's idea of the POSIX shell specification. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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. Birthdate: Sunday, January 10th, 1988. Initial author: Brian Fox */ #define INSTALL_DEBUG_MODE #include "config.h" #include "bashtypes.h" #ifndef _MINIX # include #endif #include "posixstat.h" #include "posixtime.h" #include "bashansi.h" #include #include #include #include "filecntl.h" #include #if defined (HAVE_UNISTD_H) # include #endif #define NEED_SH_SETLINEBUF_DECL /* used in externs.h */ #include "shell.h" #include "flags.h" #include "trap.h" #include "mailcheck.h" #include "builtins.h" #include "builtins/common.h" #if defined (JOB_CONTROL) #include "jobs.h" #endif /* JOB_CONTROL */ #include "input.h" #include "execute_cmd.h" #include "findcmd.h" #if defined (HISTORY) # include "bashhist.h" # include #endif #include #include #if defined (__OPENNT) # include #endif #if !defined (HAVE_GETPW_DECLS) extern struct passwd *getpwuid (); #endif /* !HAVE_GETPW_DECLS */ #if !defined (errno) extern int errno; #endif #if defined (NO_MAIN_ENV_ARG) extern char **environ; /* used if no third argument to main() */ #endif extern char *dist_version, *release_status; extern int patch_level, build_version; extern int shell_level; extern int subshell_environment; extern int last_command_exit_value; extern int line_number; extern char *primary_prompt, *secondary_prompt; extern int expand_aliases; extern char *this_command_name; extern int array_needs_making; /* Non-zero means that this shell has already been run; i.e. you should call shell_reinitialize () if you need to start afresh. */ int shell_initialized = 0; COMMAND *global_command = (COMMAND *)NULL; /* Information about the current user. */ struct user_info current_user = { (uid_t)-1, (uid_t)-1, (gid_t)-1, (gid_t)-1, (char *)NULL, (char *)NULL, (char *)NULL }; /* The current host's name. */ char *current_host_name = (char *)NULL; /* Non-zero means that this shell is a login shell. Specifically: 0 = not login shell. 1 = login shell from getty (or equivalent fake out) -1 = login shell from "--login" flag. -2 = both from getty, and from flag. */ int login_shell = 0; /* Non-zero means that at this moment, the shell is interactive. In general, this means that the shell is at this moment reading input from the keyboard. */ int interactive = 0; /* Non-zero means that the shell was started as an interactive shell. */ int interactive_shell = 0; /* Non-zero means to send a SIGHUP to all jobs when an interactive login shell exits. */ int hup_on_exit = 0; /* Tells what state the shell was in when it started: 0 = non-interactive shell script 1 = interactive 2 = -c command This is a superset of the information provided by interactive_shell. */ int startup_state = 0; /* Special debugging helper. */ int debugging_login_shell = 0; /* The environment that the shell passes to other commands. */ char **shell_environment; /* Non-zero when we are executing a top-level command. */ int executing = 0; /* The number of commands executed so far. */ int current_command_number = 1; /* Non-zero is the recursion depth for commands. */ int indirection_level = 0; /* The name of this shell, as taken from argv[0]. */ char *shell_name = (char *)NULL; /* time in seconds when the shell was started */ time_t shell_start_time; /* Are we running in an emacs shell window? */ int running_under_emacs; /* The name of the .(shell)rc file. */ static char *bashrc_file = "~/.bashrc"; /* Non-zero means to act more like the Bourne shell on startup. */ static int act_like_sh; /* Non-zero if this shell is being run by `su'. */ static int su_shell; /* Non-zero if we have already expanded and sourced $ENV. */ static int sourced_env; /* Is this shell running setuid? */ static int running_setuid; /* Values for the long-winded argument names. */ static int debugging; /* Do debugging things. */ static int no_rc; /* Don't execute ~/.bashrc */ static int no_profile; /* Don't execute .profile */ static int do_version; /* Display interesting version info. */ static int make_login_shell; /* Make this shell be a `-bash' shell. */ static int want_initial_help; /* --help option */ int no_line_editing = 0; /* Don't do fancy line editing. */ int posixly_correct = 0; /* Non-zero means posix.2 superset. */ int dump_translatable_strings; /* Dump strings in $"...", don't execute. */ int dump_po_strings; /* Dump strings in $"..." in po format */ int wordexp_only = 0; /* Do word expansion only */ /* Some long-winded argument names. These are obviously new. */ #define Int 1 #define Charp 2 struct { char *name; int type; int *int_value; char **char_value; } long_args[] = { { "debug", Int, &debugging, (char **)0x0 }, { "dump-po-strings", Int, &dump_po_strings, (char **)0x0 }, { "dump-strings", Int, &dump_translatable_strings, (char **)0x0 }, { "help", Int, &want_initial_help, (char **)0x0 }, { "init-file", Charp, (int *)0x0, &bashrc_file }, { "login", Int, &make_login_shell, (char **)0x0 }, { "noediting", Int, &no_line_editing, (char **)0x0 }, { "noprofile", Int, &no_profile, (char **)0x0 }, { "norc", Int, &no_rc, (char **)0x0 }, { "posix", Int, &posixly_correct, (char **)0x0 }, { "rcfile", Charp, (int *)0x0, &bashrc_file }, #if defined (RESTRICTED_SHELL) { "restricted", Int, &restricted, (char **)0x0 }, #endif { "verbose", Int, &echo_input_at_read, (char **)0x0 }, { "version", Int, &do_version, (char **)0x0 }, { "wordexp", Int, &wordexp_only, (char **)0x0 }, { (char *)0x0, Int, (int *)0x0, (char **)0x0 } }; /* These are extern so execute_simple_command can set them, and then longjmp back to main to execute a shell script, instead of calling main () again and resulting in indefinite, possibly fatal, stack growth. */ procenv_t subshell_top_level; int subshell_argc; char **subshell_argv; char **subshell_envp; #if defined (BUFFERED_INPUT) /* The file descriptor from which the shell is reading input. */ int default_buffered_input = -1; #endif /* The following two variables are not static so they can show up in $-. */ int read_from_stdin; /* -s flag supplied */ int want_pending_command; /* -c flag supplied */ static int shell_reinitialized = 0; static char *local_pending_command; static FILE *default_input; static STRING_INT_ALIST *shopt_alist; static int shopt_ind = 0, shopt_len = 0; static int parse_long_options __P((char **, int, int)); static int parse_shell_options __P((char **, int, int)); static int bind_args __P((char **, int, int, int)); static void add_shopt_to_alist __P((char *, int)); static void run_shopt_alist __P((void)); static void execute_env_file __P((char *)); static void run_startup_files __P((void)); static int open_shell_script __P((char *)); static void set_bash_input __P((void)); static int run_one_command __P((char *)); static int run_wordexp __P((char *)); static int uidget __P((void)); static int isnetconn __P((int)); static void init_interactive __P((void)); static void init_noninteractive __P((void)); static void set_shell_name __P((char *)); static void shell_initialize __P((void)); static void shell_reinitialize __P((void)); static void show_shell_usage __P((FILE *, int)); #ifdef __CYGWIN__ static void _cygwin32_check_tmp () { struct stat sb; if (stat ("/tmp", &sb) < 0) internal_warning ("could not find /tmp, please create!"); else { if (S_ISDIR (sb.st_mode) == 0) internal_warning ("/tmp must be a valid directory name"); } } #endif /* __CYGWIN__ */ #if defined (NO_MAIN_ENV_ARG) /* systems without third argument to main() */ int main (argc, argv) int argc; char **argv; #else /* !NO_MAIN_ENV_ARG */ int main (argc, argv, env) int argc; char **argv, **env; #endif /* !NO_MAIN_ENV_ARG */ { register int i; int code, old_errexit_flag; #if defined (RESTRICTED_SHELL) int saverst; #endif volatile int locally_skip_execution; volatile int arg_index, top_level_arg_index; #ifdef __OPENNT char **env; env = environ; #endif /* __OPENNT */ USE_VAR(argc); USE_VAR(argv); USE_VAR(env); USE_VAR(code); USE_VAR(old_errexit_flag); #if defined (RESTRICTED_SHELL) USE_VAR(saverst); #endif /* Catch early SIGINTs. */ code = setjmp (top_level); if (code) exit (2); #if defined (USING_BASH_MALLOC) && defined (DEBUG) # if 0 /* memory tracing */ malloc_set_trace(1); # endif # if 0 malloc_set_register (1); # endif #endif check_dev_tty (); #ifdef __CYGWIN__ _cygwin32_check_tmp (); #endif /* __CYGWIN__ */ /* Wait forever if we are debugging a login shell. */ while (debugging_login_shell); set_default_locale (); running_setuid = uidget (); if (getenv ("POSIXLY_CORRECT") || getenv ("POSIX_PEDANTIC")) posixly_correct = 1; #if defined (USE_GNU_MALLOC_LIBRARY) mcheck (programming_error, (void (*) ())0); #endif /* USE_GNU_MALLOC_LIBRARY */ if (setjmp (subshell_top_level)) { argc = subshell_argc; argv = subshell_argv; env = subshell_envp; sourced_env = 0; } shell_reinitialized = 0; /* Initialize `local' variables for all `invocations' of main (). */ arg_index = 1; local_pending_command = (char *)NULL; want_pending_command = locally_skip_execution = read_from_stdin = 0; default_input = stdin; #if defined (BUFFERED_INPUT) default_buffered_input = -1; #endif /* Fix for the `infinite process creation' bug when running shell scripts from startup files on System V. */ login_shell = make_login_shell = 0; /* If this shell has already been run, then reinitialize it to a vanilla state. */ if (shell_initialized || shell_name) { /* Make sure that we do not infinitely recurse as a login shell. */ if (*shell_name == '-') shell_name++; shell_reinitialize (); if (setjmp (top_level)) exit (2); } shell_environment = env; set_shell_name (argv[0]); shell_start_time = NOW; /* NOW now defined in general.h */ /* Parse argument flags from the input line. */ /* Find full word arguments first. */ arg_index = parse_long_options (argv, arg_index, argc); if (want_initial_help) { show_shell_usage (stdout, 1); exit (EXECUTION_SUCCESS); } if (do_version) { show_shell_version (1); exit (EXECUTION_SUCCESS); } /* If user supplied the "--login" flag, then set and invert LOGIN_SHELL. */ if (make_login_shell) { login_shell++; login_shell = -login_shell; } set_login_shell (login_shell != 0); /* All done with full word options; do standard shell option parsing.*/ this_command_name = shell_name; /* for error reporting */ arg_index = parse_shell_options (argv, arg_index, argc); if (dump_po_strings) dump_translatable_strings = 1; if (dump_translatable_strings) read_but_dont_execute = 1; if (running_setuid && privileged_mode == 0) disable_priv_mode (); /* Need to get the argument to a -c option processed in the above loop. The next arg is a command to execute, and the following args are $0...$n respectively. */ if (want_pending_command) { local_pending_command = argv[arg_index]; if (local_pending_command == 0) { report_error ("option `-c' requires an argument"); exit (EX_USAGE); } arg_index++; } this_command_name = (char *)NULL; /* First, let the outside world know about our interactive status. A shell is interactive if the `-i' flag was given, or if all of the following conditions are met: no -c command no arguments remaining or the -s flag given standard input is a terminal standard output is a terminal Refer to Posix.2, the description of the `sh' utility. */ if (forced_interactive || /* -i flag */ (!local_pending_command && /* No -c command and ... */ wordexp_only == 0 && /* No --wordexp and ... */ ((arg_index == argc) || /* no remaining args or... */ read_from_stdin) && /* -s flag with args, and */ isatty (fileno (stdin)) && /* Input is a terminal and */ isatty (fileno (stdout)))) /* output is a terminal. */ init_interactive (); else init_noninteractive (); #define CLOSE_FDS_AT_LOGIN #if defined (CLOSE_FDS_AT_LOGIN) /* * Some systems have the bad habit of starting login shells with lots of open * file descriptors. For instance, most systems that have picked up the * pre-4.0 Sun YP code leave a file descriptor open each time you call one * of the getpw* functions, and it's set to be open across execs. That * means one for login, one for xterm, one for shelltool, etc. */ if (login_shell && interactive_shell) { for (i = 3; i < 20; i++) close (i); } #endif /* CLOSE_FDS_AT_LOGIN */ /* If we're in a strict Posix.2 mode, turn on interactive comments and other Posix.2 things. */ if (posixly_correct) { bind_variable ("POSIXLY_CORRECT", "y"); sv_strict_posix ("POSIXLY_CORRECT"); } /* Now we run the shopt_alist and process the options. */ if (shopt_alist) run_shopt_alist (); /* From here on in, the shell must be a normal functioning shell. Variables from the environment are expected to be set, etc. */ shell_initialize (); set_default_locale_vars (); if (interactive_shell) { char *term; term = getenv ("TERM"); no_line_editing |= term && (STREQ (term, "emacs")); term = getenv ("EMACS"); running_under_emacs = term ? ((strmatch ("*term*", term, 0) == 0) ? 2 : 1) : 0; } top_level_arg_index = arg_index; old_errexit_flag = exit_immediately_on_error; /* Give this shell a place to longjmp to before executing the startup files. This allows users to press C-c to abort the lengthy startup. */ code = setjmp (top_level); if (code) { if (code == EXITPROG) exit_shell (last_command_exit_value); else { #if defined (JOB_CONTROL) /* Reset job control, since run_startup_files turned it off. */ set_job_control (interactive_shell); #endif /* Reset value of `set -e', since it's turned off before running the startup files. */ exit_immediately_on_error += old_errexit_flag; locally_skip_execution++; } } arg_index = top_level_arg_index; /* Execute the start-up scripts. */ if (interactive_shell == 0) { makunbound ("PS1", shell_variables); makunbound ("PS2", shell_variables); interactive = 0; expand_aliases = posixly_correct; } else { change_flag ('i', FLAG_ON); interactive = 1; } #if defined (RESTRICTED_SHELL) /* Set restricted_shell based on whether the basename of $0 indicates that the shell should be restricted or if the `-r' option was supplied at startup. */ restricted_shell = shell_is_restricted (shell_name); /* If the `-r' option is supplied at invocation, make sure that the shell is not in restricted mode when running the startup files. */ saverst = restricted; restricted = 0; #endif /* The startup files are run with `set -e' temporarily disabled. */ if (locally_skip_execution == 0 && running_setuid == 0) { old_errexit_flag = exit_immediately_on_error; exit_immediately_on_error = 0; run_startup_files (); exit_immediately_on_error += old_errexit_flag; } /* If we are invoked as `sh', turn on Posix mode. */ if (act_like_sh) { bind_variable ("POSIXLY_CORRECT", "y"); sv_strict_posix ("POSIXLY_CORRECT"); } #if defined (RESTRICTED_SHELL) /* Turn on the restrictions after executing the startup files. This means that `bash -r' or `set -r' invoked from a startup file will turn on the restrictions after the startup files are executed. */ restricted = saverst || restricted; if (shell_reinitialized == 0) maybe_make_restricted (shell_name); #endif /* RESTRICTED_SHELL */ if (wordexp_only) { startup_state = 3; last_command_exit_value = run_wordexp (argv[arg_index]); exit_shell (last_command_exit_value); } if (local_pending_command) { arg_index = bind_args (argv, arg_index, argc, 0); startup_state = 2; #if defined (ONESHOT) executing = 1; run_one_command (local_pending_command); exit_shell (last_command_exit_value); #else /* ONESHOT */ with_input_from_string (local_pending_command, "-c"); goto read_and_execute; #endif /* !ONESHOT */ } /* Get possible input filename and set up default_buffered_input or default_input as appropriate. */ if (arg_index != argc && read_from_stdin == 0) { open_shell_script (argv[arg_index]); arg_index++; } else if (interactive == 0) /* In this mode, bash is reading a script from stdin, which is a pipe or redirected file. */ #if defined (BUFFERED_INPUT) default_buffered_input = fileno (stdin); /* == 0 */ #else setbuf (default_input, (char *)NULL); #endif /* !BUFFERED_INPUT */ set_bash_input (); /* Bind remaining args to $1 ... $n */ arg_index = bind_args (argv, arg_index, argc, 1); /* Do the things that should be done only for interactive shells. */ if (interactive_shell) { /* Set up for checking for presence of mail. */ remember_mail_dates (); reset_mail_timer (); #if defined (HISTORY) /* Initialize the interactive history stuff. */ bash_initialize_history (); if (shell_initialized == 0) load_history (); #endif /* HISTORY */ /* Initialize terminal state for interactive shells after the .bash_profile and .bashrc are interpreted. */ get_tty_state (); } #if !defined (ONESHOT) read_and_execute: #endif /* !ONESHOT */ shell_initialized = 1; /* Read commands until exit condition. */ reader_loop (); exit_shell (last_command_exit_value); } static int parse_long_options (argv, arg_start, arg_end) char **argv; int arg_start, arg_end; { int arg_index, longarg, i; char *arg_string; arg_index = arg_start; while ((arg_index != arg_end) && (arg_string = argv[arg_index]) && (*arg_string == '-')) { longarg = 0; /* Make --login equivalent to -login. */ if (arg_string[1] == '-' && arg_string[2]) { longarg = 1; arg_string++; } for (i = 0; long_args[i].name; i++) { if (STREQ (arg_string + 1, long_args[i].name)) { if (long_args[i].type == Int) *long_args[i].int_value = 1; else if (argv[++arg_index] == 0) { report_error ("option `%s' requires an argument", long_args[i].name); exit (EX_USAGE); } else *long_args[i].char_value = argv[arg_index]; break; } } if (long_args[i].name == 0) { if (longarg) { report_error ("%s: unrecognized option", argv[arg_index]); show_shell_usage (stderr, 0); exit (EX_USAGE); } break; /* No such argument. Maybe flag arg. */ } arg_index++; } return (arg_index); } static int parse_shell_options (argv, arg_start, arg_end) char **argv; int arg_start, arg_end; { int arg_index; int arg_character, on_or_off, next_arg, i; char *o_option, *arg_string; arg_index = arg_start; while (arg_index != arg_end && (arg_string = argv[arg_index]) && (*arg_string == '-' || *arg_string == '+')) { /* There are flag arguments, so parse them. */ next_arg = arg_index + 1; /* A single `-' signals the end of options. From the 4.3 BSD sh. An option `--' means the same thing; this is the standard getopt(3) meaning. */ if (arg_string[0] == '-' && (arg_string[1] == '\0' || (arg_string[1] == '-' && arg_string[2] == '\0'))) return (next_arg); i = 1; on_or_off = arg_string[0]; while (arg_character = arg_string[i++]) { switch (arg_character) { case 'c': want_pending_command = 1; break; case 's': read_from_stdin = 1; break; case 'o': o_option = argv[next_arg]; if (o_option == 0) { list_minus_o_opts (-1, (on_or_off == '-') ? 0 : 1); break; } if (set_minus_o_option (on_or_off, o_option) != EXECUTION_SUCCESS) exit (EX_USAGE); next_arg++; break; case 'O': /* Since some of these can be overridden by the normal interactive/non-interactive shell initialization or initializing posix mode, we save the options and process them after initialization. */ o_option = argv[next_arg]; if (o_option == 0) { shopt_listopt (o_option, (on_or_off == '-') ? 0 : 1); break; } add_shopt_to_alist (o_option, on_or_off); next_arg++; break; case 'D': dump_translatable_strings = 1; break; default: if (change_flag (arg_character, on_or_off) == FLAG_ERROR) { report_error ("%c%c: unrecognized option", on_or_off, arg_character); show_shell_usage (stderr, 0); exit (EX_USAGE); } } } /* Can't do just a simple increment anymore -- what about "bash -abouo emacs ignoreeof -hP"? */ arg_index = next_arg; } return (arg_index); } /* Exit the shell with status S. */ void exit_shell (s) int s; { /* Do trap[0] if defined. Allow it to override the exit status passed to us. */ if (signal_is_trapped (0)) s = run_exit_trap (); #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ #if defined (HISTORY) if (interactive_shell) maybe_save_shell_history (); #endif /* HISTORY */ #if defined (JOB_CONTROL) /* If the user has run `shopt -s huponexit', hangup all jobs when we exit an interactive login shell. ksh does this unconditionally. */ if (interactive_shell && login_shell && hup_on_exit) hangup_all_jobs (); /* If this shell is interactive, terminate all stopped jobs and restore the original terminal process group. Don't do this if we're in a subshell and calling exit_shell after, for example, a failed word expansion. */ if (subshell_environment == 0) end_job_control (); #endif /* JOB_CONTROL */ /* Always return the exit status of the last command to our parent. */ exit (s); } #ifdef INCLUDE_UNUSED /* A wrapper for exit that (optionally) can do other things, like malloc statistics tracing. */ void sh_exit (s) int s; { exit (s); } #endif /* Source the bash startup files. If POSIXLY_CORRECT is non-zero, we obey the Posix.2 startup file rules: $ENV is expanded, and if the file it names exists, that file is sourced. The Posix.2 rules are in effect for interactive shells only. (section 4.56.5.3) */ /* Execute ~/.bashrc for most shells. Never execute it if ACT_LIKE_SH is set, or if NO_RC is set. If the executable file "/usr/gnu/src/bash/foo" contains: #!/usr/gnu/bin/bash echo hello then: COMMAND EXECUTE BASHRC -------------------------------- bash -c foo NO bash foo NO foo NO rsh machine ls YES (for rsh, which calls `bash -c') rsh machine foo YES (for shell started by rsh) NO (for foo!) echo ls | bash NO login NO bash YES */ static void execute_env_file (env_file) char *env_file; { char *fn; if (env_file && *env_file) { fn = expand_string_unsplit_to_string (env_file, Q_DOUBLE_QUOTES); if (fn && *fn) maybe_execute_file (fn, 1); FREE (fn); } } static void run_startup_files () { #if defined (JOB_CONTROL) int old_job_control; #endif int sourced_login, run_by_ssh; /* get the rshd/sshd case out of the way first. */ if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 && act_like_sh == 0 && local_pending_command) { #ifdef SSH_SOURCE_BASHRC run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) || (find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0); #else run_by_ssh = 0; #endif /* If we were run by sshd or we think we were run by rshd, execute ~/.bashrc if we are a top-level shell. */ if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2) { #ifdef SYS_BASHRC # if defined (__OPENNT) maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); # else maybe_execute_file (SYS_BASHRC, 1); # endif #endif maybe_execute_file (bashrc_file, 1); return; } } #if defined (JOB_CONTROL) /* Startup files should be run without job control enabled. */ old_job_control = interactive_shell ? set_job_control (0) : 0; #endif sourced_login = 0; /* A shell begun with the --login flag that is not in posix mode runs the login shell startup files, no matter whether or not it is interactive. If NON_INTERACTIVE_LOGIN_SHELLS is defined, run the startup files if argv[0][0] == '-' as well. */ #if defined (NON_INTERACTIVE_LOGIN_SHELLS) if (login_shell && posixly_correct == 0) #else if (login_shell < 0 && posixly_correct == 0) #endif { /* We don't execute .bashrc for login shells. */ no_rc++; /* Execute /etc/profile and one of the personal login shell initialization files. */ if (no_profile == 0) { maybe_execute_file (SYS_PROFILE, 1); if (act_like_sh) /* sh */ maybe_execute_file ("~/.profile", 1); else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ maybe_execute_file ("~/.profile", 1); } sourced_login = 1; } /* A non-interactive shell not named `sh' and not in posix mode reads and executes commands from $BASH_ENV. If `su' starts a shell with `-c cmd' and `-su' as the name of the shell, we want to read the startup files. No other non-interactive shells read any startup files. */ if (interactive_shell == 0 && !(su_shell && login_shell)) { if (posixly_correct == 0 && act_like_sh == 0 && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("BASH_ENV")); return; } /* Interactive shell or `-su' shell. */ if (posixly_correct == 0) /* bash, sh */ { if (login_shell && sourced_login++ == 0) { /* We don't execute .bashrc for login shells. */ no_rc++; /* Execute /etc/profile and one of the personal login shell initialization files. */ if (no_profile == 0) { maybe_execute_file (SYS_PROFILE, 1); if (act_like_sh) /* sh */ maybe_execute_file ("~/.profile", 1); else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ maybe_execute_file ("~/.profile", 1); } } /* bash */ if (act_like_sh == 0 && no_rc == 0) { #ifdef SYS_BASHRC # if defined (__OPENNT) maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); # else maybe_execute_file (SYS_BASHRC, 1); # endif #endif maybe_execute_file (bashrc_file, 1); } /* sh */ else if (act_like_sh && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("ENV")); } else /* bash --posix, sh --posix */ { /* bash and sh */ if (interactive_shell && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("ENV")); } #if defined (JOB_CONTROL) set_job_control (old_job_control); #endif } #if defined (RESTRICTED_SHELL) /* Return 1 if the shell should be a restricted one based on NAME or the value of `restricted'. Don't actually do anything, just return a boolean value. */ int shell_is_restricted (name) char *name; { char *temp; if (restricted) return 1; temp = base_pathname (name); return (STREQ (temp, RESTRICTED_SHELL_NAME)); } /* Perhaps make this shell a `restricted' one, based on NAME. If the basename of NAME is "rbash", then this shell is restricted. The name of the restricted shell is a configurable option, see config.h. In a restricted shell, PATH, SHELL, ENV, and BASH_ENV are read-only and non-unsettable. Do this also if `restricted' is already set to 1; maybe the shell was started with -r. */ int maybe_make_restricted (name) char *name; { char *temp; temp = base_pathname (shell_name); if (restricted || (STREQ (temp, RESTRICTED_SHELL_NAME))) { set_var_read_only ("PATH"); set_var_read_only ("SHELL"); set_var_read_only ("ENV"); set_var_read_only ("BASH_ENV"); restricted = 1; } return (restricted); } #endif /* RESTRICTED_SHELL */ /* Fetch the current set of uids and gids and return 1 if we're running setuid or setgid. */ static int uidget () { uid_t u; u = getuid (); if (current_user.uid != u) { FREE (current_user.user_name); FREE (current_user.shell); FREE (current_user.home_dir); current_user.user_name = current_user.shell = current_user.home_dir = (char *)NULL; } current_user.uid = u; current_user.gid = getgid (); current_user.euid = geteuid (); current_user.egid = getegid (); /* See whether or not we are running setuid or setgid. */ return (current_user.uid != current_user.euid) || (current_user.gid != current_user.egid); } void disable_priv_mode () { setuid (current_user.uid); setgid (current_user.gid); current_user.euid = current_user.uid; current_user.egid = current_user.gid; } static int run_wordexp (words) char *words; { int code, nw, nb; WORD_LIST *wl, *result; code = setjmp (top_level); if (code != NOT_JUMPED) { switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: return last_command_exit_value = 127; case EXITPROG: return last_command_exit_value; case DISCARD: return last_command_exit_value = 1; default: command_error ("run_wordexp", CMDERR_BADJUMP, code, 0); } } /* Run it through the parser to get a list of words and expand them */ if (words && *words) { with_input_from_string (words, "--wordexp"); if (parse_command () != 0) return (126); if (global_command == 0) { printf ("0\n0\n"); return (0); } if (global_command->type != cm_simple) return (126); wl = global_command->value.Simple->words; result = wl ? expand_words_no_vars (wl) : (WORD_LIST *)0; } else result = (WORD_LIST *)0; last_command_exit_value = 0; if (result == 0) { printf ("0\n0\n"); return (0); } /* Count up the number of words and bytes, and print them. Don't count the trailing NUL byte. */ for (nw = nb = 0, wl = result; wl; wl = wl->next) { nw++; nb += strlen (wl->word->word); } printf ("%u\n%u\n", nw, nb); /* Print each word on a separate line. This will have to be changed when the interface to glibc is completed. */ for (wl = result; wl; wl = wl->next) printf ("%s\n", wl->word->word); return (0); } #if defined (ONESHOT) /* Run one command, given as the argument to the -c option. Tell parse_and_execute not to fork for a simple command. */ static int run_one_command (command) char *command; { int code; code = setjmp (top_level); if (code != NOT_JUMPED) { #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: return last_command_exit_value = 127; case EXITPROG: return last_command_exit_value; case DISCARD: return last_command_exit_value = 1; default: command_error ("run_one_command", CMDERR_BADJUMP, code, 0); } } return (parse_and_execute (savestring (command), "-c", SEVAL_NOHIST)); } #endif /* ONESHOT */ static int bind_args (argv, arg_start, arg_end, start_index) char **argv; int arg_start, arg_end, start_index; { register int i; WORD_LIST *args; for (i = arg_start, args = (WORD_LIST *)NULL; i != arg_end; i++) args = make_word_list (make_word (argv[i]), args); if (args) { args = REVERSE_LIST (args, WORD_LIST *); if (start_index == 0) /* bind to $0...$n for sh -c command */ { /* Posix.2 4.56.3 says that the first argument after sh -c command becomes $0, and the rest of the arguments become $1...$n */ shell_name = savestring (args->word->word); FREE (dollar_vars[0]); dollar_vars[0] = savestring (args->word->word); remember_args (args->next, 1); } else /* bind to $1...$n for shell script */ remember_args (args, 1); dispose_words (args); } return (i); } void unbind_args () { remember_args ((WORD_LIST *)NULL, 1); } static int open_shell_script (script_name) char *script_name; { int fd, e, fd_is_tty; char *filename, *path_filename; char sample[80]; int sample_len; struct stat sb; free (dollar_vars[0]); dollar_vars[0] = savestring (script_name); filename = savestring (script_name); fd = open (filename, O_RDONLY); if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0)) { e = errno; /* If it's not in the current directory, try looking through PATH for it. */ path_filename = find_path_file (script_name); if (path_filename) { free (filename); filename = path_filename; fd = open (filename, O_RDONLY); } else errno = e; } if (fd < 0) { e = errno; file_error (filename); exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT); } #ifdef HAVE_DEV_FD fd_is_tty = isatty (fd); #else fd_is_tty = 0; #endif /* Only do this with non-tty file descriptors we can seek on. */ if (fd_is_tty == 0 && (lseek (fd, 0L, 1) != -1)) { /* Check to see if the `file' in `bash file' is a binary file according to the same tests done by execute_simple_command (), and report an error and exit if it is. */ sample_len = read (fd, sample, sizeof (sample)); if (sample_len < 0) { e = errno; if ((fstat (fd, &sb) == 0) && S_ISDIR (sb.st_mode)) internal_error ("%s: is a directory", filename); else { errno = e; file_error (filename); } exit (EX_NOEXEC); } else if (sample_len > 0 && (check_binary_file (sample, sample_len))) { internal_error ("%s: cannot execute binary file", filename); exit (EX_BINARY_FILE); } /* Now rewind the file back to the beginning. */ lseek (fd, 0L, 0); } /* Open the script. But try to move the file descriptor to a randomly large one, in the hopes that any descriptors used by the script will not match with ours. */ fd = move_to_high_fd (fd, 0, -1); #if defined (__CYGWIN__) && defined (O_TEXT) setmode (fd, O_TEXT); #endif #if defined (BUFFERED_INPUT) default_buffered_input = fd; SET_CLOSE_ON_EXEC (default_buffered_input); #else /* !BUFFERED_INPUT */ default_input = fdopen (fd, "r"); if (default_input == 0) { file_error (filename); exit (EX_NOTFOUND); } SET_CLOSE_ON_EXEC (fd); if (fileno (default_input) != fd) SET_CLOSE_ON_EXEC (fileno (default_input)); #endif /* !BUFFERED_INPUT */ /* Just about the only way for this code to be executed is if something like `bash -i /dev/stdin' is executed. */ if (interactive_shell && fd_is_tty) { dup2 (fd, 0); close (fd); fd = 0; #if defined (BUFFERED_INPUT) default_buffered_input = 0; #else fclose (default_input); default_input = stdin; #endif } else if (forced_interactive && fd_is_tty == 0) /* But if a script is called with something like `bash -i scriptname', we need to do a non-interactive setup here, since we didn't do it before. */ init_noninteractive (); free (filename); return (fd); } /* Initialize the input routines for the parser. */ static void set_bash_input () { /* Make sure the fd from which we are reading input is not in no-delay mode. */ #if defined (BUFFERED_INPUT) if (interactive == 0) sh_unset_nodelay_mode (default_buffered_input); else #endif /* !BUFFERED_INPUT */ sh_unset_nodelay_mode (fileno (stdin)); /* with_input_from_stdin really means `with_input_from_readline' */ if (interactive && no_line_editing == 0) with_input_from_stdin (); else #if defined (BUFFERED_INPUT) { if (interactive == 0) with_input_from_buffered_stream (default_buffered_input, dollar_vars[0]); else with_input_from_stream (default_input, dollar_vars[0]); } #else /* !BUFFERED_INPUT */ with_input_from_stream (default_input, dollar_vars[0]); #endif /* !BUFFERED_INPUT */ } /* Close the current shell script input source and forget about it. This is extern so execute_cmd.c:initialize_subshell() can call it. If CHECK_ZERO is non-zero, we close default_buffered_input even if it's the standard input (fd 0). */ void unset_bash_input (check_zero) int check_zero; { #if defined (BUFFERED_INPUT) if ((check_zero && default_buffered_input >= 0) || (check_zero == 0 && default_buffered_input > 0)) { close_buffered_fd (default_buffered_input); default_buffered_input = bash_input.location.buffered_fd = -1; } #else /* !BUFFERED_INPUT */ if (default_input) { fclose (default_input); default_input = (FILE *)NULL; } #endif /* !BUFFERED_INPUT */ } #if !defined (PROGRAM) # define PROGRAM "bash" #endif static void set_shell_name (argv0) char *argv0; { /* Here's a hack. If the name of this shell is "sh", then don't do any startup files; just try to be more like /bin/sh. */ shell_name = base_pathname (argv0); if (*shell_name == '-') shell_name++; if (shell_name[0] == 's' && shell_name[1] == 'h' && shell_name[2] == '\0') act_like_sh++; if (shell_name[0] == 's' && shell_name[1] == 'u' && shell_name[2] == '\0') su_shell++; shell_name = argv0; FREE (dollar_vars[0]); dollar_vars[0] = savestring (shell_name); if (*shell_name == '-') { shell_name++; login_shell++; } /* A program may start an interactive shell with "execl ("/bin/bash", "-", NULL)". If so, default the name of this shell to our name. */ if (!shell_name || !*shell_name || (shell_name[0] == '-' && !shell_name[1])) shell_name = PROGRAM; } static void init_interactive () { interactive_shell = startup_state = interactive = 1; expand_aliases = 1; } static void init_noninteractive () { #if defined (HISTORY) bash_history_reinit (0); #endif /* HISTORY */ interactive_shell = startup_state = interactive = 0; expand_aliases = 0; no_line_editing = 1; #if defined (JOB_CONTROL) set_job_control (0); #endif /* JOB_CONTROL */ } void get_current_user_info () { struct passwd *entry; /* Don't fetch this more than once. */ if (current_user.user_name == 0) { entry = getpwuid (current_user.uid); if (entry) { current_user.user_name = savestring (entry->pw_name); current_user.shell = (entry->pw_shell && entry->pw_shell[0]) ? savestring (entry->pw_shell) : savestring ("/bin/sh"); current_user.home_dir = savestring (entry->pw_dir); } else { current_user.user_name = savestring ("I have no name!"); current_user.shell = savestring ("/bin/sh"); current_user.home_dir = savestring ("/"); } endpwent (); } } /* Do whatever is necessary to initialize the shell. Put new initializations in here. */ static void shell_initialize () { char hostname[256]; /* Line buffer output for stderr and stdout. */ if (shell_initialized == 0) { sh_setlinebuf (stderr); sh_setlinebuf (stdout); } /* Sort the array of shell builtins so that the binary search in find_shell_builtin () works correctly. */ initialize_shell_builtins (); /* Initialize the trap signal handlers before installing our own signal handlers. traps.c:restore_original_signals () is responsible for restoring the original default signal handlers. That function is called when we make a new child. */ initialize_traps (); initialize_signals (); /* It's highly unlikely that this will change. */ if (current_host_name == 0) { /* Initialize current_host_name. */ if (gethostname (hostname, 255) < 0) current_host_name = "??host??"; else current_host_name = savestring (hostname); } /* Initialize the stuff in current_user that comes from the password file. We don't need to do this right away if the shell is not interactive. */ if (interactive_shell) get_current_user_info (); /* Initialize our interface to the tilde expander. */ tilde_initialize (); /* Initialize internal and environment variables. Don't import shell functions from the environment if we are running in privileged or restricted mode or if the shell is running setuid. */ #if defined (RESTRICTED_SHELL) initialize_shell_variables (shell_environment, privileged_mode||restricted||running_setuid); #else initialize_shell_variables (shell_environment, privileged_mode||running_setuid); #endif #if 0 /* Initialize filename hash tables. */ initialize_filename_hashing (); #endif /* Initialize the data structures for storing and running jobs. */ initialize_job_control (0); /* Initialize input streams to null. */ initialize_bash_input (); /* Initialize the shell options. Don't import the shell options from the environment variable $SHELLOPTS if we are running in privileged or restricted mode or if the shell is running setuid. */ #if defined (RESTRICTED_SHELL) initialize_shell_options (privileged_mode||restricted||running_setuid); #else initialize_shell_options (privileged_mode||running_setuid); #endif } /* Function called by main () when it appears that the shell has already had some initialization performed. This is supposed to reset the world back to a pristine state, as if we had been exec'ed. */ static void shell_reinitialize () { /* The default shell prompts. */ primary_prompt = PPROMPT; secondary_prompt = SPROMPT; /* Things that get 1. */ current_command_number = 1; /* We have decided that the ~/.bashrc file should not be executed for the invocation of each shell script. If the variable $ENV (or $BASH_ENV) is set, its value is used as the name of a file to source. */ no_rc = no_profile = 1; /* Things that get 0. */ login_shell = make_login_shell = interactive = executing = 0; debugging = do_version = line_number = last_command_exit_value = 0; forced_interactive = interactive_shell = subshell_environment = 0; expand_aliases = 0; #if defined (HISTORY) bash_history_reinit (0); #endif /* HISTORY */ #if defined (RESTRICTED_SHELL) restricted = 0; #endif /* RESTRICTED_SHELL */ /* Ensure that the default startup file is used. (Except that we don't execute this file for reinitialized shells). */ bashrc_file = "~/.bashrc"; /* Delete all variables and functions. They will be reinitialized when the environment is parsed. */ delete_all_variables (shell_variables); delete_all_variables (shell_functions); shell_reinitialized = 1; } static void show_shell_usage (fp, extra) FILE *fp; int extra; { int i; char *set_opts, *s, *t; if (extra) fprintf (fp, "GNU bash, version %s-(%s)\n", shell_version_string (), MACHTYPE); fprintf (fp, "Usage:\t%s [GNU long option] [option] ...\n\t%s [GNU long option] [option] script-file ...\n", shell_name, shell_name); fputs ("GNU long options:\n", fp); for (i = 0; long_args[i].name; i++) fprintf (fp, "\t--%s\n", long_args[i].name); fputs ("Shell options:\n", fp); fputs ("\t-irsD or -c command or -O shopt_option\t\t(invocation only)\n", fp); for (i = 0, set_opts = 0; shell_builtins[i].name; i++) if (STREQ (shell_builtins[i].name, "set")) set_opts = savestring (shell_builtins[i].short_doc); if (set_opts) { s = strchr (set_opts, '['); if (s == 0) s = set_opts; while (*++s == '-') ; t = strchr (s, ']'); if (t) *t = '\0'; fprintf (fp, "\t-%s or -o option\n", s); free (set_opts); } if (extra) { fprintf (fp, "Type `%s -c \"help set\"' for more information about shell options.\n", shell_name); fprintf (fp, "Type `%s -c help' for more information about shell builtin commands.\n", shell_name); fprintf (fp, "Use the `bashbug' command to report bugs.\n"); } } static void add_shopt_to_alist (opt, on_or_off) char *opt; int on_or_off; { if (shopt_ind >= shopt_len) { shopt_len += 8; shopt_alist = (STRING_INT_ALIST *)xrealloc (shopt_alist, shopt_len * sizeof (shopt_alist[0])); } shopt_alist[shopt_ind].word = opt; shopt_alist[shopt_ind].token = on_or_off; shopt_ind++; } static void run_shopt_alist () { register int i; for (i = 0; i < shopt_ind; i++) if (shopt_setopt (shopt_alist[i].word, (shopt_alist[i].token == '-')) != EXECUTION_SUCCESS) exit (EX_USAGE); free (shopt_alist); shopt_alist = 0; shopt_ind = shopt_len = 0; } /* The second and subsequent conditions must match those used to decide whether or not to call getpeername() in isnetconn(). */ #if defined (HAVE_SYS_SOCKET_H) && defined (HAVE_GETPEERNAME) && !defined (SVR4_2) # include #endif /* Is FD a socket or network connection? */ static int isnetconn (fd) int fd; { #if defined (HAVE_GETPEERNAME) && !defined (SVR4_2) && !defined (__BEOS__) int rv; socklen_t l; struct sockaddr sa; l = sizeof(sa); rv = getpeername(fd, &sa, &l); /* Solaris 2.5 getpeername() returns EINVAL if the fd is not a socket. */ return ((rv < 0 && (errno == ENOTSOCK || errno == EINVAL)) ? 0 : 1); #else /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ # if defined (SVR4) || defined (SVR4_2) /* Sockets on SVR4 and SVR4.2 are character special (streams) devices. */ struct stat sb; if (isatty (fd)) return (0); if (fstat (fd, &sb) < 0) return (0); # if defined (S_ISFIFO) if (S_ISFIFO (sb.st_mode)) return (0); # endif /* S_ISFIFO */ return (S_ISCHR (sb.st_mode)); # else /* !SVR4 && !SVR4_2 */ # if defined (S_ISSOCK) && !defined (__BEOS__) struct stat sb; if (fstat (fd, &sb) < 0) return (0); return (S_ISSOCK (sb.st_mode)); # else /* !S_ISSOCK || __BEOS__ */ return (0); # endif /* !S_ISSOCK || __BEOS__ */ # endif /* !SVR4 && !SVR4_2 */ #endif /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ } /* sig.c - interface for shell signal handlers and signal initialization. */ /* Copyright (C) 1994 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include #include #include "shell.h" #if defined (JOB_CONTROL) #include "jobs.h" #endif /* JOB_CONTROL */ #include "siglist.h" #include "sig.h" #include "trap.h" #include "builtins/common.h" #if defined (READLINE) # include "bashline.h" #endif #if defined (HISTORY) # include "bashhist.h" #endif extern int last_command_exit_value; extern int return_catch_flag; extern int loop_level, continuing, breaking; extern int parse_and_execute_level, shell_initialized; extern int startup_state; /* Non-zero after SIGINT. */ int interrupt_state; /* The environment at the top-level R-E loop. We use this in the case of error return. */ procenv_t top_level; #if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) /* The signal masks that this shell runs with. */ sigset_t top_level_mask; #endif /* JOB_CONTROL */ /* When non-zero, we throw_to_top_level (). */ int interrupt_immediately = 0; static void initialize_shell_signals __P((void)); void initialize_signals () { initialize_shell_signals (); initialize_job_signals (); #if !defined (HAVE_SYS_SIGLIST) && !defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_STRSIGNAL) initialize_siglist (); #endif /* !HAVE_SYS_SIGLIST && !HAVE_UNDER_SYS_SIGLIST && !HAVE_STRSIGNAL */ } void reinitialize_signals () { initialize_shell_signals (); initialize_job_signals (); } /* A structure describing a signal that terminates the shell if not caught. The orig_handler member is present so children can reset these signals back to their original handlers. */ struct termsig { int signum; SigHandler *orig_handler; }; #define NULL_HANDLER (SigHandler *)SIG_DFL /* The list of signals that would terminate the shell if not caught. We catch them, but just so that we can write the history file, and so forth. */ static struct termsig terminating_signals[] = { #ifdef SIGHUP { SIGHUP, NULL_HANDLER }, #endif #ifdef SIGINT { SIGINT, NULL_HANDLER }, #endif #ifdef SIGILL { SIGILL, NULL_HANDLER }, #endif #ifdef SIGTRAP { SIGTRAP, NULL_HANDLER }, #endif #ifdef SIGIOT { SIGIOT, NULL_HANDLER }, #endif #ifdef SIGDANGER { SIGDANGER, NULL_HANDLER }, #endif #ifdef SIGEMT { SIGEMT, NULL_HANDLER }, #endif #ifdef SIGFPE { SIGFPE, NULL_HANDLER }, #endif #ifdef SIGBUS { SIGBUS, NULL_HANDLER }, #endif #ifdef SIGSEGV { SIGSEGV, NULL_HANDLER }, #endif #ifdef SIGSYS { SIGSYS, NULL_HANDLER }, #endif #ifdef SIGPIPE { SIGPIPE, NULL_HANDLER }, #endif #ifdef SIGALRM { SIGALRM, NULL_HANDLER }, #endif #ifdef SIGTERM { SIGTERM, NULL_HANDLER }, #endif #ifdef SIGXCPU { SIGXCPU, NULL_HANDLER }, #endif #ifdef SIGXFSZ { SIGXFSZ, NULL_HANDLER }, #endif #ifdef SIGVTALRM { SIGVTALRM, NULL_HANDLER }, #endif #if 0 #ifdef SIGPROF { SIGPROF, NULL_HANDLER }, #endif #endif #ifdef SIGLOST { SIGLOST, NULL_HANDLER }, #endif #ifdef SIGUSR1 { SIGUSR1, NULL_HANDLER }, #endif #ifdef SIGUSR2 { SIGUSR2, NULL_HANDLER }, #endif }; #define TERMSIGS_LENGTH (sizeof (terminating_signals) / sizeof (struct termsig)) #define XSIG(x) (terminating_signals[x].signum) #define XHANDLER(x) (terminating_signals[x].orig_handler) static int termsigs_initialized = 0; /* Initialize signals that will terminate the shell to do some unwind protection. For non-interactive shells, we only call this when a trap is defined for EXIT (0). */ void initialize_terminating_signals () { register int i; #if defined (HAVE_POSIX_SIGNALS) struct sigaction act, oact; #endif if (termsigs_initialized) return; /* The following code is to avoid an expensive call to set_signal_handler () for each terminating_signals. Fortunately, this is possible in Posix. Unfortunately, we have to call signal () on non-Posix systems for each signal in terminating_signals. */ #if defined (HAVE_POSIX_SIGNALS) act.sa_handler = termination_unwind_protect; act.sa_flags = 0; sigemptyset (&act.sa_mask); sigemptyset (&oact.sa_mask); for (i = 0; i < TERMSIGS_LENGTH; i++) sigaddset (&act.sa_mask, XSIG (i)); for (i = 0; i < TERMSIGS_LENGTH; i++) { sigaction (XSIG (i), &act, &oact); XHANDLER(i) = oact.sa_handler; /* Don't do anything with signals that are ignored at shell entry if the shell is not interactive. */ if (!interactive_shell && XHANDLER (i) == SIG_IGN) { sigaction (XSIG (i), &oact, &act); set_signal_ignored (XSIG (i)); } #if defined (SIGPROF) && !defined (_MINIX) if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) sigaction (XSIG (i), &oact, (struct sigaction *)NULL); #endif /* SIGPROF && !_MINIX */ } #else /* !HAVE_POSIX_SIGNALS */ for (i = 0; i < TERMSIGS_LENGTH; i++) { XHANDLER(i) = signal (XSIG (i), termination_unwind_protect); /* Don't do anything with signals that are ignored at shell entry if the shell is not interactive. */ if (!interactive_shell && XHANDLER (i) == SIG_IGN) { signal (XSIG (i), SIG_IGN); set_signal_ignored (XSIG (i)); } #ifdef SIGPROF if (XSIG (i) == SIGPROF && XHANDLER (i) != SIG_DFL && XHANDLER (i) != SIG_IGN) signal (XSIG (i), XHANDLER (i)); #endif } #endif /* !HAVE_POSIX_SIGNALS */ termsigs_initialized = 1; } static void initialize_shell_signals () { if (interactive) initialize_terminating_signals (); #if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) /* All shells use the signal mask they inherit, and pass it along to child processes. Children will never block SIGCHLD, though. */ sigemptyset (&top_level_mask); sigprocmask (SIG_BLOCK, (sigset_t *)NULL, &top_level_mask); # if defined (SIGCHLD) sigdelset (&top_level_mask, SIGCHLD); # endif #endif /* JOB_CONTROL || HAVE_POSIX_SIGNALS */ /* And, some signals that are specifically ignored by the shell. */ set_signal_handler (SIGQUIT, SIG_IGN); if (interactive) { set_signal_handler (SIGINT, sigint_sighandler); set_signal_handler (SIGTERM, SIG_IGN); } } void reset_terminating_signals () { register int i; #if defined (HAVE_POSIX_SIGNALS) struct sigaction act; #endif if (termsigs_initialized == 0) return; #if defined (HAVE_POSIX_SIGNALS) act.sa_flags = 0; sigemptyset (&act.sa_mask); for (i = 0; i < TERMSIGS_LENGTH; i++) { /* Skip a signal if it's trapped or handled specially, because the trap code will restore the correct value. */ if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) continue; act.sa_handler = XHANDLER (i); sigaction (XSIG (i), &act, (struct sigaction *) NULL); } #else /* !HAVE_POSIX_SIGNALS */ for (i = 0; i < TERMSIGS_LENGTH; i++) { if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) continue; signal (XSIG (i), XHANDLER (i)); } #endif /* !HAVE_POSIX_SIGNALS */ } #undef XSIG #undef XHANDLER /* What to do when we've been interrupted, and it is safe to handle it. */ void throw_to_top_level () { int print_newline = 0; if (interrupt_state) { print_newline = 1; DELINTERRUPT; } if (interrupt_state) return; last_command_exit_value |= 128; /* Run any traps set on SIGINT. */ run_interrupt_trap (); /* Cleanup string parser environment. */ while (parse_and_execute_level) parse_and_execute_cleanup (); #if defined (JOB_CONTROL) give_terminal_to (shell_pgrp, 0); #endif /* JOB_CONTROL */ #if defined (JOB_CONTROL) || defined (HAVE_POSIX_SIGNALS) /* This should not be necessary on systems using sigsetjmp/siglongjmp. */ sigprocmask (SIG_SETMASK, &top_level_mask, (sigset_t *)NULL); #endif reset_parser (); #if defined (READLINE) if (interactive) bashline_reinitialize (); #endif /* READLINE */ #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ run_unwind_protects (); loop_level = continuing = breaking = 0; return_catch_flag = 0; if (interactive && print_newline) { fflush (stdout); fprintf (stderr, "\n"); fflush (stderr); } /* An interrupted `wait' command in a script does not exit the script. */ if (interactive || (interactive_shell && !shell_initialized) || (print_newline && signal_is_trapped (SIGINT))) jump_to_top_level (DISCARD); else jump_to_top_level (EXITPROG); } /* This is just here to isolate the longjmp calls. */ void jump_to_top_level (value) int value; { longjmp (top_level, value); } sighandler termination_unwind_protect (sig) int sig; { if (sig == SIGINT && signal_is_trapped (SIGINT)) run_interrupt_trap (); #if defined (HISTORY) if (interactive_shell && sig != SIGABRT) maybe_save_shell_history (); #endif /* HISTORY */ #if defined (JOB_CONTROL) if (interactive && sig == SIGHUP) hangup_all_jobs (); end_job_control (); #endif /* JOB_CONTROL */ #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ run_exit_trap (); set_signal_handler (sig, SIG_DFL); kill (getpid (), sig); SIGRETURN (0); } /* What we really do when SIGINT occurs. */ sighandler sigint_sighandler (sig) int sig; { #if defined (MUST_REINSTALL_SIGHANDLERS) signal (sig, sigint_sighandler); #endif /* interrupt_state needs to be set for the stack of interrupts to work right. Should it be set unconditionally? */ if (interrupt_state == 0) ADDINTERRUPT; if (interrupt_immediately) { interrupt_immediately = 0; throw_to_top_level (); } SIGRETURN (0); } /* Signal functions used by the rest of the code. */ #if !defined (HAVE_POSIX_SIGNALS) #if defined (JOB_CONTROL) /* Perform OPERATION on NEWSET, perhaps leaving information in OLDSET. */ sigprocmask (operation, newset, oldset) int operation, *newset, *oldset; { int old, new; if (newset) new = *newset; else new = 0; switch (operation) { case SIG_BLOCK: old = sigblock (new); break; case SIG_SETMASK: sigsetmask (new); break; default: internal_error ("Bad code in sig.c: sigprocmask"); } if (oldset) *oldset = old; } #endif /* JOB_CONTROL */ #else #if !defined (SA_INTERRUPT) # define SA_INTERRUPT 0 #endif #if !defined (SA_RESTART) # define SA_RESTART 0 #endif SigHandler * set_signal_handler (sig, handler) int sig; SigHandler *handler; { struct sigaction act, oact; act.sa_handler = handler; act.sa_flags = 0; #if 0 if (sig == SIGALRM) act.sa_flags |= SA_INTERRUPT; /* XXX */ else act.sa_flags |= SA_RESTART; /* XXX */ #endif sigemptyset (&act.sa_mask); sigemptyset (&oact.sa_mask); sigaction (sig, &act, &oact); return (oact.sa_handler); } #endif /* HAVE_POSIX_SIGNALS */ /* siglist.c -- signal list for those machines that don't have one. */ /* Copyright (C) 1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 (HAVE_SYS_SIGLIST) && !defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_STRSIGNAL) #include #include "bashtypes.h" #include #include "siglist.h" #if !defined (NSIG) # include "trap.h" #endif #include "xmalloc.h" char *sys_siglist[NSIG]; void initialize_siglist () { register int i; for (i = 0; i < NSIG; i++) sys_siglist[i] = (char *)0x0; sys_siglist[0] = "Bogus signal"; #if defined (SIGHUP) sys_siglist[SIGHUP] = "Hangup"; #endif #if defined (SIGINT) sys_siglist[SIGINT] = "Interrupt"; #endif #if defined (SIGQUIT) sys_siglist[SIGQUIT] = "Quit"; #endif #if defined (SIGILL) sys_siglist[SIGILL] = "Illegal instruction"; #endif #if defined (SIGTRAP) sys_siglist[SIGTRAP] = "BPT trace/trap"; #endif #if defined (SIGIOT) && !defined (SIGABRT) #define SIGABRT SIGIOT #endif #if defined (SIGABRT) sys_siglist[SIGABRT] = "ABORT instruction"; #endif #if defined (SIGEMT) sys_siglist[SIGEMT] = "EMT instruction"; #endif #if defined (SIGFPE) sys_siglist[SIGFPE] = "Floating point exception"; #endif #if defined (SIGKILL) sys_siglist[SIGKILL] = "Killed"; #endif #if defined (SIGBUS) sys_siglist[SIGBUS] = "Bus error"; #endif #if defined (SIGSEGV) sys_siglist[SIGSEGV] = "Segmentation fault"; #endif #if defined (SIGSYS) sys_siglist[SIGSYS] = "Bad system call"; #endif #if defined (SIGPIPE) sys_siglist[SIGPIPE] = "Broken pipe"; #endif #if defined (SIGALRM) sys_siglist[SIGALRM] = "Alarm clock"; #endif #if defined (SIGTERM) sys_siglist[SIGTERM] = "Terminated"; #endif #if defined (SIGURG) sys_siglist[SIGURG] = "Urgent IO condition"; #endif #if defined (SIGSTOP) sys_siglist[SIGSTOP] = "Stopped (signal)"; #endif #if defined (SIGTSTP) sys_siglist[SIGTSTP] = "Stopped"; #endif #if defined (SIGCONT) sys_siglist[SIGCONT] = "Continue"; #endif #if !defined (SIGCHLD) && defined (SIGCLD) #define SIGCHLD SIGCLD #endif #if defined (SIGCHLD) sys_siglist[SIGCHLD] = "Child death or stop"; #endif #if defined (SIGTTIN) sys_siglist[SIGTTIN] = "Stopped (tty input)"; #endif #if defined (SIGTTOU) sys_siglist[SIGTTOU] = "Stopped (tty output)"; #endif #if defined (SIGIO) sys_siglist[SIGIO] = "I/O ready"; #endif #if defined (SIGXCPU) sys_siglist[SIGXCPU] = "CPU limit"; #endif #if defined (SIGXFSZ) sys_siglist[SIGXFSZ] = "File limit"; #endif #if defined (SIGVTALRM) sys_siglist[SIGVTALRM] = "Alarm (virtual)"; #endif #if defined (SIGPROF) sys_siglist[SIGPROF] = "Alarm (profile)"; #endif #if defined (SIGWINCH) sys_siglist[SIGWINCH] = "Window changed"; #endif #if defined (SIGLOST) sys_siglist[SIGLOST] = "Record lock"; #endif #if defined (SIGUSR1) sys_siglist[SIGUSR1] = "User signal 1"; #endif #if defined (SIGUSR2) sys_siglist[SIGUSR2] = "User signal 2"; #endif #if defined (SIGMSG) sys_siglist[SIGMSG] = "HFT input data pending"; #endif #if defined (SIGPWR) sys_siglist[SIGPWR] = "power failure imminent"; #endif #if defined (SIGDANGER) sys_siglist[SIGDANGER] = "system crash imminent"; #endif #if defined (SIGMIGRATE) sys_siglist[SIGMIGRATE] = "migrate process to another CPU"; #endif #if defined (SIGPRE) sys_siglist[SIGPRE] = "programming error"; #endif #if defined (SIGGRANT) sys_siglist[SIGGRANT] = "HFT monitor mode granted"; #endif #if defined (SIGRETRACT) sys_siglist[SIGRETRACT] = "HFT monitor mode retracted"; #endif #if defined (SIGSOUND) sys_siglist[SIGSOUND] = "HFT sound sequence has completed"; #endif #if defined (SIGINFO) sys_siglist[SIGINFO] = "Information request"; #endif for (i = 0; i < NSIG; i++) { if (!sys_siglist[i]) { sys_siglist[i] = (char *)xmalloc (10 + strlen ("Unknown Signal #")); sprintf (sys_siglist[i], "Unknown Signal #%d", i); } } } #endif /* !HAVE_SYS_SIGLIST && !HAVE_UNDER_SYS_SIGLIST && !HAVE_STRSIGNAL */ /* stringlib.c - Miscellaneous string functions. */ /* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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" #if defined (HAVE_UNISTD_H) # include #endif #include "bashansi.h" #include #include "chartypes.h" #include "shell.h" #include "pathexp.h" #include #if defined (EXTENDED_GLOB) # include #endif /* **************************************************************** */ /* */ /* Functions to manage arrays of strings */ /* */ /* **************************************************************** */ /* Cons up a new array of words. The words are taken from LIST, which is a WORD_LIST *. If COPY is true, everything is malloc'ed, so you should free everything in this array when you are done. The array is NULL terminated. If IP is non-null, it gets the number of words in the returned array. STARTING_INDEX says where to start filling in the returned array; it can be used to reserve space at the beginning of the array. */ char ** word_list_to_argv (list, copy, starting_index, ip) WORD_LIST *list; int copy, starting_index, *ip; { int count; char **array; count = list_length (list); array = (char **)xmalloc ((1 + count + starting_index) * sizeof (char *)); for (count = 0; count < starting_index; count++) array[count] = (char *)NULL; for (count = starting_index; list; count++, list = list->next) array[count] = copy ? savestring (list->word->word) : list->word->word; array[count] = (char *)NULL; if (ip) *ip = count; return (array); } /* Convert an array of strings into the form used internally by the shell. COPY means to copy the values in ARRAY into the returned list rather than allocate new storage. STARTING_INDEX says where in ARRAY to begin. */ WORD_LIST * argv_to_word_list (array, copy, starting_index) char **array; int copy, starting_index; { WORD_LIST *list; WORD_DESC *w; int i, count; if (array == 0 || array[0] == 0) return (WORD_LIST *)NULL; for (count = 0; array[count]; count++) ; for (i = starting_index, list = (WORD_LIST *)NULL; i < count; i++) { w = make_bare_word (copy ? "" : array[i]); if (copy) { free (w->word); w->word = array[i]; } list = make_word_list (w, list); } return (REVERSE_LIST(list, WORD_LIST *)); } /* Find STRING in ALIST, a list of string key/int value pairs. If FLAGS is 1, STRING is treated as a pattern and matched using strmatch. */ int find_string_in_alist (string, alist, flags) char *string; STRING_INT_ALIST *alist; int flags; { register int i; int r; for (i = r = 0; alist[i].word; i++) { #if defined (EXTENDED_GLOB) if (flags) r = strmatch (alist[i].word, string, FNM_EXTMATCH) != FNM_NOMATCH; else #endif r = STREQ (string, alist[i].word); if (r) return (alist[i].token); } return -1; } /* **************************************************************** */ /* */ /* String Management Functions */ /* */ /* **************************************************************** */ /* Replace occurrences of PAT with REP in STRING. If GLOBAL is non-zero, replace all occurrences, otherwise replace only the first. This returns a new string; the caller should free it. */ char * strsub (string, pat, rep, global) char *string, *pat, *rep; int global; { int patlen, replen, templen, tempsize, repl, i; char *temp, *r; patlen = strlen (pat); replen = strlen (rep); for (temp = (char *)NULL, i = templen = tempsize = 0, repl = 1; string[i]; ) { if (repl && STREQN (string + i, pat, patlen)) { if (replen) RESIZE_MALLOCED_BUFFER (temp, templen, replen, tempsize, (replen * 2)); for (r = rep; *r; ) temp[templen++] = *r++; i += patlen ? patlen : 1; /* avoid infinite recursion */ repl = global != 0; } else { RESIZE_MALLOCED_BUFFER (temp, templen, 1, tempsize, 16); temp[templen++] = string[i++]; } } temp[templen] = 0; return (temp); } /* Replace all instances of C in STRING with TEXT. TEXT may be empty or NULL. If DO_GLOB is non-zero, we quote the replacement text for globbing. Backslash may be used to quote C. */ char * strcreplace (string, c, text, do_glob) char *string; int c; char *text; int do_glob; { char *ret, *p, *r, *t; int len, rlen, ind, tlen; len = STRLEN (text); rlen = len + strlen (string) + 2; ret = (char *)xmalloc (rlen); for (p = string, r = ret; p && *p; ) { if (*p == c) { if (len) { ind = r - ret; if (do_glob && (glob_pattern_p (text) || strchr (text, '\\'))) { t = quote_globbing_chars (text); tlen = strlen (t); RESIZE_MALLOCED_BUFFER (ret, ind, tlen, rlen, rlen); r = ret + ind; /* in case reallocated */ strcpy (r, t); r += tlen; free (t); } else { RESIZE_MALLOCED_BUFFER (ret, ind, len, rlen, rlen); r = ret + ind; /* in case reallocated */ strcpy (r, text); r += len; } } p++; continue; } if (*p == '\\' && p[1] == c) p++; ind = r - ret; RESIZE_MALLOCED_BUFFER (ret, ind, 2, rlen, rlen); r = ret + ind; /* in case reallocated */ *r++ = *p++; } *r = '\0'; return ret; } #ifdef INCLUDE_UNUSED /* Remove all leading whitespace from STRING. This includes newlines. STRING should be terminated with a zero. */ void strip_leading (string) char *string; { char *start = string; while (*string && (whitespace (*string) || *string == '\n')) string++; if (string != start) { int len = strlen (string); FASTCOPY (string, start, len); start[len] = '\0'; } } #endif /* Remove all trailing whitespace from STRING. This includes newlines. If NEWLINES_ONLY is non-zero, only trailing newlines are removed. STRING should be terminated with a zero. */ void strip_trailing (string, len, newlines_only) char *string; int len; int newlines_only; { while (len >= 0) { if ((newlines_only && string[len] == '\n') || (!newlines_only && whitespace (string[len]))) len--; else break; } string[len + 1] = '\0'; } /* A wrapper for bcopy that can be prototyped in general.h */ void xbcopy (s, d, n) char *s, *d; int n; { FASTCOPY (s, d, n); } /* subst.c -- The part of the shell that does parameter, command, and globbing substitutions. */ /* ``Have a little faith, there's magic in the night. You ain't a beauty, but, hey, you're alright.'' */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 #include "chartypes.h" #include #include #include #if defined (HAVE_UNISTD_H) # include #endif #include "bashansi.h" #include "posixstat.h" #include "shell.h" #include "flags.h" #include "jobs.h" #include "execute_cmd.h" #include "filecntl.h" #include "trap.h" #include "pathexp.h" #include "mailcheck.h" #include "builtins/getopt.h" #include "builtins/common.h" #include #include #if !defined (errno) extern int errno; #endif /* !errno */ /* The size that strings change by. */ #define DEFAULT_INITIAL_ARRAY_SIZE 112 #define DEFAULT_ARRAY_SIZE 128 /* Variable types. */ #define VT_VARIABLE 0 #define VT_POSPARMS 1 #define VT_ARRAYVAR 2 #define VT_ARRAYMEMBER 3 /* Flags for quoted_strchr */ #define ST_BACKSL 0x01 #define ST_CTLESC 0x02 /* These defs make it easier to use the editor. */ #define LBRACE '{' #define RBRACE '}' #define LPAREN '(' #define RPAREN ')' /* Evaluates to 1 if C is one of the shell's special parameters whose length can be taken, but is also one of the special expansion characters. */ #define VALID_SPECIAL_LENGTH_PARAM(c) \ ((c) == '-' || (c) == '?' || (c) == '#') /* Evaluates to 1 if C is one of the shell's special parameters for which an indirect variable reference may be made. */ #define VALID_INDIR_PARAM(c) \ ((c) == '#' || (c) == '?' || (c) == '@' || (c) == '*') /* Evaluates to 1 if C is one of the OP characters that follows the parameter in ${parameter[:]OPword}. */ #define VALID_PARAM_EXPAND_CHAR(c) \ ((c) == '-' || (c) == '=' || (c) == '?' || (c) == '+') /* Evaluates to 1 if this is one of the shell's special variables. */ #define SPECIAL_VAR(name, wi) \ ((DIGIT (*name) && all_digits (name)) || \ (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \ (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1]))) /* An expansion function that takes a string and a quoted flag and returns a WORD_LIST *. Used as the type of the third argument to expand_string_if_necessary(). */ typedef WORD_LIST *EXPFUNC __P((char *, int)); /* Process ID of the last command executed within command substitution. */ pid_t last_command_subst_pid = NO_PID; pid_t current_command_subst_pid = NO_PID; /* Extern functions and variables from different files. */ extern int last_command_exit_value; extern int subshell_environment, startup_state; extern int return_catch_flag, return_catch_value; extern pid_t dollar_dollar_pid; extern int posixly_correct; extern char *this_command_name; extern struct fd_bitmap *current_fds_to_close; extern int wordexp_only; /* Non-zero means to allow unmatched globbed filenames to expand to a null file. */ int allow_null_glob_expansion; #if 0 /* Variables to keep track of which words in an expanded word list (the output of expand_word_list_internal) are the result of globbing expansions. GLOB_ARGV_FLAGS is used by execute_cmd.c. (CURRENTLY UNUSED). */ char *glob_argv_flags; static int glob_argv_flags_size; #endif static WORD_LIST expand_word_error, expand_word_fatal; static char expand_param_error, expand_param_fatal; /* Tell the expansion functions to not longjmp back to top_level on fatal errors. Enabled when doing completion and prompt string expansion. */ static int no_longjmp_on_fatal_error = 0; /* Set by expand_word_unsplit; used to inhibit splitting and re-joining $* on $IFS, primarily when doing assignment statements. */ static int expand_no_split_dollar_star = 0; /* Used to hold a list of variable assignments preceding a command. Global so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a SIGCHLD trap. */ WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL; /* A WORD_LIST of words to be expanded by expand_word_list_internal, without any leading variable assignments. */ static WORD_LIST *garglist = (WORD_LIST *)NULL; static char *quoted_substring __P((char *, int, int)); static inline char *quoted_strchr __P((char *, int, int)); static char *expand_string_if_necessary __P((char *, int, EXPFUNC *)); static inline char *expand_string_to_string_internal __P((char *, int, EXPFUNC *)); static WORD_LIST *call_expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); static WORD_LIST *expand_string_internal __P((char *, int)); static WORD_LIST *expand_string_leave_quoted __P((char *, int)); static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *)); static char *remove_quoted_escapes __P((char *)); static WORD_LIST *list_quote_escapes __P((WORD_LIST *)); static char *make_quoted_char __P((int)); static WORD_LIST *quote_list __P((WORD_LIST *)); static WORD_LIST *dequote_list __P((WORD_LIST *)); static void remove_quoted_nulls __P((char *)); static int unquoted_substring __P((char *, char *)); static int unquoted_member __P((int, char *)); static int do_assignment_internal __P((const char *, int)); static char *string_extract_verbatim __P((char *, int *, char *)); static char *string_extract __P((char *, int *, char *, int)); static char *string_extract_double_quoted __P((char *, int *, int)); static char *string_extract_single_quoted __P((char *, int *)); static inline int skip_single_quoted __P((char *, int)); static int skip_double_quoted __P((char *, int)); static char *extract_delimited_string __P((char *, int *, char *, char *, char *)); static char *extract_dollar_brace_string __P((char *, int *, int)); static char *string_list_internal __P((WORD_LIST *, char *)); static char *pos_params __P((char *, int, int, int)); static char *remove_pattern __P((char *, char *, int)); static int match_pattern_char __P((char *, char *)); static int match_pattern __P((char *, char *, int, char **, char **)); static int getpatspec __P((int, char *)); static char *getpattern __P((char *, int, int)); static char *parameter_brace_remove_pattern __P((char *, char *, int, int)); static char *list_remove_pattern __P((WORD_LIST *, char *, int, int, int)); static char *parameter_list_remove_pattern __P((char *, int, int, int)); #ifdef ARRAY_VARS static char *array_remove_pattern __P((char *, char *, char *, int, int)); #endif static char *process_substitute __P((char *, int)); static char *read_comsub __P((int, int)); #ifdef ARRAY_VARS static arrayind_t array_length_reference __P((char *)); #endif static int valid_brace_expansion_word __P((char *, int)); static char *parameter_brace_expand_word __P((char *, int, int)); static char *parameter_brace_expand_indir __P((char *, int, int)); static char *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *)); static void parameter_brace_expand_error __P((char *, char *)); static int valid_length_expression __P((char *)); static long parameter_brace_expand_length __P((char *)); static char *skiparith __P((char *, int)); static int verify_substring_values __P((char *, char *, int, long *, long *)); static int get_var_and_type __P((char *, char *, SHELL_VAR **, char **)); static char *parameter_brace_substring __P((char *, char *, char *, int)); static char *pos_params_pat_subst __P((char *, char *, char *, int)); static char *parameter_brace_patsub __P((char *, char *, char *, int)); static char *parameter_brace_expand __P((char *, int *, int, int *, int *)); static char *param_expand __P((char *, int *, int, int *, int *, int *, int *, int)); static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *)); static char *getifs __P((void)); static WORD_LIST *word_list_split __P((WORD_LIST *)); static WORD_LIST *separate_out_assignments __P((WORD_LIST *)); static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int)); #ifdef BRACE_EXPANSION static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int)); #endif static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int)); static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int)); /* **************************************************************** */ /* */ /* Utility Functions */ /* */ /* **************************************************************** */ /* Cons a new string from STRING starting at START and ending at END, not including END. */ char * substring (string, start, end) char *string; int start, end; { register int len; register char *result; len = end - start; result = (char *)xmalloc (len + 1); strncpy (result, string + start, len); result[len] = '\0'; return (result); } static char * quoted_substring (string, start, end) char *string; int start, end; { register int len, l; register char *result, *s, *r; len = end - start; /* Move to string[start], skipping quoted characters. */ for (s = string, l = 0; *s && l < start; ) { if (*s == CTLESC) { s++; continue; } l++; if (*s == 0) break; } r = result = (char *)xmalloc (2*len + 1); /* save room for quotes */ /* Copy LEN characters, including quote characters. */ s = string + l; for (l = 0; l < len; s++) { if (*s == CTLESC) *r++ = *s++; *r++ = *s; l++; if (*s == 0) break; } *r = '\0'; return result; } /* Find the first occurrence of character C in string S, obeying shell quoting rules. If (FLAGS & ST_BACKSL) is non-zero, backslash-escaped characters are skipped. If (FLAGS & ST_CTLESC) is non-zero, characters escaped with CTLESC are skipped. */ static inline char * quoted_strchr (s, c, flags) char *s; int c, flags; { register char *p; for (p = s; *p; p++) { if (((flags & ST_BACKSL) && *p == '\\') || ((flags & ST_CTLESC) && *p == CTLESC)) { p++; if (*p == '\0') return ((char *)NULL); continue; } else if (*p == c) return p; } return ((char *)NULL); } /* Return 1 if CHARACTER appears in an unquoted portion of STRING. Return 0 otherwise. */ static int unquoted_member (character, string) int character; char *string; { int sindex, c; for (sindex = 0; c = string[sindex]; ) { if (c == character) return (1); switch (c) { default: sindex++; break; case '\\': sindex++; if (string[sindex]) sindex++; break; case '\'': sindex = skip_single_quoted (string, ++sindex); break; case '"': sindex = skip_double_quoted (string, ++sindex); break; } } return (0); } /* Return 1 if SUBSTR appears in an unquoted portion of STRING. */ static int unquoted_substring (substr, string) char *substr, *string; { int sindex, c, sublen; if (substr == 0 || *substr == '\0') return (0); sublen = strlen (substr); for (sindex = 0; c = string[sindex]; ) { if (STREQN (string + sindex, substr, sublen)) return (1); switch (c) { case '\\': sindex++; if (string[sindex]) sindex++; break; case '\'': sindex = skip_single_quoted (string, ++sindex); break; case '"': sindex = skip_double_quoted (string, ++sindex); break; default: sindex++; break; } } return (0); } /* Most of the substitutions must be done in parallel. In order to avoid using tons of unclear goto's, I have some functions for manipulating malloc'ed strings. They all take INDX, a pointer to an integer which is the offset into the string where manipulation is taking place. They also take SIZE, a pointer to an integer which is the current length of the character array for this string. */ /* Append SOURCE to TARGET at INDEX. SIZE is the current amount of space allocated to TARGET. SOURCE can be NULL, in which case nothing happens. Gets rid of SOURCE by freeing it. Returns TARGET in case the location has changed. */ inline char * sub_append_string (source, target, indx, size) char *source, *target; int *indx, *size; { if (source) { int srclen, n; srclen = STRLEN (source); if (srclen >= (int)(*size - *indx)) { n = srclen + *indx; n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE); target = (char *)xrealloc (target, (*size = n)); } FASTCOPY (source, target + *indx, srclen); *indx += srclen; target[*indx] = '\0'; free (source); } return (target); } #if 0 /* UNUSED */ /* Append the textual representation of NUMBER to TARGET. INDX and SIZE are as in SUB_APPEND_STRING. */ char * sub_append_number (number, target, indx, size) long number; int *indx, *size; char *target; { char *temp; temp = itos (number); return (sub_append_string (temp, target, indx, size)); } #endif /* Extract a substring from STRING, starting at SINDEX and ending with one of the characters in CHARLIST. Don't make the ending character part of the string. Leave SINDEX pointing at the ending character. Understand about backslashes in the string. If VARNAME is non-zero, and array variables have been compiled into the shell, everything between a `[' and a corresponding `]' is skipped over. */ static char * string_extract (string, sindex, charlist, varname) char *string; int *sindex; char *charlist; int varname; { register int c, i; char *temp; for (i = *sindex; c = string[i]; i++) { if (c == '\\') if (string[i + 1]) i++; else break; #if defined (ARRAY_VARS) else if (varname && c == '[') { int ni; /* If this is an array subscript, skip over it and continue. */ ni = skipsubscript (string, i); if (string[ni] == ']') i = ni; } #endif else if (MEMBER (c, charlist)) break; } temp = substring (string, *sindex, i); *sindex = i; return (temp); } /* Extract the contents of STRING as if it is enclosed in double quotes. SINDEX, when passed in, is the offset of the character immediately following the opening double quote; on exit, SINDEX is left pointing after the closing double quote. If STRIPDQ is non-zero, unquoted double quotes are stripped and the string is terminated by a null byte. Backslashes between the embedded double quotes are processed. If STRIPDQ is zero, an unquoted `"' terminates the string. */ static inline char * string_extract_double_quoted (string, sindex, stripdq) char *string; int *sindex, stripdq; { int j, i, t; unsigned char c; char *temp, *ret; /* The new string we return. */ int pass_next, backquote, si; /* State variables for the machine. */ int dquote; pass_next = backquote = dquote = 0; temp = (char *)xmalloc (1 + strlen (string) - *sindex); for (j = 0, i = *sindex; c = string[i]; i++) { /* Process a character that was quoted by a backslash. */ if (pass_next) { /* Posix.2 sez: ``The backslash shall retain its special meaning as an escape character only when followed by one of the characters: $ ` " \ ''. If STRIPDQ is zero, we handle the double quotes here and let expand_word_internal handle the rest. If STRIPDQ is non-zero, we have already been through one round of backslash stripping, and want to strip these backslashes only if DQUOTE is non-zero, indicating that we are inside an embedded double-quoted string. */ /* If we are in an embedded quoted string, then don't strip backslashes before characters for which the backslash retains its special meaning, but remove backslashes in front of other characters. If we are not in an embedded quoted string, don't strip backslashes at all. This mess is necessary because the string was already surrounded by double quotes (and sh has some really weird quoting rules). The returned string will be run through expansion as if it were double-quoted. */ if ((stripdq == 0 && c != '"') || (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0))) temp[j++] = '\\'; temp[j++] = c; pass_next = 0; continue; } /* A backslash protects the next character. The code just above handles preserving the backslash in front of any character but a double quote. */ if (c == '\\') { pass_next++; continue; } /* Inside backquotes, ``the portion of the quoted string from the initial backquote and the characters up to the next backquote that is not preceded by a backslash, having escape characters removed, defines that command''. */ if (backquote) { if (c == '`') backquote = 0; temp[j++] = c; continue; } if (c == '`') { temp[j++] = c; backquote++; continue; } /* Pass everything between `$(' and the matching `)' or a quoted ${ ... } pair through according to the Posix.2 specification. */ if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) { si = i + 2; if (string[i + 1] == LPAREN) ret = extract_delimited_string (string, &si, "$(", "(", ")"); /*)*/ else ret = extract_dollar_brace_string (string, &si, 1); temp[j++] = '$'; temp[j++] = string[i + 1]; for (t = 0; ret[t]; t++, j++) temp[j] = ret[t]; temp[j++] = string[si]; i = si; free (ret); continue; } /* Add any character but a double quote to the quoted string we're accumulating. */ if (c != '"') { temp[j++] = c; continue; } /* c == '"' */ if (stripdq) { dquote ^= 1; continue; } break; } temp[j] = '\0'; /* Point to after the closing quote. */ if (c) i++; *sindex = i; return (temp); } /* This should really be another option to string_extract_double_quoted. */ static int skip_double_quoted (string, sind) char *string; int sind; { int c, i; char *ret; int pass_next, backquote, si; pass_next = backquote = 0; for (i = sind; c = string[i]; i++) { if (pass_next) { pass_next = 0; continue; } else if (c == '\\') { pass_next++; continue; } else if (backquote) { if (c == '`') backquote = 0; continue; } else if (c == '`') { backquote++; continue; } else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE))) { si = i + 2; if (string[i + 1] == LPAREN) ret = extract_delimited_string (string, &si, "$(", "(", ")"); else ret = extract_dollar_brace_string (string, &si, 0); i = si; free (ret); continue; } else if (c != '"') continue; else break; } if (c) i++; return (i); } /* Extract the contents of STRING as if it is enclosed in single quotes. SINDEX, when passed in, is the offset of the character immediately following the opening single quote; on exit, SINDEX is left pointing after the closing single quote. */ static inline char * string_extract_single_quoted (string, sindex) char *string; int *sindex; { register int i; char *t; for (i = *sindex; string[i] && string[i] != '\''; i++) ; t = substring (string, *sindex, i); if (string[i]) i++; *sindex = i; return (t); } static inline int skip_single_quoted (string, sind) char *string; int sind; { register int c; for (c = sind; string[c] && string[c] != '\''; c++) ; if (string[c]) c++; return c; } /* Just like string_extract, but doesn't hack backslashes or any of that other stuff. Obeys CTLESC quoting. Used to do splitting on $IFS. */ static char * string_extract_verbatim (string, sindex, charlist) char *string; int *sindex; char *charlist; { register int i = *sindex; int c; char *temp; if (charlist[0] == '\'' && charlist[1] == '\0') { temp = string_extract_single_quoted (string, sindex); --*sindex; /* leave *sindex at separator character */ return temp; } for (i = *sindex; c = string[i]; i++) { if (c == CTLESC) { i++; continue; } if (MEMBER (c, charlist)) break; } temp = substring (string, *sindex, i); *sindex = i; return (temp); } /* Extract the $( construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "$(". Make (SINDEX) get the position of the matching ")". */ char * extract_command_subst (string, sindex) char *string; int *sindex; { return (extract_delimited_string (string, sindex, "$(", "(", ")")); } /* Extract the $[ construct in STRING, and return a new string. (]) Start extracting at (SINDEX) as if we had just seen "$[". Make (SINDEX) get the position of the matching "]". */ char * extract_arithmetic_subst (string, sindex) char *string; int *sindex; { return (extract_delimited_string (string, sindex, "$[", "[", "]")); /*]*/ } #if defined (PROCESS_SUBSTITUTION) /* Extract the <( or >( construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "<(". Make (SINDEX) get the position of the matching ")". */ /*))*/ char * extract_process_subst (string, starter, sindex) char *string; char *starter; int *sindex; { return (extract_delimited_string (string, sindex, starter, "(", ")")); } #endif /* PROCESS_SUBSTITUTION */ #if defined (ARRAY_VARS) char * extract_array_assignment_list (string, sindex) char *string; int *sindex; { return (extract_delimited_string (string, sindex, "(", (char *)NULL, ")")); } #endif /* Extract and create a new string from the contents of STRING, a character string delimited with OPENER and CLOSER. SINDEX is the address of an int describing the current offset in STRING; it should point to just after the first OPENER found. On exit, SINDEX gets the position of the last character of the matching CLOSER. If OPENER is more than a single character, ALT_OPENER, if non-null, contains a character string that can also match CLOSER and thus needs to be skipped. */ static char * extract_delimited_string (string, sindex, opener, alt_opener, closer) char *string; int *sindex; char *opener, *alt_opener, *closer; { int i, c, si; char *t, *result; int pass_character, nesting_level; int len_closer, len_opener, len_alt_opener; len_opener = STRLEN (opener); len_alt_opener = STRLEN (alt_opener); len_closer = STRLEN (closer); pass_character = 0; nesting_level = 1; i = *sindex; while (nesting_level) { c = string[i]; if (c == 0) break; if (pass_character) /* previous char was backslash */ { pass_character = 0; i++; continue; } if (c == CTLESC) { pass_character++; i++; continue; } if (c == '\\') { pass_character++; i++; continue; } /* Process a nested OPENER. */ if (STREQN (string + i, opener, len_opener)) { si = i + len_opener; t = extract_delimited_string (string, &si, opener, alt_opener, closer); i = si + 1; FREE (t); continue; } /* Process a nested ALT_OPENER */ if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener)) { si = i + len_alt_opener; t = extract_delimited_string (string, &si, alt_opener, alt_opener, closer); i = si + 1; FREE (t); continue; } /* If the current substring terminates the delimited string, decrement the nesting level. */ if (STREQN (string + i, closer, len_closer)) { i += len_closer - 1; /* move to last char of the closer */ nesting_level--; if (nesting_level == 0) break; } /* Pass old-style command substitution through verbatim. */ if (c == '`') { si = i + 1; t = string_extract (string, &si, "`", 0); i = si + 1; FREE (t); continue; } /* Pass single-quoted strings through verbatim. */ if (c == '\'') { si = i + 1; i = skip_single_quoted (string, si); continue; } /* Pass embedded double-quoted strings through verbatim as well. */ if (c == '"') { si = i + 1; i = skip_double_quoted (string, si); continue; } i++; /* move past this character, which was not special. */ } #if 0 if (c == 0 && nesting_level) #else if (c == 0 && nesting_level && no_longjmp_on_fatal_error == 0) #endif { report_error ("bad substitution: no `%s' in %s", closer, string); last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (DISCARD); } si = i - *sindex - len_closer + 1; result = (char *)xmalloc (1 + si); strncpy (result, string + *sindex, si); result[si] = '\0'; *sindex = i; return (result); } /* Extract a parameter expansion expression within ${ and } from STRING. Obey the Posix.2 rules for finding the ending `}': count braces while skipping over enclosed quoted strings and command substitutions. SINDEX is the address of an int describing the current offset in STRING; it should point to just after the first `{' found. On exit, SINDEX gets the position of the matching `}'. QUOTED is non-zero if this occurs inside double quotes. */ /* XXX -- this is very similar to extract_delimited_string -- XXX */ static char * extract_dollar_brace_string (string, sindex, quoted) char *string; int *sindex, quoted; { register int i, c; int pass_character, nesting_level, si; char *result, *t; pass_character = 0; nesting_level = 1; for (i = *sindex; (c = string[i]); i++) { if (pass_character) { pass_character = 0; continue; } /* CTLESCs and backslashes quote the next character. */ if (c == CTLESC || c == '\\') { pass_character++; continue; } if (string[i] == '$' && string[i+1] == LBRACE) { nesting_level++; i++; continue; } if (c == RBRACE) { nesting_level--; if (nesting_level == 0) break; continue; } /* Pass the contents of old-style command substitutions through verbatim. */ if (c == '`') { si = i + 1; t = string_extract (string, &si, "`", 0); i = si; free (t); continue; } /* Pass the contents of new-style command substitutions and arithmetic substitutions through verbatim. */ if (string[i] == '$' && string[i+1] == LPAREN) { si = i + 2; t = extract_delimited_string (string, &si, "$(", "(", ")"); /*)*/ i = si; free (t); continue; } /* Pass the contents of single-quoted and double-quoted strings through verbatim. */ if (c == '\'' || c == '"') { si = i + 1; i = (c == '\'') ? skip_single_quoted (string, si) : skip_double_quoted (string, si); /* skip_XXX_quoted leaves index one past close quote */ i--; continue; } } if (c == 0 && nesting_level && no_longjmp_on_fatal_error == 0) { report_error ("bad substitution: no ending `}' in %s", string); last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (DISCARD); } result = substring (string, *sindex, i); *sindex = i; return (result); } /* Remove backslashes which are quoting backquotes from STRING. Modifies STRING, and returns a pointer to it. */ char * de_backslash (string) char *string; { register int i, l; for (i = 0, l = strlen (string); i < l; i++) if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' || string[i + 1] == '$')) strcpy (string + i, string + i + 1); /* XXX - should be memmove */ return (string); } #if 0 /*UNUSED*/ /* Replace instances of \! in a string with !. */ void unquote_bang (string) char *string; { register int i, j; register char *temp; temp = (char *)xmalloc (1 + strlen (string)); for (i = 0, j = 0; (temp[j] = string[i]); i++, j++) { if (string[i] == '\\' && string[i + 1] == '!') { temp[j] = '!'; i++; } } strcpy (string, temp); free (temp); } #endif #if defined (READLINE) /* Return 1 if the portion of STRING ending at EINDEX is quoted (there is an unclosed quoted string), or if the character at EINDEX is quoted by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various single and double-quoted string parsing functions should not return an error if there are unclosed quotes or braces. */ #define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0) int char_is_quoted (string, eindex) char *string; int eindex; { int i, pass_next; no_longjmp_on_fatal_error = 1; for (i = pass_next = 0; i <= eindex; i++) { if (pass_next) { pass_next = 0; if (i >= eindex) /* XXX was if (i >= eindex - 1) */ CQ_RETURN(1); continue; } else if (string[i] == '\'' || string[i] == '"') { i = (string[i] == '\'') ? skip_single_quoted (string, ++i) : skip_double_quoted (string, ++i); if (i > eindex) CQ_RETURN(1); i--; /* the skip functions increment past the closing quote. */ } else if (string[i] == '\\') { pass_next = 1; continue; } } CQ_RETURN(0); } int unclosed_pair (string, eindex, openstr) char *string; int eindex; char *openstr; { int i, pass_next, openc, olen; olen = strlen (openstr); for (i = pass_next = openc = 0; i <= eindex; i++) { if (pass_next) { pass_next = 0; if (i >= eindex) /* XXX was if (i >= eindex - 1) */ return 0; continue; } else if (STREQN (string + i, openstr, olen)) { openc = 1 - openc; i += olen - 1; } else if (string[i] == '\'' || string[i] == '"') { i = (string[i] == '\'') ? skip_single_quoted (string, i) : skip_double_quoted (string, i); if (i > eindex) return 0; } else if (string[i] == '\\') { pass_next = 1; continue; } } return (openc); } /* Skip characters in STRING until we find a character in DELIMS, and return the index of that character. START is the index into string at which we begin. This is similar in spirit to strpbrk, but it returns an index into STRING and takes a starting index. This little piece of code knows quite a lot of shell syntax. It's very similar to skip_double_quoted and other functions of that ilk. */ int skip_to_delim (string, start, delims) char *string; int start; char *delims; { int i, pass_next, backq, si; char *temp; no_longjmp_on_fatal_error = 1; for (i = start, pass_next = backq = 0; string[i]; i++) { if (pass_next) { pass_next = 0; if (string[i] == 0) CQ_RETURN(i); continue; } else if (string[i] == '\\') { pass_next = 1; continue; } else if (backq) { if (string[i] == '`') backq = 0; continue; } else if (string[i] == '`') { backq = 1; continue; } else if (string[i] == '\'' || string[i] == '"') { i = (string[i] == '\'') ? skip_single_quoted (string, ++i) : skip_double_quoted (string, ++i); i--; /* the skip functions increment past the closing quote. */ } else if (string[i] == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE)) { si = i + 2; if (string[si] == '\0') CQ_RETURN(si); if (string[i+1] == LPAREN) temp = extract_delimited_string (string, &si, "$(", "(", ")"); /* ) */ else temp = extract_dollar_brace_string (string, &si, 0); i = si; free (temp); if (string[i] == '\0') /* don't increment i past EOS in loop */ break; continue; } else if (member (string[i], delims)) break; } CQ_RETURN(i); } /* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the individual words. If DELIMS is NULL, the current value of $IFS is used to split the string. SENTINEL is an index to look for. NWP, if non-NULL gets the number of words in the returned list. CWP, if non-NULL, gets the index of the word containing SENTINEL. Non-whitespace chars in DELIMS delimit separate fields. */ WORD_LIST * split_at_delims (string, slen, delims, sentinel, nwp, cwp) char *string; int slen; char *delims; int sentinel; int *nwp, *cwp; { int ts, te, i, nw, cw; char *token, *d, *d2; WORD_LIST *ret, *tl; if (string == 0 || *string == '\0') { if (nwp) *nwp = 0; if (cwp) *cwp = 0; return ((WORD_LIST *)NULL); } d = (delims == 0) ? getifs () : delims; /* Make d2 the non-whitespace characters in delims */ d2 = 0; if (delims) { d2 = (char *)xmalloc (strlen (delims) + 1); for (i = ts = 0; delims[i]; i++) { if (whitespace(delims[i]) == 0) d2[ts++] = delims[i]; } d2[ts] = '\0'; } ret = (WORD_LIST *)NULL; for (i = 0; member (string[i], d) && (whitespace(string[i]) || string[i] == '\n'); i++) ; if (string[i] == '\0') return (ret); ts = i; nw = 0; cw = -1; while (1) { te = skip_to_delim (string, ts, d); /* If we have a non-whitespace delimiter character, use it to make a separate field. This is just about what $IFS splitting does and is closer to the behavior of the shell parser. */ if (ts == te && d2 && member (string[ts], d2)) { te = ts + 1; while (member (string[te], d2)) te++; } token = substring (string, ts, te); ret = add_string_to_list (token, ret); free (token); nw++; if (sentinel >= ts && sentinel <= te) cw = nw; /* If the cursor is at whitespace just before word start, set the sentinel word to the current word. */ if (cwp && cw == -1 && sentinel == ts-1) cw = nw; /* If the cursor is at whitespace between two words, make a new, empty word, add it before (well, after, since the list is in reverse order) the word we just added, and set the current word to that one. */ if (cwp && cw == -1 && sentinel < ts) { tl = (WORD_LIST *)xmalloc (sizeof (WORD_LIST)); tl->word = make_word (""); tl->next = ret->next; ret->next = tl; cw = nw; nw++; } if (string[te] == 0) break; i = te /* + member (string[te], d) */; while (member (string[i], d) && whitespace(string[i])) i++; if (string[i]) ts = i; else break; } /* Special case for SENTINEL at the end of STRING. If we haven't found the word containing SENTINEL yet, and the index we're looking for is at the end of STRING, add an additional null argument and set the current word pointer to that. */ if (cwp && cw == -1 && sentinel >= slen) { if (whitespace (string[sentinel - 1])) { token = ""; ret = add_string_to_list (token, ret); nw++; } cw = nw; } if (nwp) *nwp = nw; if (cwp) *cwp = cw; return (REVERSE_LIST (ret, WORD_LIST *)); } #endif /* READLINE */ #if 0 /* UNUSED */ /* Extract the name of the variable to bind to from the assignment string. */ char * assignment_name (string) char *string; { int offset; char *temp; offset = assignment (string); if (offset == 0) return (char *)NULL; temp = substring (string, 0, offset); return (temp); } #endif /* **************************************************************** */ /* */ /* Functions to convert strings to WORD_LISTs and vice versa */ /* */ /* **************************************************************** */ /* Return a single string of all the words in LIST. SEP is the separator to put between individual elements of LIST in the output string. */ static char * string_list_internal (list, sep) WORD_LIST *list; char *sep; { register WORD_LIST *t; char *result, *r; int word_len, sep_len, result_size; if (list == 0) return ((char *)NULL); /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */ sep_len = STRLEN (sep); result_size = 0; for (t = list; t; t = t->next) { if (t != list) result_size += sep_len; result_size += strlen (t->word->word); } r = result = (char *)xmalloc (result_size + 1); for (t = list; t; t = t->next) { if (t != list && sep_len) { if (sep_len > 1) { FASTCOPY (sep, r, sep_len); r += sep_len; } else *r++ = sep[0]; } word_len = strlen (t->word->word); FASTCOPY (t->word->word, r, word_len); r += word_len; } *r = '\0'; return (result); } /* Return a single string of all the words present in LIST, separating each word with a space. */ char * string_list (list) WORD_LIST *list; { return (string_list_internal (list, " ")); } /* Return a single string of all the words present in LIST, obeying the quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the expansion [of $*] appears within a double quoted string, it expands to a single field with the value of each parameter separated by the first character of the IFS variable, or by a if IFS is unset." */ char * string_list_dollar_star (list) WORD_LIST *list; { char *ifs, sep[2]; ifs = get_string_value ("IFS"); sep[0] = (ifs == 0) ? ' ' : *ifs; sep[1] = '\0'; return (string_list_internal (list, sep)); } /* Turn $@ into a string. If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) is non-zero, the $@ appears within double quotes, and we should quote the list before converting it into a string. If IFS is unset, and the word is not quoted, we just need to quote CTLESC and CTLNUL characters in the words in the list, because the default value of $IFS is , IFS characters in the words in the list should also be split. If IFS is null, and the word is not quoted, we need to quote the words in the list to preserve the positional parameters exactly. */ char * string_list_dollar_at (list, quoted) WORD_LIST *list; int quoted; { char *ifs, sep[2]; WORD_LIST *tlist; ifs = get_string_value ("IFS"); sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs; sep[1] = '\0'; tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0)) ? quote_list (list) : list_quote_escapes (list); return (string_list_internal (tlist, sep)); } /* Return the list of words present in STRING. Separate the string into words at any of the characters found in SEPARATORS. If QUOTED is non-zero then word in the list will have its quoted flag set, otherwise the quoted flag is left as make_word () deemed fit. This obeys the P1003.2 word splitting semantics. If `separators' is exactly , then the splitting algorithm is that of the Bourne shell, which treats any sequence of characters from `separators' as a delimiter. If IFS is unset, which results in `separators' being set to "", no splitting occurs. If separators has some other value, the following rules are applied (`IFS white space' means zero or more occurrences of , , or , as long as those characters are in `separators'): 1) IFS white space is ignored at the start and the end of the string. 2) Each occurrence of a character in `separators' that is not IFS white space, along with any adjacent occurrences of IFS white space delimits a field. 3) Any nonzero-length sequence of IFS white space delimits a field. */ /* BEWARE! list_string strips null arguments. Don't call it twice and expect to have "" preserved! */ /* This performs word splitting and quoted null character removal on STRING. */ #define issep(c) (member ((c), separators)) WORD_LIST * list_string (string, separators, quoted) register char *string, *separators; int quoted; { WORD_LIST *result; WORD_DESC *t; char *current_word, *s; int sindex, sh_style_split, whitesep; if (!string || !*string) return ((WORD_LIST *)NULL); sh_style_split = separators && *separators && (STREQ (separators, " \t\n")); /* Remove sequences of whitespace at the beginning of STRING, as long as those characters appear in IFS. Do not do this if STRING is quoted or if there are no separator characters. */ if (!quoted || !separators || !*separators) { for (s = string; *s && spctabnl (*s) && issep (*s); s++); if (!*s) return ((WORD_LIST *)NULL); string = s; } /* OK, now STRING points to a word that does not begin with white space. The splitting algorithm is: extract a word, stopping at a separator skip sequences of spc, tab, or nl as long as they are separators This obeys the field splitting rules in Posix.2. */ for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; ) { current_word = string_extract_verbatim (string, &sindex, separators); if (current_word == 0) break; /* If we have a quoted empty string, add a quoted null argument. We want to preserve the quoted null character iff this is a quoted empty string; otherwise the quoted null characters are removed below. */ if (QUOTED_NULL (current_word)) { t = make_bare_word (""); t->flags |= W_QUOTED; free (t->word); t->word = make_quoted_char ('\0'); result = make_word_list (t, result); } else if (current_word[0] != '\0') { /* If we have something, then add it regardless. However, perform quoted null character removal on the current word. */ remove_quoted_nulls (current_word); result = add_string_to_list (current_word, result); if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) result->word->flags |= W_QUOTED; } /* If we're not doing sequences of separators in the traditional Bourne shell style, then add a quoted null argument. */ else if (!sh_style_split && !spctabnl (string[sindex])) { t = make_bare_word (""); t->flags |= W_QUOTED; free (t->word); t->word = make_quoted_char ('\0'); result = make_word_list (t, result); } free (current_word); /* Note whether or not the separator is IFS whitespace, used later. */ whitesep = string[sindex] && spctabnl (string[sindex]); /* Move past the current separator character. */ if (string[sindex]) sindex++; /* Now skip sequences of space, tab, or newline characters if they are in the list of separators. */ while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex])) sindex++; /* If the first separator was IFS whitespace and the current character is a non-whitespace IFS character, it should be part of the current field delimiter, not a separate delimiter that would result in an empty field. Look at POSIX.2, 3.6.5, (3)(b). */ if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex])) sindex++; } return (REVERSE_LIST (result, WORD_LIST *)); } /* Parse a single word from STRING, using SEPARATORS to separate fields. ENDPTR is set to the first character after the word. This is used by the `read' builtin. XXX - this function is very similar to list_string; they should be combined - XXX */ char * get_word_from_string (stringp, separators, endptr) char **stringp, *separators, **endptr; { register char *s; char *current_word; int sindex, sh_style_split, whitesep; if (!stringp || !*stringp || !**stringp) return ((char *)NULL); s = *stringp; sh_style_split = separators && *separators && (STREQ (separators, " \t\n")); /* Remove sequences of whitespace at the beginning of STRING, as long as those characters appear in IFS. */ if (sh_style_split || !separators || !*separators) { for (; *s && spctabnl (*s) && issep (*s); s++); /* If the string is nothing but whitespace, update it and return. */ if (!*s) { *stringp = s; if (endptr) *endptr = s; return ((char *)NULL); } } /* OK, S points to a word that does not begin with white space. Now extract a word, stopping at a separator, save a pointer to the first character after the word, then skip sequences of spc, tab, or nl as long as they are separators. This obeys the field splitting rules in Posix.2. */ sindex = 0; current_word = string_extract_verbatim (s, &sindex, separators); /* Set ENDPTR to the first character after the end of the word. */ if (endptr) *endptr = s + sindex; /* Note whether or not the separator is IFS whitespace, used later. */ whitesep = s[sindex] && spctabnl (s[sindex]); /* Move past the current separator character. */ if (s[sindex]) sindex++; /* Now skip sequences of space, tab, or newline characters if they are in the list of separators. */ while (s[sindex] && spctabnl (s[sindex]) && issep (s[sindex])) sindex++; /* If the first separator was IFS whitespace and the current character is a non-whitespace IFS character, it should be part of the current field delimiter, not a separate delimiter that would result in an empty field. Look at POSIX.2, 3.6.5, (3)(b). */ if (s[sindex] && whitesep && issep (s[sindex]) && !spctabnl (s[sindex])) sindex++; /* Update STRING to point to the next field. */ *stringp = s + sindex; return (current_word); } /* Remove IFS white space at the end of STRING. Start at the end of the string and walk backwards until the beginning of the string or we find a character that's not IFS white space and not CTLESC. Only let CTLESC escape a white space character if SAW_ESCAPE is non-zero. */ char * strip_trailing_ifs_whitespace (string, separators, saw_escape) char *string, *separators; int saw_escape; { char *s; s = string + STRLEN (string) - 1; while (s > string && ((spctabnl (*s) && issep (*s)) || (saw_escape && *s == CTLESC && spctabnl (s[1])))) s--; *++s = '\0'; return string; } #if 0 /* UNUSED */ /* Split STRING into words at whitespace. Obeys shell-style quoting with backslashes, single and double quotes. */ WORD_LIST * list_string_with_quotes (string) char *string; { WORD_LIST *list; char *token, *s; int c, i, tokstart, len; for (s = string; s && *s && spctabnl (*s); s++) ; if (s == 0 || *s == 0) return ((WORD_LIST *)NULL); tokstart = i = 0; list = (WORD_LIST *)NULL; while (1) { c = s[i]; if (c == '\\') { i++; if (s[i]) i++; } else if (c == '\'') i = skip_single_quoted (s, ++i); else if (c == '"') i = skip_double_quoted (s, ++i); else if (c == 0 || spctabnl (c)) { /* We have found the end of a token. Make a word out of it and add it to the word list. */ token = substring (s, tokstart, i); list = add_string_to_list (token, list); free (token); while (spctabnl (s[i])) i++; if (s[i]) tokstart = i; else break; } else i++; /* normal character */ } return (REVERSE_LIST (list, WORD_LIST *)); } #endif /********************************************************/ /* */ /* Functions to perform assignment statements */ /* */ /********************************************************/ /* Given STRING, an assignment string, get the value of the right side of the `=', and bind it to the left side. If EXPAND is true, then perform parameter expansion, command substitution, and arithmetic expansion on the right-hand side. Perform tilde expansion in any case. Do not perform word splitting on the result of expansion. */ static int do_assignment_internal (string, expand) const char *string; int expand; { int offset; char *name, *value; SHELL_VAR *entry; #if defined (ARRAY_VARS) char *t; int ni, assign_list = 0; #endif offset = assignment (string); name = savestring (string); value = (char *)NULL; if (name[offset] == '=') { char *temp; name[offset] = 0; temp = name + offset + 1; #if defined (ARRAY_VARS) if (expand && temp[0] == LPAREN && strchr (temp, RPAREN)) { assign_list = ni = 1; value = extract_delimited_string (temp, &ni, "(", (char *)NULL, ")"); } else #endif /* Perform tilde expansion. */ if (expand && temp[0]) { temp = (strchr (temp, '~') && unquoted_member ('~', temp)) ? bash_tilde_expand (temp) : savestring (temp); value = expand_string_if_necessary (temp, 0, expand_string_unsplit); free (temp); } else value = savestring (temp); } if (value == 0) { value = (char *)xmalloc (1); value[0] = '\0'; } 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); } #define ASSIGN_RETURN(r) do { FREE (value); free (name); return (r); } while (0) #if defined (ARRAY_VARS) if (t = strchr (name, '[')) /*]*/ { if (assign_list) { report_error ("%s: cannot assign list to array member", name); ASSIGN_RETURN (0); } entry = assign_array_element (name, value); if (entry == 0) ASSIGN_RETURN (0); } else if (assign_list) entry = assign_array_from_string (name, value); else #endif /* ARRAY_VARS */ entry = bind_variable (name, value); stupidly_hack_special_variables (name); if (entry) VUNSETATTR (entry, att_invisible); /* Return 1 if the assignment seems to have been performed correctly. */ ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0); } /* Perform the assignment statement in STRING, and expand the right side by doing command and parameter expansion. */ int do_assignment (string) const char *string; { return do_assignment_internal (string, 1); } /* Given STRING, an assignment string, get the value of the right side of the `=', and bind it to the left side. Do not do command and parameter substitution on the right hand side. */ int do_assignment_no_expand (string) const char *string; { return do_assignment_internal (string, 0); } /*************************************************** * * * Functions to manage the positional parameters * * * ***************************************************/ /* Return the word list that corresponds to `$*'. */ WORD_LIST * list_rest_of_args () { register WORD_LIST *list, *args; int i; /* Break out of the loop as soon as one of the dollar variables is null. */ for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++) list = make_word_list (make_bare_word (dollar_vars[i]), list); for (args = rest_of_args; args; args = args->next) list = make_word_list (make_bare_word (args->word->word), list); return (REVERSE_LIST (list, WORD_LIST *)); } int number_of_args () { register WORD_LIST *list; int n; for (n = 0; n < 9 && dollar_vars[n+1]; n++) ; for (list = rest_of_args; list; list = list->next) n++; return n; } /* Return the value of a positional parameter. This handles values > 10. */ char * get_dollar_var_value (ind) long ind; { char *temp; WORD_LIST *p; if (ind < 10) temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL; else /* We want something like ${11} */ { ind -= 10; for (p = rest_of_args; p && ind--; p = p->next) ; temp = p ? savestring (p->word->word) : (char *)NULL; } return (temp); } /* Make a single large string out of the dollar digit variables, and the rest_of_args. If DOLLAR_STAR is 1, then obey the special case of "$*" with respect to IFS. */ char * string_rest_of_args (dollar_star) int dollar_star; { register WORD_LIST *list; char *string; list = list_rest_of_args (); string = dollar_star ? string_list_dollar_star (list) : string_list (list); dispose_words (list); return (string); } /* Return a string containing the positional parameters from START to END, inclusive. If STRING[0] == '*', we obey the rules for $*, which only makes a difference if QUOTED is non-zero. */ static char * pos_params (string, start, end, quoted) char *string; int start, end, quoted; { WORD_LIST *save, *params, *h, *t; char *ret; int i; /* see if we can short-circuit. if start == end, we want 0 parameters. */ if (start == end) return ((char *)NULL); save = params = list_rest_of_args (); if (save == 0) return ((char *)NULL); for (i = 1; params && i < start; i++) params = params->next; if (params == 0) return ((char *)NULL); for (h = t = params; params && i < end; i++) { t = params; params = params->next; } t->next = (WORD_LIST *)NULL; if (string[0] == '*') ret = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (h) : string_list (h); else ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (h) : h); if (t != params) t->next = params; dispose_words (save); return (ret); } /******************************************************************/ /* */ /* Functions to expand strings to strings or WORD_LISTs */ /* */ /******************************************************************/ #if defined (PROCESS_SUBSTITUTION) #define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC) #else #define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC) #endif /* If there are any characters in STRING that require full expansion, then call FUNC to expand STRING; otherwise just perform quote removal if necessary. This returns a new string. */ static char * expand_string_if_necessary (string, quoted, func) char *string; int quoted; EXPFUNC *func; { WORD_LIST *list; int i, saw_quote; char *ret; for (i = saw_quote = 0; string[i]; i++) { if (EXP_CHAR (string[i])) break; else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"') saw_quote = 1; } if (string[i]) { list = (*func) (string, quoted); if (list) { ret = string_list (list); dispose_words (list); } else ret = (char *)NULL; } else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0)) ret = string_quote_removal (string, quoted); else ret = savestring (string); return ret; } static inline char * expand_string_to_string_internal (string, quoted, func) char *string; int quoted; EXPFUNC *func; { WORD_LIST *list; char *ret; if (string == 0 || *string == '\0') return ((char *)NULL); list = (*func) (string, quoted); if (list) { ret = string_list (list); dispose_words (list); } else ret = (char *)NULL; return (ret); } char * expand_string_to_string (string, quoted) char *string; int quoted; { return (expand_string_to_string_internal (string, quoted, expand_string)); } char * expand_string_unsplit_to_string (string, quoted) char *string; int quoted; { return (expand_string_to_string_internal (string, quoted, expand_string_unsplit)); } #if defined (COND_COMMAND) /* Just remove backslashes in STRING. Returns a new string. */ char * remove_backslashes (string) char *string; { char *r, *ret, *s; r = ret = (char *)xmalloc (strlen (string) + 1); for (s = string; s && *s; ) { if (*s == '\\') s++; if (*s == 0) break; *r++ = *s++; } *r = '\0'; return ret; } /* This needs better error handling. */ /* Expand W for use as an argument to a unary or binary operator in a [[...]] expression. If SPECIAL is nonzero, this is the rhs argument to the != or == operator, and should be treated as a pattern. In this case, we quote the string specially for the globbing code. The caller is responsible for removing the backslashes if the unquoted words is needed later. */ char * cond_expand_word (w, special) WORD_DESC *w; int special; { char *r, *p; WORD_LIST *l; if (w->word == 0 || w->word[0] == '\0') return ((char *)NULL); if (strchr (w->word, '~') && unquoted_member ('~', w->word)) { p = bash_tilde_expand (w->word); free (w->word); w->word = p; } l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0); if (l) { if (special == 0) { dequote_list (l); r = string_list (l); } else { p = string_list (l); r = quote_string_for_globbing (p, QGLOB_CVTNULL); free (p); } dispose_words (l); } else r = (char *)NULL; return r; } #endif /* Call expand_word_internal to expand W and handle error returns. A convenience function for functions that don't want to handle any errors or free any memory before aborting. */ static WORD_LIST * call_expand_word_internal (w, q, i, c, e) WORD_DESC *w; int q, i, *c, *e; { WORD_LIST *result; result = expand_word_internal (w, q, i, c, e); if (result == &expand_word_error || result == &expand_word_fatal) { expand_no_split_dollar_star = 0; /* XXX */ /* By convention, each time this error is returned, w->word has already been freed (it sometimes may not be in the fatal case, but that doesn't result in a memory leak because we're going to exit in most cases). */ w->word = (char *)NULL; last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF); /* NOTREACHED */ } else return (result); } /* Perform parameter expansion, command substitution, and arithmetic expansion on STRING, as if it were a word. Leave the result quoted. */ static WORD_LIST * expand_string_internal (string, quoted) char *string; int quoted; { WORD_DESC td; WORD_LIST *tresult; if (string == 0 || *string == 0) return ((WORD_LIST *)NULL); td.flags = 0; td.word = savestring (string); tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); FREE (td.word); return (tresult); } /* Expand STRING by performing parameter expansion, command substitution, and arithmetic expansion. Dequote the resulting WORD_LIST before returning it, but do not perform word splitting. The call to remove_quoted_nulls () is in here because word splitting normally takes care of quote removal. */ WORD_LIST * expand_string_unsplit (string, quoted) char *string; int quoted; { WORD_LIST *value; if (string == 0 || *string == '\0') return ((WORD_LIST *)NULL); expand_no_split_dollar_star = 1; value = expand_string_internal (string, quoted); expand_no_split_dollar_star = 0; if (value) { if (value->word) remove_quoted_nulls (value->word->word); dequote_list (value); } return (value); } /* Expand one of the PS? prompt strings. This is a sort of combination of expand_string_unsplit and expand_string_internal, but returns the passed string when an error occurs. Might want to trap other calls to jump_to_top_level here so we don't endlessly loop. */ WORD_LIST * expand_prompt_string (string, quoted) char *string; int quoted; { WORD_LIST *value; WORD_DESC td; if (string == 0 || *string == 0) return ((WORD_LIST *)NULL); td.flags = 0; td.word = savestring (string); no_longjmp_on_fatal_error = 1; value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL); no_longjmp_on_fatal_error = 0; if (value == &expand_word_error || value == &expand_word_fatal) { value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL); return value; } FREE (td.word); if (value) { if (value->word) remove_quoted_nulls (value->word->word); dequote_list (value); } return (value); } /* Expand STRING just as if you were expanding a word, but do not dequote the resultant WORD_LIST. This is called only from within this file, and is used to correctly preserve quoted characters when expanding things like ${1+"$@"}. This does parameter expansion, command substitution, arithmetic expansion, and word splitting. */ static WORD_LIST * expand_string_leave_quoted (string, quoted) char *string; int quoted; { WORD_LIST *tlist; WORD_LIST *tresult; if (string == 0 || *string == '\0') return ((WORD_LIST *)NULL); tlist = expand_string_internal (string, quoted); if (tlist) { tresult = word_list_split (tlist); dispose_words (tlist); return (tresult); } return ((WORD_LIST *)NULL); } /* This does not perform word splitting or dequote the WORD_LIST it returns. */ static WORD_LIST * expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at) char *string; int quoted, *dollar_at_p, *has_dollar_at; { WORD_DESC td; WORD_LIST *tresult; if (string == 0 || *string == '\0') return (WORD_LIST *)NULL; td.flags = 0; td.word = string; tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at); return (tresult); } /* Expand STRING just as if you were expanding a word. This also returns a list of words. Note that filename globbing is *NOT* done for word or string expansion, just when the shell is expanding a command. This does parameter expansion, command substitution, arithmetic expansion, and word splitting. Dequote the resultant WORD_LIST before returning. */ WORD_LIST * expand_string (string, quoted) char *string; int quoted; { WORD_LIST *result; if (string == 0 || *string == '\0') return ((WORD_LIST *)NULL); result = expand_string_leave_quoted (string, quoted); return (result ? dequote_list (result) : result); } /*************************************************** * * * Functions to handle quoting chars * * * ***************************************************/ /* Conventions: A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string. The parser passes CTLNUL as CTLESC CTLNUL. */ /* The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL. This is necessary to make unquoted CTLESC and CTLNUL characters in the data stream pass through properly. Here we remove doubled CTLESC characters inside quoted strings before quoting the entire string, so we do not double the number of CTLESC characters. */ static char * remove_quoted_escapes (string) char *string; { register char *s; int docopy; char *t, *t1; if (string == NULL) return (string); t1 = t = (char *)xmalloc (strlen (string) + 1); for (docopy = 0, s = string; *s; s++, t1++) { if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL)) { s++; docopy = 1; } *t1 = *s; } *t1 = '\0'; if (docopy) strcpy (string, t); free (t); return (string); } /* Quote escape characters in string s, but no other characters. This is used to protect CTLESC and CTLNUL in variable values from the rest of the word expansion process after the variable is expanded. */ char * quote_escapes (string) char *string; { register char *s, *t; char *result; result = (char *)xmalloc ((strlen (string) * 2) + 1); for (s = string, t = result; *s; ) { if (*s == CTLESC || *s == CTLNUL) *t++ = CTLESC; *t++ = *s++; } *t = '\0'; return (result); } static WORD_LIST * list_quote_escapes (list) WORD_LIST *list; { register WORD_LIST *w; char *t; for (w = list; w; w = w->next) { t = w->word->word; w->word->word = quote_escapes (t); free (t); } return list; } #if 0 /* UNUSED */ static char * dequote_escapes (string) char *string; { register char *s, *t; char *result; result = (char *)xmalloc (strlen (string) + 1); for (s = string, t = result; *s; ) { if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL)) { s++; if (*s == '\0') break; } *t++ = *s++; } *t = '\0'; return result; } #endif static WORD_LIST * dequote_list (list) WORD_LIST *list; { register char *s; register WORD_LIST *tlist; for (tlist = list; tlist; tlist = tlist->next) { s = dequote_string (tlist->word->word); free (tlist->word->word); tlist->word->word = s; } return list; } /* Return a new string with the quoted representation of character C. */ static char * make_quoted_char (c) int c; { char *temp; temp = (char *)xmalloc (3); if (c == 0) { temp[0] = CTLNUL; temp[1] = '\0'; } else { temp[0] = CTLESC; temp[1] = c; temp[2] = '\0'; } return (temp); } /* Quote STRING. Return a new string. */ char * quote_string (string) char *string; { register char *t; char *result; if (*string == 0) { result = (char *)xmalloc (2); result[0] = CTLNUL; result[1] = '\0'; } else { result = (char *)xmalloc ((strlen (string) * 2) + 1); for (t = result; *string; ) { *t++ = CTLESC; *t++ = *string++; } *t = '\0'; } return (result); } /* De-quoted quoted characters in STRING. */ char * dequote_string (string) char *string; { register char *t; char *result; result = (char *)xmalloc (strlen (string) + 1); if (QUOTED_NULL (string)) { result[0] = '\0'; return (result); } /* If no character in the string can be quoted, don't bother examining each character. Just return a copy of the string passed to us. */ if (strchr (string, CTLESC) == NULL) /* XXX */ { /* XXX */ strcpy (result, string); /* XXX */ return (result); /* XXX */ } for (t = result; *string; string++, t++) { if (*string == CTLESC) { string++; if (!*string) break; } *t = *string; } *t = '\0'; return (result); } /* Quote the entire WORD_LIST list. */ static WORD_LIST * quote_list (list) WORD_LIST *list; { register WORD_LIST *w; char *t; for (w = list; w; w = w->next) { t = w->word->word; w->word->word = quote_string (t); free (t); w->word->flags |= W_QUOTED; } return list; } /* Perform quoted null character removal on STRING. We don't allow any quoted null characters in the middle or at the ends of strings because of how expand_word_internal works. remove_quoted_nulls () turns STRING into an empty string iff it only consists of a quoted null, and removes all unquoted CTLNUL characters. */ /* #define remove_quoted_nulls(string) \ do { if (QUOTED_NULL (string)) string[0] ='\0'; } while (0) */ static void remove_quoted_nulls (string) char *string; { char *nstr, *s, *p; nstr = savestring (string); nstr[0] = '\0'; for (p = nstr, s = string; *s; s++) { if (*s == CTLESC) { *p++ = *s++; /* CTLESC */ if (*s == 0) break; *p++ = *s; /* quoted char */ continue; } if (*s == CTLNUL) continue; *p++ = *s; } *p = '\0'; strcpy (string, nstr); free (nstr); } /* Perform quoted null character removal on each element of LIST. This modifies LIST. */ void word_list_remove_quoted_nulls (list) WORD_LIST *list; { register WORD_LIST *t; for (t = list; t; t = t->next) remove_quoted_nulls (t->word->word); } /* **************************************************************** */ /* */ /* Functions for Matching and Removing Patterns */ /* */ /* **************************************************************** */ /* Remove the portion of PARAM matched by PATTERN according to OP, where OP can have one of 4 values: RP_LONG_LEFT remove longest matching portion at start of PARAM RP_SHORT_LEFT remove shortest matching portion at start of PARAM RP_LONG_RIGHT remove longest matching portion at end of PARAM RP_SHORT_RIGHT remove shortest matching portion at end of PARAM */ #define RP_LONG_LEFT 1 #define RP_SHORT_LEFT 2 #define RP_LONG_RIGHT 3 #define RP_SHORT_RIGHT 4 static char * remove_pattern (param, pattern, op) char *param, *pattern; int op; { register int len; register char *end; register char *p, *ret, c; if (param == NULL || *param == '\0') return (param); if (pattern == NULL || *pattern == '\0') /* minor optimization */ return (savestring (param)); len = STRLEN (param); end = param + len; switch (op) { case RP_LONG_LEFT: /* remove longest match at start */ for (p = end; p >= param; p--) { c = *p; *p = '\0'; if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) { *p = c; return (savestring (p)); } *p = c; } break; case RP_SHORT_LEFT: /* remove shortest match at start */ for (p = param; p <= end; p++) { c = *p; *p = '\0'; if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH) { *p = c; return (savestring (p)); } *p = c; } break; case RP_LONG_RIGHT: /* remove longest match at end */ for (p = param; p <= end; p++) { if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) { c = *p; *p = '\0'; ret = savestring (param); *p = c; return (ret); } } break; case RP_SHORT_RIGHT: /* remove shortest match at end */ for (p = end; p >= param; p--) { if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH) { c = *p; *p = '\0'; ret = savestring (param); *p = c; return (ret); } } break; } return (savestring (param)); /* no match, return original string */ } /* Return 1 of the first character of STRING could match the first character of pattern PAT. Used to avoid n2 calls to strmatch(). */ static int match_pattern_char (pat, string) char *pat, *string; { char c; if (*string == 0) return (0); switch (c = *pat++) { default: return (*string == c); case '\\': return (*string == *pat); case '?': return (*pat == LPAREN ? 1 : (*string != '\0')); case '*': return (1); case '+': case '!': case '@': return (*pat == LPAREN ? 1 : (*string == c)); case '[': return (*string != '\0'); } } /* Match PAT anywhere in STRING and return the match boundaries. This returns 1 in case of a successful match, 0 otherwise. SP and EP are pointers into the string where the match begins and ends, respectively. MTYPE controls what kind of match is attempted. MATCH_BEG and MATCH_END anchor the match at the beginning and end of the string, respectively. The longest match is returned. */ static int match_pattern (string, pat, mtype, sp, ep) char *string, *pat; int mtype; char **sp, **ep; { int c; register char *p, *p1; char *end; if (string == 0 || *string == 0 || pat == 0 || *pat == 0) return (0); end = string + STRLEN (string); switch (mtype) { case MATCH_ANY: for (p = string; p <= end; p++) { if (match_pattern_char (pat, p)) { for (p1 = end; p1 >= p; p1--) { c = *p1; *p1 = '\0'; if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) { *p1 = c; *sp = p; *ep = p1; return 1; } *p1 = c; } } } return (0); case MATCH_BEG: if (match_pattern_char (pat, string) == 0) return (0); for (p = end; p >= string; p--) { c = *p; *p = '\0'; if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0) { *p = c; *sp = string; *ep = p; return 1; } *p = c; } return (0); case MATCH_END: for (p = string; p <= end; p++) if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0) { *sp = p; *ep = end; return 1; } return (0); } return (0); } static int getpatspec (c, value) int c; char *value; { if (c == '#') return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT); else /* c == '%' */ return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT); } /* Posix.2 says that the WORD should be run through tilde expansion, parameter expansion, command substitution and arithmetic expansion. This leaves the result quoted, so quote_string_for_globbing () has to be called to fix it up for strmatch (). If QUOTED is non-zero, it means that the entire expression was enclosed in double quotes. This means that quoting characters in the pattern do not make any special pattern characters quoted. For example, the `*' in the following retains its special meaning: "${foo#'*'}". */ static char * getpattern (value, quoted, expandpat) char *value; int quoted, expandpat; { char *pat, *tword; WORD_LIST *l; int i; tword = strchr (value, '~') ? bash_tilde_expand (value) : savestring (value); /* expand_string_internal () leaves WORD quoted and does not perform word splitting. */ if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword) { i = 0; pat = string_extract_double_quoted (tword, &i, 1); free (tword); tword = pat; } /* There is a problem here: how to handle single or double quotes in the pattern string when the whole expression is between double quotes? */ #if 0 l = *tword ? expand_string_for_rhs (tword, quoted, (int *)NULL, (int *)NULL) #else l = *tword ? expand_string_for_rhs (tword, (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_NOQUOTE : quoted, (int *)NULL, (int *)NULL) #endif : (WORD_LIST *)0; free (tword); pat = string_list (l); dispose_words (l); if (pat) { tword = quote_string_for_globbing (pat, QGLOB_CVTNULL); free (pat); pat = tword; } return (pat); } /* Handle removing a pattern from a string as a result of ${name%[%]value} or ${name#[#]value}. */ static char * parameter_brace_remove_pattern (value, temp, c, quoted) char *value, *temp; int c, quoted; { int patspec; char *pattern, *tword; patspec = getpatspec (c, value); if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT) value++; pattern = getpattern (value, quoted, 1); tword = remove_pattern (temp, pattern, patspec); FREE (pattern); return (tword); } static char * list_remove_pattern (list, pattern, patspec, type, quoted) WORD_LIST *list; char *pattern; int patspec, type, quoted; { WORD_LIST *new, *l; WORD_DESC *w; char *tword; for (new = (WORD_LIST *)NULL, l = list; l; l = l->next) { tword = remove_pattern (l->word->word, pattern, patspec); w = make_bare_word (tword); free (tword); new = make_word_list (w, new); } l = REVERSE_LIST (new, WORD_LIST *); if (type == '*') tword = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? string_list_dollar_star (l) : string_list (l); else tword = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (l) : l); dispose_words (l); return (tword); } static char * parameter_list_remove_pattern (value, type, c, quoted) char *value; int type, c, quoted; { int patspec; char *pattern, *ret; WORD_LIST *list; patspec = getpatspec (c, value); if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT) value++; pattern = getpattern (value, quoted, 1); list = list_rest_of_args (); ret = list_remove_pattern (list, pattern, patspec, type, quoted); dispose_words (list); FREE (pattern); return (ret); } #if defined (ARRAY_VARS) static char * array_remove_pattern (value, aspec, aval, c, quoted) char *value, *aspec, *aval; /* AVAL == evaluated ASPEC */ int c, quoted; { SHELL_VAR *var; int len, patspec; char *ret, *t, *pattern; WORD_LIST *l; var = array_variable_part (aspec, &t, &len); if (var == 0) return ((char *)NULL); patspec = getpatspec (c, value); if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT) value++; pattern = getpattern (value, quoted, 1); if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') { if (array_p (var) == 0) { report_error ("%s: bad array subscript", aspec); FREE (pattern); return ((char *)NULL); } l = array_to_word_list (array_cell (var)); if (l == 0) return ((char *)NULL); ret = list_remove_pattern (l, pattern, patspec, t[0], quoted); dispose_words (l); } else { ret = remove_pattern (aval, pattern, patspec); if (ret) { t = quote_escapes (ret); free (ret); ret = t; } } FREE (pattern); return ret; } #endif /* ARRAY_VARS */ /******************************************* * * * Functions to expand WORD_DESCs * * * *******************************************/ /* Expand WORD, performing word splitting on the result. This does parameter expansion, command substitution, arithmetic expansion, word splitting, and quote removal. */ WORD_LIST * expand_word (word, quoted) WORD_DESC *word; int quoted; { WORD_LIST *result, *tresult; tresult = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); result = word_list_split (tresult); dispose_words (tresult); return (result ? dequote_list (result) : result); } /* Expand WORD, but do not perform word splitting on the result. This does parameter expansion, command substitution, arithmetic expansion, and quote removal. */ WORD_LIST * expand_word_unsplit (word, quoted) WORD_DESC *word; int quoted; { WORD_LIST *result; expand_no_split_dollar_star = 1; result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL); expand_no_split_dollar_star = 0; return (result ? dequote_list (result) : result); } /* Perform shell expansions on WORD, but do not perform word splitting or quote removal on the result. */ WORD_LIST * expand_word_leave_quoted (word, quoted) WORD_DESC *word; int quoted; { return (call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL)); } #if defined (PROCESS_SUBSTITUTION) /*****************************************************************/ /* */ /* Hacking Process Substitution */ /* */ /*****************************************************************/ #if !defined (HAVE_DEV_FD) /* Named pipes must be removed explicitly with `unlink'. This keeps a list of FIFOs the shell has open. unlink_fifo_list will walk the list and unlink all of them. add_fifo_list adds the name of an open FIFO to the list. NFIFO is a count of the number of FIFOs in the list. */ #define FIFO_INCR 20 struct temp_fifo { char *file; pid_t proc; }; static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL; static int nfifo; static int fifo_list_size; static void add_fifo_list (pathname) char *pathname; { if (nfifo >= fifo_list_size - 1) { fifo_list_size += FIFO_INCR; fifo_list = (struct temp_fifo *)xrealloc (fifo_list, fifo_list_size * sizeof (struct temp_fifo)); } fifo_list[nfifo].file = savestring (pathname); nfifo++; } void unlink_fifo_list () { int saved, i, j; if (nfifo == 0) return; for (i = saved = 0; i < nfifo; i++) { if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1)) { unlink (fifo_list[i].file); free (fifo_list[i].file); fifo_list[i].file = (char *)NULL; fifo_list[i].proc = -1; } else saved++; } /* If we didn't remove some of the FIFOs, compact the list. */ if (saved) { for (i = j = 0; i < nfifo; i++) if (fifo_list[i].file) { fifo_list[j].file = fifo_list[i].file; fifo_list[j].proc = fifo_list[i].proc; j++; } nfifo = j; } else nfifo = 0; } static char * make_named_pipe () { char *tname; tname = sh_mktmpname ("sh-np", MT_USERANDOM); if (mkfifo (tname, 0600) < 0) { free (tname); return ((char *)NULL); } add_fifo_list (tname); return (tname); } #else /* HAVE_DEV_FD */ /* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell has open to children. NFDS is a count of the number of bits currently set in DEV_FD_LIST. TOTFDS is a count of the highest possible number of open files. */ static char *dev_fd_list = (char *)NULL; static int nfds; static int totfds; /* The highest possible number of open files. */ static void add_fifo_list (fd) int fd; { if (!dev_fd_list || fd >= totfds) { int ofds; ofds = totfds; totfds = getdtablesize (); if (totfds < 0 || totfds > 256) totfds = 256; if (fd > totfds) totfds = fd + 2; dev_fd_list = (char *)xrealloc (dev_fd_list, totfds); bzero (dev_fd_list + ofds, totfds - ofds); } dev_fd_list[fd] = 1; nfds++; } void unlink_fifo_list () { register int i; if (nfds == 0) return; for (i = 0; nfds && i < totfds; i++) if (dev_fd_list[i]) { close (i); dev_fd_list[i] = 0; nfds--; } nfds = 0; } #if defined (NOTDEF) print_dev_fd_list () { register int i; fprintf (stderr, "pid %ld: dev_fd_list:", (long)getpid ()); fflush (stderr); for (i = 0; i < totfds; i++) { if (dev_fd_list[i]) fprintf (stderr, " %d", i); } fprintf (stderr, "\n"); } #endif /* NOTDEF */ static char * make_dev_fd_filename (fd) int fd; { char *ret, intbuf[INT_STRLEN_BOUND (int) + 1], *p; ret = (char *)xmalloc (sizeof (DEV_FD_PREFIX) + 4); strcpy (ret, DEV_FD_PREFIX); p = inttostr (fd, intbuf, sizeof (intbuf)); strcpy (ret + sizeof (DEV_FD_PREFIX) - 1, p); add_fifo_list (fd); return (ret); } #endif /* HAVE_DEV_FD */ /* Return a filename that will open a connection to the process defined by executing STRING. HAVE_DEV_FD, if defined, means open a pipe and return a filename in /dev/fd corresponding to a descriptor that is one of the ends of the pipe. If not defined, we use named pipes on systems that have them. Systems without /dev/fd and named pipes are out of luck. OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or use the read end of the pipe and dup that file descriptor to fd 0 in the child. If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for writing or use the write end of the pipe in the child, and dup that file descriptor to fd 1 in the child. The parent does the opposite. */ static char * process_substitute (string, open_for_read_in_child) char *string; int open_for_read_in_child; { char *pathname; int fd, result; pid_t old_pid, pid; #if defined (HAVE_DEV_FD) int parent_pipe_fd, child_pipe_fd; int fildes[2]; #endif /* HAVE_DEV_FD */ #if defined (JOB_CONTROL) pid_t old_pipeline_pgrp; #endif if (!string || !*string || wordexp_only) return ((char *)NULL); #if !defined (HAVE_DEV_FD) pathname = make_named_pipe (); #else /* HAVE_DEV_FD */ if (pipe (fildes) < 0) { sys_error ("cannot make pipe for process substitution"); return ((char *)NULL); } /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of the pipe in the parent, otherwise the read end. */ parent_pipe_fd = fildes[open_for_read_in_child]; child_pipe_fd = fildes[1 - open_for_read_in_child]; /* Move the parent end of the pipe to some high file descriptor, to avoid clashes with FDs used by the script. */ parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64); pathname = make_dev_fd_filename (parent_pipe_fd); #endif /* HAVE_DEV_FD */ if (!pathname) { sys_error ("cannot make pipe for process substitution"); return ((char *)NULL); } old_pid = last_made_pid; #if defined (JOB_CONTROL) old_pipeline_pgrp = pipeline_pgrp; pipeline_pgrp = shell_pgrp; save_pipeline (1); #endif /* JOB_CONTROL */ pid = make_child ((char *)NULL, 1); if (pid == 0) { reset_terminating_signals (); /* XXX */ /* Cancel traps, in trap.c. */ restore_original_signals (); setup_async_signals (); subshell_environment |= SUBSHELL_COMSUB; } #if defined (JOB_CONTROL) set_sigchld_handler (); stop_making_children (); pipeline_pgrp = old_pipeline_pgrp; #endif /* JOB_CONTROL */ if (pid < 0) { sys_error ("cannot make child for process substitution"); free (pathname); #if defined (HAVE_DEV_FD) close (parent_pipe_fd); close (child_pipe_fd); #endif /* HAVE_DEV_FD */ return ((char *)NULL); } if (pid > 0) { #if defined (JOB_CONTROL) restore_pipeline (1); #endif #if !defined (HAVE_DEV_FD) fifo_list[nfifo-1].proc = pid; #endif last_made_pid = old_pid; #if defined (JOB_CONTROL) && defined (PGRP_PIPE) close_pgrp_pipe (); #endif /* JOB_CONTROL && PGRP_PIPE */ #if defined (HAVE_DEV_FD) close (child_pipe_fd); #endif /* HAVE_DEV_FD */ return (pathname); } set_sigint_handler (); #if defined (JOB_CONTROL) set_job_control (0); #endif /* JOB_CONTROL */ #if !defined (HAVE_DEV_FD) /* Open the named pipe in the child. */ fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY); if (fd < 0) { sys_error ("cannot open named pipe %s for %s", pathname, open_for_read_in_child ? "reading" : "writing"); exit (127); } if (open_for_read_in_child) { if (sh_unset_nodelay_mode (fd) < 0) { sys_error ("cannout reset nodelay mode for fd %d", fd); exit (127); } } #else /* HAVE_DEV_FD */ fd = child_pipe_fd; #endif /* HAVE_DEV_FD */ if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0) { sys_error ("cannot duplicate named pipe %s as fd %d", pathname, open_for_read_in_child ? 0 : 1); exit (127); } if (fd != (open_for_read_in_child ? 0 : 1)) close (fd); /* Need to close any files that this process has open to pipes inherited from its parent. */ if (current_fds_to_close) { close_fd_bitmap (current_fds_to_close); current_fds_to_close = (struct fd_bitmap *)NULL; } #if defined (HAVE_DEV_FD) /* Make sure we close the parent's end of the pipe and clear the slot in the fd list so it is not closed later, if reallocated by, for instance, pipe(2). */ close (parent_pipe_fd); dev_fd_list[parent_pipe_fd] = 0; #endif /* HAVE_DEV_FD */ result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST)); #if !defined (HAVE_DEV_FD) /* Make sure we close the named pipe in the child before we exit. */ close (open_for_read_in_child ? 0 : 1); #endif /* !HAVE_DEV_FD */ exit (result); /*NOTREACHED*/ } #endif /* PROCESS_SUBSTITUTION */ /***********************************/ /* */ /* Command Substitution */ /* */ /***********************************/ static char * read_comsub (fd, quoted) int fd, quoted; { char *istring, buf[128], *bufp; int istring_index, istring_size, c; ssize_t bufn; istring = (char *)NULL; istring_index = istring_size = bufn = 0; #ifdef __CYGWIN__ setmode (fd, O_TEXT); /* we don't want CR/LF, we want Unix-style */ #endif /* Read the output of the command through the pipe. */ while (1) { if (fd < 0) break; if (--bufn <= 0) { bufn = zread (fd, buf, sizeof (buf)); if (bufn <= 0) break; bufp = buf; } c = *bufp++; if (c == 0) { #if 0 internal_warning ("read_comsub: ignored null byte in input"); #endif continue; } /* Add the character to ISTRING, possibly after resizing it. */ RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE); if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || c == CTLESC || c == CTLNUL) istring[istring_index++] = CTLESC; istring[istring_index++] = c; #if 0 #if defined (__CYGWIN__) if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r') { istring_index--; istring[istring_index - 1] = '\n'; } #endif #endif } if (istring) istring[istring_index] = '\0'; /* If we read no output, just return now and save ourselves some trouble. */ if (istring_index == 0) { FREE (istring); return (char *)NULL; } /* Strip trailing newlines from the output of the command. */ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) { while (istring_index > 0) { if (istring[istring_index - 1] == '\n') { --istring_index; /* If the newline was quoted, remove the quoting char. */ if (istring[istring_index - 1] == CTLESC) --istring_index; } else break; } istring[istring_index] = '\0'; } else strip_trailing (istring, istring_index - 1, 1); return istring; } /* Perform command substitution on STRING. This returns a string, possibly quoted. */ char * command_substitute (string, quoted) char *string; int quoted; { pid_t pid, old_pid, old_pipeline_pgrp; char *istring; int result, fildes[2], function_value; istring = (char *)NULL; /* Don't fork () if there is no need to. In the case of no command to run, just return NULL. */ if (!string || !*string || (string[0] == '\n' && !string[1])) return ((char *)NULL); if (wordexp_only && read_but_dont_execute) { last_command_exit_value = 125; jump_to_top_level (EXITPROG); } /* We're making the assumption here that the command substitution will eventually run a command from the file system. Since we'll run maybe_make_export_env in this subshell before executing that command, the parent shell and any other shells it starts will have to remake the environment. If we make it before we fork, other shells won't have to. Don't bother if we have any temporary variable assignments, though, because the export environment will be remade after this command completes anyway, but do it if all the words to be expanded are variable assignments. */ if (subst_assign_varlist == 0 || garglist == 0) maybe_make_export_env (); /* XXX */ /* Pipe the output of executing STRING into the current shell. */ if (pipe (fildes) < 0) { sys_error ("cannot make pipes for command substitution"); goto error_exit; } old_pid = last_made_pid; #if defined (JOB_CONTROL) old_pipeline_pgrp = pipeline_pgrp; /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */ if ((subshell_environment & SUBSHELL_PIPE) == 0) pipeline_pgrp = shell_pgrp; cleanup_the_pipeline (); #endif pid = make_child ((char *)NULL, 0); if (pid == 0) /* Reset the signal handlers in the child, but don't free the trap strings. */ reset_signal_handlers (); #if defined (JOB_CONTROL) set_sigchld_handler (); stop_making_children (); pipeline_pgrp = old_pipeline_pgrp; #else stop_making_children (); #endif /* JOB_CONTROL */ if (pid < 0) { sys_error ("cannot make child for command substitution"); error_exit: FREE (istring); close (fildes[0]); close (fildes[1]); return ((char *)NULL); } if (pid == 0) { set_sigint_handler (); /* XXX */ if (dup2 (fildes[1], 1) < 0) { sys_error ("command_substitute: cannot duplicate pipe as fd 1"); exit (EXECUTION_FAILURE); } /* If standard output is closed in the parent shell (such as after `exec >&-'), file descriptor 1 will be the lowest available file descriptor, and end up in fildes[0]. This can happen for stdin and stderr as well, but stdout is more important -- it will cause no output to be generated from this command. */ if ((fildes[1] != fileno (stdin)) && (fildes[1] != fileno (stdout)) && (fildes[1] != fileno (stderr))) close (fildes[1]); if ((fildes[0] != fileno (stdin)) && (fildes[0] != fileno (stdout)) && (fildes[0] != fileno (stderr))) close (fildes[0]); /* The currently executing shell is not interactive. */ interactive = 0; /* This is a subshell environment. */ subshell_environment |= SUBSHELL_COMSUB; /* When not in POSIX mode, command substitution does not inherit the -e flag. */ if (posixly_correct == 0) exit_immediately_on_error = 0; remove_quoted_escapes (string); startup_state = 2; /* see if we can avoid a fork */ /* Give command substitution a place to jump back to on failure, so we don't go back up to main (). */ result = setjmp (top_level); /* If we're running a command substitution inside a shell function, trap `return' so we don't return from the function in the subshell and go off to never-never land. */ if (result == 0 && return_catch_flag) function_value = setjmp (return_catch); else function_value = 0; if (result == EXITPROG) exit (last_command_exit_value); else if (result) exit (EXECUTION_FAILURE); else if (function_value) exit (return_catch_value); else exit (parse_and_execute (string, "command substitution", SEVAL_NOHIST)); } else { #if defined (JOB_CONTROL) && defined (PGRP_PIPE) close_pgrp_pipe (); #endif /* JOB_CONTROL && PGRP_PIPE */ close (fildes[1]); istring = read_comsub (fildes[0], quoted); close (fildes[0]); current_command_subst_pid = pid; last_command_exit_value = wait_for (pid); last_command_subst_pid = pid; last_made_pid = old_pid; #if defined (JOB_CONTROL) /* If last_command_exit_value > 128, then the substituted command was terminated by a signal. If that signal was SIGINT, then send SIGINT to ourselves. This will break out of loops, for instance. */ if (last_command_exit_value == (128 + SIGINT)) kill (getpid (), SIGINT); /* wait_for gives the terminal back to shell_pgrp. If some other process group should have it, give it away to that group here. pipeline_pgrp is non-zero only while we are constructing a pipline, so what we are concerned about is whether or not that pipeline was started in the background. A pipeline started in the background should never get the tty back here. */ #if 0 if (interactive && pipeline_pgrp != (pid_t)0 && pipeline_pgrp != last_asynchronous_pid) #else if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0) #endif give_terminal_to (pipeline_pgrp, 0); #endif /* JOB_CONTROL */ return (istring); } } /******************************************************** * * * Utility functions for parameter expansion * * * ********************************************************/ #if defined (ARRAY_VARS) static arrayind_t array_length_reference (s) char *s; { int len; arrayind_t ind; char *t, c; ARRAY *array; SHELL_VAR *var; var = array_variable_part (s, &t, &len); /* If unbound variables should generate an error, report one and return failure. */ if ((var == 0 || array_p (var) == 0) && unbound_vars_is_error) { c = *--t; *t = '\0'; report_error ("%s: unbound variable", s); *t = c; return (-1); } else if (var == 0) return 0; /* We support a couple of expansions for variables that are not arrays. We'll return the length of the value for v[0], and 1 for v[@] or v[*]. Return 0 for everything else. */ array = array_p (var) ? array_cell (var) : (ARRAY *)NULL; if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') return (array_p (var) ? array_num_elements (array) : 1); ind = array_expand_index (t, len); if (ind < 0) { report_error ("%s: bad array subscript", t); return (-1); } if (array_p (var)) t = array_reference (array, ind); else t = (ind == 0) ? value_cell (var) : (char *)NULL; len = STRLEN (t); return (len); } #endif /* ARRAY_VARS */ static int valid_brace_expansion_word (name, var_is_special) char *name; int var_is_special; { if (DIGIT (*name) && all_digits (name)) return 1; else if (var_is_special) return 1; #if defined (ARRAY_VARS) else if (valid_array_reference (name)) return 1; #endif /* ARRAY_VARS */ else if (legal_identifier (name)) return 1; else return 0; } /* Parameter expand NAME, and return a new string which is the expansion, or NULL if there was no expansion. VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in the shell, e.g., "@", "$", "*", etc. QUOTED, if non-zero, means that NAME was found inside of a double-quoted expression. */ static char * parameter_brace_expand_word (name, var_is_special, quoted) char *name; int var_is_special, quoted; { char *temp, *tt; long arg_index; SHELL_VAR *var; #if 0 WORD_LIST *l; #endif /* Handle multiple digit arguments, as in ${11}. */ if (legal_number (name, &arg_index)) temp = get_dollar_var_value (arg_index); else if (var_is_special) /* ${@} */ { int sindex; tt = (char *)xmalloc (2 + strlen (name)); tt[sindex = 0] = '$'; strcpy (tt + 1, name); #if 0 l = expand_string_leave_quoted (tt, quoted); free (tt); temp = string_list (l); dispose_words (l); #else temp = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL, (int *)NULL, (int *)NULL, 0); free (tt); #endif } #if defined (ARRAY_VARS) else if (valid_array_reference (name)) { temp = array_value (name, quoted); } #endif else if (var = find_variable (name)) { if (var && invisible_p (var) == 0) { #if defined (ARRAY_VARS) temp = array_p (var) ? array_reference (array_cell (var), 0) : value_cell (var); #else temp = value_cell (var); #endif if (temp) temp = quote_escapes (temp); if (tempvar_p (var)) dispose_variable (var); } else temp = (char *)NULL; } else temp = (char *)NULL; return (temp); } /* Expand an indirect reference to a variable: ${!NAME} expands to the value of the variable whose name is the value of NAME. */ static char * parameter_brace_expand_indir (name, var_is_special, quoted) char *name; int var_is_special, quoted; { char *temp, *t; t = parameter_brace_expand_word (name, var_is_special, quoted); if (t == 0) return (t); #if 0 temp = parameter_brace_expand_word (t, t[0] == '@' && t[1] == '\0', quoted); #else temp = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted); #endif free (t); return temp; } /* Expand the right side of a parameter expansion of the form ${NAMEcVALUE}, depending on the value of C, the separating character. C can be one of "-", "+", or "=". QUOTED is true if the entire brace expression occurs between double quotes. */ static char * parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat) char *name, *value; int c, quoted, *qdollaratp, *hasdollarat; { WORD_LIST *l; char *t, *t1, *temp; int hasdol; temp = (*value == '~' || (strchr (value, '~') && unquoted_substring ("=~", value))) ? bash_tilde_expand (value) : savestring (value); /* If the entire expression is between double quotes, we want to treat the value as a double-quoted string, with the exception that we strip embedded unescaped double quotes. */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *temp) { hasdol = 0; t = string_extract_double_quoted (temp, &hasdol, 1); free (temp); temp = t; } hasdol = 0; /* XXX was 0 not quoted */ l = *temp ? expand_string_for_rhs (temp, quoted, &hasdol, (int *)NULL) : (WORD_LIST *)0; if (hasdollarat) *hasdollarat = hasdol || (l && l->next); free (temp); if (l) { /* The expansion of TEMP returned something. We need to treat things slightly differently if HASDOL is non-zero. */ temp = string_list (l); /* If l->next is not null, we know that TEMP contained "$@", since that is the only expansion that creates more than one word. */ if ((hasdol && quoted) || l->next) *qdollaratp = 1; dispose_words (l); } else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol) { /* The brace expansion occurred between double quotes and there was a $@ in TEMP. It does not matter if the $@ is quoted, as long as it does not expand to anything. In this case, we want to return a quoted empty string. */ temp = (char *)xmalloc (2); temp[0] = CTLNUL; temp[1] = '\0'; } else temp = (char *)NULL; if (c == '-' || c == '+') return (temp); /* c == '=' */ t = temp ? savestring (temp) : savestring (""); t1 = dequote_string (t); free (t); bind_variable (name, t1); free (t1); return (temp); } /* Deal with the right hand side of a ${name:?value} expansion in the case that NAME is null or not set. If VALUE is non-null it is expanded and used as the error message to print, otherwise a standard message is printed. */ static void parameter_brace_expand_error (name, value) char *name, *value; { WORD_LIST *l; char *temp; if (value && *value) { temp = (*value == '~' || (strchr (value, '~') && unquoted_substring ("=~", value))) ? bash_tilde_expand (value) : savestring (value); l = expand_string (temp, 0); FREE (temp); temp = string_list (l); report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */ FREE (temp); dispose_words (l); } else report_error ("%s: parameter null or not set", name); /* Free the data we have allocated during this expansion, since we are about to longjmp out. */ free (name); FREE (value); } /* Return 1 if NAME is something for which parameter_brace_expand_length is OK to do. */ static int valid_length_expression (name) char *name; { return (name[1] == '\0' || /* ${#} */ ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') || /* special param */ (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */ #if defined (ARRAY_VARS) valid_array_reference (name + 1) || /* ${#a[7]} */ #endif legal_identifier (name + 1)); /* ${#PS1} */ } /* Handle the parameter brace expansion that requires us to return the length of a parameter. */ static long parameter_brace_expand_length (name) char *name; { char *t, *newname; long number, arg_index; WORD_LIST *list; #if defined (ARRAY_VARS) SHELL_VAR *var; #endif if (name[1] == '\0') /* ${#} */ number = number_of_args (); else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0') /* ${#@}, ${#*} */ number = number_of_args (); else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') { /* Take the lengths of some of the shell's special parameters. */ switch (name[1]) { case '-': t = which_set_flags (); break; case '?': t = itos (last_command_exit_value); break; case '$': t = itos (dollar_dollar_pid); break; case '!': if (last_asynchronous_pid == NO_PID) t = (char *)NULL; else t = itos (last_asynchronous_pid); break; case '#': t = itos (number_of_args ()); break; } number = STRLEN (t); FREE (t); } #if defined (ARRAY_VARS) else if (valid_array_reference (name + 1)) number = array_length_reference (name + 1); #endif /* ARRAY_VARS */ else { number = 0; if (legal_number (name + 1, &arg_index)) /* ${#1} */ { t = get_dollar_var_value (arg_index); number = STRLEN (t); FREE (t); } #if defined (ARRAY_VARS) else if ((var = find_variable (name + 1)) && array_p (var)) { t = array_reference (array_cell (var), 0); number = STRLEN (t); } #endif else /* ${#PS1} */ { newname = savestring (name); newname[0] = '$'; list = expand_string (newname, Q_DOUBLE_QUOTES); t = list ? string_list (list) : (char *)NULL; free (newname); if (list) dispose_words (list); number = STRLEN (t); FREE (t); } } return (number); } /* Skip characters in SUBSTR until DELIM. SUBSTR is an arithmetic expression, so we do some ad-hoc parsing of an arithmetic expression to find the first DELIM, instead of using strchr(3). Two rules: 1. If the substring contains a `(', read until closing `)'. 2. If the substring contains a `?', read past one `:' for each `?'. */ static char * skiparith (substr, delim) char *substr; int delim; { int skipcol, pcount; char *t; for (skipcol = pcount = 0, t = substr; *t; t++) { /* Balance parens */ if (*t == '(') { pcount++; continue; } if (*t == ')' && pcount) { pcount--; continue; } if (pcount) continue; /* Skip one `:' for each `?' */ if (*t == ':' && skipcol) { skipcol--; continue; } if (*t == delim) break; if (*t == '?') { skipcol++; continue; } } return t; } /* Verify and limit the start and end of the desired substring. If VTYPE == 0, a regular shell variable is being used; if it is 1, then the positional parameters are being used; if it is 2, then VALUE is really a pointer to an array variable that should be used. Return value is 1 if both values were OK, 0 if there was a problem with an invalid expression, or -1 if the values were out of range. */ static int verify_substring_values (value, substr, vtype, e1p, e2p) char *value, *substr; int vtype; long *e1p, *e2p; { char *t, *temp1, *temp2; arrayind_t len; int expok; #if defined (ARRAY_VARS) ARRAY *a; #endif /* duplicate behavior of strchr(3) */ t = skiparith (substr, ':'); if (*t && *t == ':') *t = '\0'; else t = (char *)0; temp1 = expand_string_if_necessary (substr, Q_DOUBLE_QUOTES, expand_string); *e1p = evalexp (temp1, &expok); free (temp1); if (expok == 0) return (0); len = -1; /* paranoia */ switch (vtype) { case VT_VARIABLE: case VT_ARRAYMEMBER: len = strlen (value); break; case VT_POSPARMS: len = number_of_args () + 1; break; #if defined (ARRAY_VARS) case VT_ARRAYVAR: a = (ARRAY *)value; len = array_num_elements (a) + 1; break; #endif } if (len == -1) /* paranoia */ return -1; if (*e1p < 0) /* negative offsets count from end */ *e1p += len; if (*e1p >= len || *e1p < 0) return (-1); if (t) { t++; temp2 = savestring (t); temp1 = expand_string_if_necessary (temp2, Q_DOUBLE_QUOTES, expand_string); free (temp2); t[-1] = ':'; *e2p = evalexp (temp1, &expok); free (temp1); if (expok == 0) return (0); if (*e2p < 0) { internal_error ("%s: substring expression < 0", t); return (0); } *e2p += *e1p; /* want E2 chars starting at E1 */ if (*e2p > len) *e2p = len; } else *e2p = len; return (1); } /* Return the type of variable specified by VARNAME (simple variable, positional param, or array variable). Also return the value specified by VARNAME (value of a variable or a reference to an array element). */ static int get_var_and_type (varname, value, varp, valp) char *varname, *value; SHELL_VAR **varp; char **valp; { int vtype; char *temp; #if defined (ARRAY_VARS) SHELL_VAR *v; #endif vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0'; /* VT_POSPARMS */ *varp = (SHELL_VAR *)NULL; #if defined (ARRAY_VARS) if (valid_array_reference (varname)) { v = array_variable_part (varname, &temp, (int *)0); if (v && array_p (v)) { /* [ */ if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']') { vtype = VT_ARRAYVAR; *valp = (char *)array_cell (v); } else { vtype = VT_ARRAYMEMBER; *valp = array_value (varname, 1); } *varp = v; } else return -1; } else if ((v = find_variable (varname)) && array_p (v)) { vtype = VT_VARIABLE; *varp = v; *valp = array_reference (array_cell (v), 0); } else #endif *valp = value; return vtype; } /******************************************************/ /* */ /* Functions to extract substrings of variable values */ /* */ /******************************************************/ /* Process a variable substring expansion: ${name:e1[:e2]}. If VARNAME is `@', use the positional parameters; otherwise, use the value of VARNAME. If VARNAME is an array variable, use the array elements. */ static char * parameter_brace_substring (varname, value, substr, quoted) char *varname, *value, *substr; int quoted; { long e1, e2; int vtype, r; char *temp, *val; SHELL_VAR *v; if (value == 0) return ((char *)NULL); this_command_name = varname; vtype = get_var_and_type (varname, value, &v, &val); if (vtype == -1) return ((char *)NULL); r = verify_substring_values (val, substr, vtype, &e1, &e2); if (r <= 0) { if (val && vtype == VT_ARRAYMEMBER) free (val); return ((r == 0) ? &expand_param_error : (char *)NULL); } switch (vtype) { case VT_VARIABLE: case VT_ARRAYMEMBER: temp = quoted ? quoted_substring (value, e1, e2) : substring (value, e1, e2); if (val && vtype == VT_ARRAYMEMBER) free (val); break; case VT_POSPARMS: temp = pos_params (varname, e1, e2, quoted); break; #if defined (ARRAY_VARS) case VT_ARRAYVAR: temp = array_subrange (array_cell (v), e1, e2, quoted); break; #endif default: temp = (char *)NULL; } return temp; } /****************************************************************/ /* */ /* Functions to perform pattern substitution on variable values */ /* */ /****************************************************************/ char * pat_subst (string, pat, rep, mflags) char *string, *pat, *rep; int mflags; { char *ret, *s, *e, *str; int rsize, rptr, l, replen, mtype; mtype = mflags & MATCH_TYPEMASK; /* Special cases: * 1. A null pattern with mtype == MATCH_BEG means to prefix STRING * with REP and return the result. * 2. A null pattern with mtype == MATCH_END means to append REP to * STRING and return the result. */ if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END)) { replen = STRLEN (rep); l = strlen (string); ret = (char *)xmalloc (replen + l + 2); if (replen == 0) strcpy (ret, string); else if (mtype == MATCH_BEG) { strcpy (ret, rep); strcpy (ret + replen, string); } else { strcpy (ret, string); strcpy (ret + l, rep); } return (ret); } ret = (char *)xmalloc (rsize = 64); ret[0] = '\0'; for (replen = STRLEN (rep), rptr = 0, str = string;;) { if (match_pattern (str, pat, mtype, &s, &e) == 0) break; l = s - str; RESIZE_MALLOCED_BUFFER (ret, rptr, (l + replen), rsize, 64); /* OK, now copy the leading unmatched portion of the string (from str to s) to ret starting at rptr (the current offset). Then copy the replacement string at ret + rptr + (s - str). Increment rptr (if necessary) and str and go on. */ if (l) { strncpy (ret + rptr, str, l); rptr += l; } if (replen) { strncpy (ret + rptr, rep, replen); rptr += replen; } if (s == e) e++; /* avoid infinite recursion on zero-length match */ str = e; /* e == end of match */ if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY) break; } /* Now copy the unmatched portion of the input string */ if (*str) { RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64); strcpy (ret + rptr, str); } else ret[rptr] = '\0'; return ret; } /* Do pattern match and replacement on the positional parameters. */ static char * pos_params_pat_subst (string, pat, rep, mflags) char *string, *pat, *rep; int mflags; { WORD_LIST *save, *params; WORD_DESC *w; char *ret; save = params = list_rest_of_args (); if (save == 0) return ((char *)NULL); for ( ; params; params = params->next) { ret = pat_subst (params->word->word, pat, rep, mflags); w = make_bare_word (ret); dispose_word (params->word); params->word = w; FREE (ret); } ret = string_list ((mflags & MATCH_QUOTED) ? quote_list (save) : save); dispose_words (save); return (ret); } /* Perform pattern substitution on VALUE, which is the expansion of VARNAME. PATSUB is an expression supplying the pattern to match and the string to substitute. QUOTED is a flags word containing the type of quoting currently in effect. */ static char * parameter_brace_patsub (varname, value, patsub, quoted) char *varname, *value, *patsub; int quoted; { int vtype, mflags; char *val, *temp, *pat, *rep, *p, *lpatsub; SHELL_VAR *v; if (value == 0) return ((char *)NULL); this_command_name = varname; vtype = get_var_and_type (varname, value, &v, &val); if (vtype == -1) return ((char *)NULL); mflags = 0; if (*patsub == '/') { mflags |= MATCH_GLOBREP; patsub++; } /* Malloc this because expand_string_if_necessary or one of the expansion functions in its call chain may free it on a substitution error. */ lpatsub = savestring (patsub); if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) mflags |= MATCH_QUOTED; if (rep = quoted_strchr (lpatsub, '/', ST_BACKSL)) *rep++ = '\0'; else rep = (char *)NULL; if (rep && *rep == '\0') rep = (char *)NULL; /* Expand PAT and REP for command, variable and parameter, arithmetic, and process substitution. Also perform quote removal. Do not perform word splitting or filename generation. */ #if 0 pat = expand_string_if_necessary (lpatsub, quoted, expand_string_unsplit); #else pat = expand_string_if_necessary (lpatsub, (quoted & ~Q_DOUBLE_QUOTES), expand_string_unsplit); #endif if (rep) { if ((mflags & MATCH_QUOTED) == 0) rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit); else rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit); } p = pat; if (pat && pat[0] == '#') { mflags |= MATCH_BEG; p++; } else if (pat && pat[0] == '%') { mflags |= MATCH_END; p++; } else mflags |= MATCH_ANY; /* OK, we now want to substitute REP for PAT in VAL. If flags & MATCH_GLOBREP is non-zero, the substitution is done everywhere, otherwise only the first occurrence of PAT is replaced. */ switch (vtype) { case VT_VARIABLE: case VT_ARRAYMEMBER: temp = pat_subst (val, p, rep, mflags); break; case VT_POSPARMS: temp = pos_params_pat_subst (val, p, rep, mflags); break; #if defined (ARRAY_VARS) case VT_ARRAYVAR: temp = array_pat_subst (array_cell (v), p, rep, mflags); break; #endif } if (val && v && array_p (v) && vtype == VT_ARRAYMEMBER) free (val); FREE (pat); FREE (rep); free (lpatsub); return temp; } /****************************************************************/ /* */ /* Functions to perform parameter expansion on a string */ /* */ /****************************************************************/ /* ${[#][!]name[[:]#[#]%[%]-=?+[word][:e1[:e2]]]} */ static char * parameter_brace_expand (string, indexp, quoted, quoted_dollar_atp, contains_dollar_at) char *string; int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at; { int check_nullness, var_is_set, var_is_null, var_is_special; int want_substring, want_indir, want_patsub; char *name, *value, *temp, *temp1; int t_index, sindex, c; long number; value = (char *)NULL; var_is_set = var_is_null = var_is_special = check_nullness = 0; want_substring = want_indir = want_patsub = 0; sindex = *indexp; t_index = ++sindex; name = string_extract (string, &t_index, "#%:-=?+/}", 1); /* If the name really consists of a special variable, then make sure that we have the entire name. We don't allow indirect references to special variables except `#', `?', `@' and `*'. */ if ((sindex == t_index && (string[t_index] == '-' || string[t_index] == '?' || string[t_index] == '#')) || (sindex == t_index - 1 && string[sindex] == '!' && (string[t_index] == '#' || string[t_index] == '?' || string[t_index] == '@' || string[t_index] == '*'))) { t_index++; free (name); temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0); name = (char *)xmalloc (3 + (strlen (temp1))); *name = string[sindex]; if (string[sindex] == '!') { /* indirect reference of $#, $?, $@, or $* */ name[1] = string[sindex + 1]; strcpy (name + 2, temp1); } else strcpy (name + 1, temp1); free (temp1); } sindex = t_index; /* Find out what character ended the variable name. Then do the appropriate thing. */ if (c = string[sindex]) sindex++; /* If c is followed by one of the valid parameter expansion characters, move past it as normal. If not, assume that a substring specification is being given, and do not move past it. */ if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex])) { check_nullness++; if (c = string[sindex]) sindex++; } else if (c == ':' && string[sindex] != RBRACE) want_substring = 1; else if (c == '/' && string[sindex] != RBRACE) want_patsub = 1; /* Catch the valid and invalid brace expressions that made it through the tests above. */ /* ${#-} is a valid expansion and means to take the length of $-. Similarly for ${#?} and ${##}... */ if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE) { name = (char *)xrealloc (name, 3); name[1] = c; name[2] = '\0'; c = string[sindex++]; } /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */ if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 && member (c, "%:=+/") && string[sindex] == RBRACE) { temp = (char *)NULL; goto bad_substitution; } /* Indirect expansion begins with a `!'. A valid indirect expansion is either a variable name, one of the positional parameters or a special variable that expands to one of the positional parameters. */ want_indir = *name == '!' && (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1]) || VALID_INDIR_PARAM (name[1])); /* Determine the value of this variable. */ /* Check for special variables, directly referenced. */ if (SPECIAL_VAR (name, want_indir)) var_is_special++; /* Check for special expansion things, like the length of a parameter */ if (*name == '#' && name[1]) { /* If we are not pointing at the character just after the closing brace, then we haven't gotten all of the name. Since it begins with a special character, this is a bad substitution. Also check NAME for validity before trying to go on. */ if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0)) { temp = (char *)NULL; goto bad_substitution; } number = parameter_brace_expand_length (name); free (name); *indexp = sindex; return ((number < 0) ? &expand_param_error : itos (number)); } /* ${@} is identical to $@. */ if (name[0] == '@' && name[1] == '\0') { if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 1; if (contains_dollar_at) *contains_dollar_at = 1; } /* Process ${PREFIX*} expansion. */ if (want_indir && string[sindex - 1] == RBRACE && (string[sindex - 2] == '*' || string[sindex - 2] == '@') && legal_variable_starter ((unsigned char) name[1])) { char **x; WORD_LIST *xlist; temp1 = savestring (name + 1); number = strlen (temp1); temp1[number - 1] = '\0'; x = all_variables_matching_prefix (temp1); xlist = argv_to_word_list (x, 1, 0); if (string[sindex - 2] == '*') temp = string_list_dollar_star (xlist); else { temp = string_list_dollar_at (xlist, quoted); if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 1; if (contains_dollar_at) *contains_dollar_at = 1; } free (x); free (xlist); free (temp1); *indexp = sindex; return (temp); } /* Make sure that NAME is valid before trying to go on. */ if (valid_brace_expansion_word (want_indir ? name + 1 : name, var_is_special) == 0) { temp = (char *)NULL; goto bad_substitution; } if (want_indir) temp = parameter_brace_expand_indir (name + 1, var_is_special, quoted); else temp = parameter_brace_expand_word (name, var_is_special, quoted); #if defined (ARRAY_VARS) if (valid_array_reference (name)) { temp1 = strchr (name, '['); if (temp1 && temp1[1] == '@' && temp1[2] == ']') { if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 1; if (contains_dollar_at) *contains_dollar_at = 1; } /* [ */ /* ${array[*]}, when unquoted, should be treated like ${array[@]}, which should result in separate words even when IFS is unset. */ if (temp1 && temp1[1] == '*' && temp1[2] == ']' && quoted == 0) { if (contains_dollar_at) *contains_dollar_at = 1; } } #endif var_is_set = temp != (char *)0; var_is_null = check_nullness && (var_is_set == 0 || *temp == 0); /* Get the rest of the stuff inside the braces. */ if (c && c != RBRACE) { /* Extract the contents of the ${ ... } expansion according to the Posix.2 rules. */ value = extract_dollar_brace_string (string, &sindex, quoted); if (string[sindex] == RBRACE) sindex++; else goto bad_substitution; } else value = (char *)NULL; *indexp = sindex; /* If this is a substring spec, process it and add the result. */ if (want_substring) { temp1 = parameter_brace_substring (name, temp, value, quoted); FREE (name); FREE (value); FREE (temp); return (temp1); } else if (want_patsub) { temp1 = parameter_brace_patsub (name, temp, value, quoted); FREE (name); FREE (value); FREE (temp); return (temp1); } /* Do the right thing based on which character ended the variable name. */ switch (c) { default: case '\0': bad_substitution: report_error ("%s: bad substitution", string ? string : "??"); FREE (value); FREE (temp); free (name); return &expand_param_error; case RBRACE: if (var_is_set == 0 && unbound_vars_is_error) { report_error ("%s: unbound variable", name); FREE (value); FREE (temp); free (name); last_command_exit_value = EXECUTION_FAILURE; return (interactive_shell ? &expand_param_error : &expand_param_fatal); } break; case '#': /* ${param#[#]pattern} */ case '%': /* ${param%[%]pattern} */ if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0') { FREE (value); break; } if ((name[0] == '@' || name[0] == '*') && name[1] == '\0') temp1 = parameter_list_remove_pattern (value, name[0], c, quoted); #if defined (ARRAY_VARS) else if (valid_array_reference (name)) temp1 = array_remove_pattern (value, name, temp, c, quoted); #endif else temp1 = parameter_brace_remove_pattern (value, temp, c, quoted); free (temp); free (value); temp = temp1; break; case '-': case '=': case '?': case '+': if (var_is_set && var_is_null == 0) { /* If the operator is `+', we don't want the value of the named variable for anything, just the value of the right hand side. */ if (c == '+') { /* XXX -- if we're double-quoted and the named variable is "$@", we want to turn off any special handling of "$@" -- we're not using it, so whatever is on the rhs applies. */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 0; if (contains_dollar_at) *contains_dollar_at = 0; FREE (temp); if (value) { temp = parameter_brace_expand_rhs (name, value, c, quoted, quoted_dollar_atp, contains_dollar_at); free (value); } else temp = (char *)NULL; } else { FREE (value); } /* Otherwise do nothing; just use the value in TEMP. */ } else /* VAR not set or VAR is NULL. */ { FREE (temp); temp = (char *)NULL; if (c == '=' && var_is_special) { report_error ("$%s: cannot assign in this way", name); free (name); free (value); return &expand_param_error; } else if (c == '?') { parameter_brace_expand_error (name, value); return (interactive_shell ? &expand_param_error : &expand_param_fatal); } else if (c != '+') { /* XXX -- if we're double-quoted and the named variable is "$@", we want to turn off any special handling of "$@" -- we're not using it, so whatever is on the rhs applies. */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp) *quoted_dollar_atp = 0; if (contains_dollar_at) *contains_dollar_at = 0; temp = parameter_brace_expand_rhs (name, value, c, quoted, quoted_dollar_atp, contains_dollar_at); } free (value); } break; } free (name); return (temp); } /* Expand a single ${xxx} expansion. The braces are optional. When the braces are used, parameter_brace_expand() does the work, possibly calling param_expand recursively. */ static char * param_expand (string, sindex, quoted, expanded_something, contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p, pflags) char *string; int *sindex, quoted, *expanded_something, *contains_dollar_at; int *quoted_dollar_at_p, *had_quoted_null_p, pflags; { char *temp, *temp1; int zindex, t_index, expok; unsigned char c; long number; SHELL_VAR *var; WORD_LIST *list; zindex = *sindex; c = string[++zindex]; temp = (char *)NULL; /* Do simple cases first. Switch on what follows '$'. */ switch (c) { /* $0 .. $9? */ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': temp1 = dollar_vars[TODIGIT (c)]; if (unbound_vars_is_error && temp1 == (char *)NULL) { report_error ("$%c: unbound variable", c); last_command_exit_value = EXECUTION_FAILURE; return (interactive_shell ? &expand_param_error : &expand_param_fatal); } temp = temp1 ? savestring (temp1) : (char *)NULL; break; /* $$ -- pid of the invoking shell. */ case '$': temp = itos (dollar_dollar_pid); break; /* $# -- number of positional parameters. */ case '#': temp = itos (number_of_args ()); break; /* $? -- return value of the last synchronous command. */ case '?': temp = itos (last_command_exit_value); break; /* $- -- flags supplied to the shell on invocation or by `set'. */ case '-': temp = which_set_flags (); break; /* $! -- Pid of the last asynchronous command. */ case '!': /* If no asynchronous pids have been created, expand to nothing. If `set -u' has been executed, and no async processes have been created, this is an expansion error. */ if (last_asynchronous_pid == NO_PID) { if (expanded_something) *expanded_something = 0; temp = (char *)NULL; if (unbound_vars_is_error) { report_error ("$%c: unbound variable", c); last_command_exit_value = EXECUTION_FAILURE; return (interactive_shell ? &expand_param_error : &expand_param_fatal); } } else temp = itos (last_asynchronous_pid); break; /* The only difference between this and $@ is when the arg is quoted. */ case '*': /* `$*' */ list = list_rest_of_args (); /* If there are no command-line arguments, this should just disappear if there are other characters in the expansion, even if it's quoted. */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0) temp = (char *)NULL; else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) { /* If we have "$*" we want to make a string of the positional parameters, separated by the first character of $IFS, and quote the whole string, including the separators. If IFS is unset, the parameters are separated by ' '; if $IFS is null, the parameters are concatenated. */ temp = string_list_dollar_star (list); temp1 = quote_string (temp); free (temp); temp = temp1; } else { /* If the $* is not quoted it is identical to $@ */ temp = string_list_dollar_at (list, quoted); if (expand_no_split_dollar_star == 0 && contains_dollar_at) *contains_dollar_at = 1; } dispose_words (list); break; /* When we have "$@" what we want is "$1" "$2" "$3" ... This means that we have to turn quoting off after we split into the individually quoted arguments so that the final split on the first character of $IFS is still done. */ case '@': /* `$@' */ list = list_rest_of_args (); /* We want to flag the fact that we saw this. We can't turn off quoting entirely, because other characters in the string might need it (consider "\"$@\""), but we need some way to signal that the final split on the first character of $IFS should be done, even though QUOTED is 1. */ if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) *quoted_dollar_at_p = 1; if (contains_dollar_at) *contains_dollar_at = 1; /* We want to separate the positional parameters with the first character of $IFS in case $IFS is something other than a space. We also want to make sure that splitting is done no matter what -- according to POSIX.2, this expands to a list of the positional parameters no matter what IFS is set to. */ temp = string_list_dollar_at (list, quoted); dispose_words (list); break; case LBRACE: temp = parameter_brace_expand (string, &zindex, quoted, quoted_dollar_at_p, contains_dollar_at); if (temp == &expand_param_error || temp == &expand_param_fatal) return (temp); /* XXX */ /* Quoted nulls should be removed if there is anything else in the string. */ /* Note that we saw the quoted null so we can add one back at the end of this function if there are no other characters in the string, discard TEMP, and go on. The exception to this is when we have "${@}" and $1 is '', since $@ needs special handling. */ if (temp && QUOTED_NULL (temp)) { if (had_quoted_null_p) *had_quoted_null_p = 1; if (*quoted_dollar_at_p == 0) { free (temp); temp = (char *)NULL; } } goto return0; /* Do command or arithmetic substitution. */ case LPAREN: /* We have to extract the contents of this paren substitution. */ t_index = zindex + 1; temp = extract_command_subst (string, &t_index); zindex = t_index; /* For Posix.2-style `$(( ))' arithmetic substitution, extract the expression and pass it to the evaluator. */ if (temp && *temp == LPAREN) { char *temp2; temp1 = temp + 1; temp2 = savestring (temp1); t_index = strlen (temp2) - 1; if (temp2[t_index] != RPAREN) { free (temp2); goto comsub; } /* Cut off ending `)' */ temp2[t_index] = '\0'; /* Expand variables found inside the expression. */ temp1 = expand_string_if_necessary (temp2, Q_DOUBLE_QUOTES, expand_string); free (temp2); arithsub: /* No error messages. */ this_command_name = (char *)NULL; number = evalexp (temp1, &expok); free (temp); free (temp1); if (expok == 0) { if (interactive_shell == 0 && posixly_correct) { last_command_exit_value = EXECUTION_FAILURE; return (&expand_param_fatal); } else return (&expand_param_error); } temp = itos (number); break; } comsub: temp1 = command_substitute (temp, quoted); FREE (temp); temp = temp1; break; /* Do POSIX.2d9-style arithmetic substitution. This will probably go away in a future bash release. */ case '[': /* Extract the contents of this arithmetic substitution. */ t_index = zindex + 1; temp = extract_arithmetic_subst (string, &t_index); zindex = t_index; /* Do initial variable expansion. */ temp1 = expand_string_if_necessary (temp, Q_DOUBLE_QUOTES, expand_string); goto arithsub; default: /* Find the variable in VARIABLE_LIST. */ temp = (char *)NULL; for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++) ; temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL; /* If this isn't a variable name, then just output the `$'. */ if (temp1 == 0 || *temp1 == '\0') { FREE (temp1); temp = (char *)xmalloc (2); temp[0] = '$'; temp[1] = '\0'; if (expanded_something) *expanded_something = 0; goto return0; } /* If the variable exists, return its value cell. */ var = find_variable (temp1); if (var && invisible_p (var) == 0 && value_cell (var)) { #if defined (ARRAY_VARS) if (array_p (var)) { temp = array_reference (array_cell (var), 0); if (temp) temp = quote_escapes (temp); } else #endif temp = quote_escapes (value_cell (var)); free (temp1); if (tempvar_p (var)) /* XXX */ { dispose_variable (var); /* XXX */ var = (SHELL_VAR *)NULL; } goto return0; } temp = (char *)NULL; if (unbound_vars_is_error) report_error ("%s: unbound variable", temp1); else { free (temp1); goto return0; } free (temp1); last_command_exit_value = EXECUTION_FAILURE; return ((unbound_vars_is_error && interactive_shell == 0) ? &expand_param_fatal : &expand_param_error); } if (string[zindex]) zindex++; return0: *sindex = zindex; return (temp); } /* Make a word list which is the result of parameter and variable expansion, command substitution, arithmetic substitution, and quote removal of WORD. Return a pointer to a WORD_LIST which is the result of the expansion. If WORD contains a null word, the word list returned is also null. QUOTED contains flag values defined in shell.h. ISEXP is used to tell expand_word_internal that the word should be treated as the result of an expansion. This has implications for how IFS characters in the word are treated. CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null they point to an integer value which receives information about expansion. CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero. EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions, else zero. This only does word splitting in the case of $@ expansion. In that case, we split on ' '. */ /* Values for the local variable quoted_state. */ #define UNQUOTED 0 #define PARTIALLY_QUOTED 1 #define WHOLLY_QUOTED 2 static WORD_LIST * expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something) WORD_DESC *word; int quoted, isexp; int *contains_dollar_at; int *expanded_something; { WORD_LIST *list; WORD_DESC *tword; /* The intermediate string that we build while expanding. */ char *istring; /* The current size of the above object. */ int istring_size; /* Index into ISTRING. */ int istring_index; /* Temporary string storage. */ char *temp, *temp1; /* The text of WORD. */ register char *string; /* The index into STRING. */ int sindex; /* This gets 1 if we see a $@ while quoted. */ int quoted_dollar_at; /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on whether WORD contains no quoting characters, a partially quoted string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */ int quoted_state; int had_quoted_null; int has_dollar_at; int tflag; register unsigned char c; /* Current character. */ unsigned char uc; int t_index; /* For calls to string_extract_xxx. */ char ifscmap[256]; char twochars[2]; istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE); istring[istring_index = 0] = '\0'; quoted_dollar_at = had_quoted_null = has_dollar_at = 0; quoted_state = UNQUOTED; string = word->word; if (string == 0) goto finished_with_string; if (contains_dollar_at) *contains_dollar_at = 0; /* Cache a bitmap of characters in IFS for quoting IFS characters that are not part of an expansion. POSIX.2 says this is a must. */ temp = getifs (); bzero (ifscmap, sizeof (ifscmap)); for (temp1 = temp; temp1 && *temp1; temp1++) #if 0 /* This check compensates for what I think is a parsing problem -- the end brace matching algorithms for ${...} expressions differ between parse.y and subst.c. For instance, the parser passes ${abc:-G { I } K } as one word when it should be three. */ if (*temp1 != ' ' && *temp1 != '\t' && *temp1 != '\n') #endif { uc = *temp1; ifscmap[uc] = 1; } /* Begin the expansion. */ for (sindex = 0; ;) { c = string[sindex]; /* Case on toplevel character. */ switch (c) { case '\0': goto finished_with_string; case CTLESC: temp = (char *)xmalloc (3); temp[0] = CTLESC; temp[1] = c = string[++sindex]; temp[2] = '\0'; dollar_add_string: if (string[sindex]) sindex++; add_string: if (temp) { istring = sub_append_string (temp, istring, &istring_index, &istring_size); temp = (char *)0; } break; #if defined (PROCESS_SUBSTITUTION) /* Process substitution. */ case '<': case '>': { if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || posixly_correct) { sindex--; /* add_character: label increments sindex */ goto add_character; } else t_index = sindex + 1; /* skip past both '<' and LPAREN */ temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/ sindex = t_index; /* If the process substitution specification is `<()', we want to open the pipe for writing in the child and produce output; if it is `>()', we want to open the pipe for reading in the child and consume input. */ temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0; FREE (temp1); goto dollar_add_string; } #endif /* PROCESS_SUBSTITUTION */ case '$': if (expanded_something) *expanded_something = 1; has_dollar_at = 0; temp = param_expand (string, &sindex, quoted, expanded_something, &has_dollar_at, "ed_dollar_at, &had_quoted_null, 0); if (temp == &expand_param_error || temp == &expand_param_fatal) { free (string); free (istring); return ((temp == &expand_param_error) ? &expand_word_error : &expand_word_fatal); } if (contains_dollar_at && has_dollar_at) *contains_dollar_at = 1; goto add_string; break; case '`': /* Backquoted command substitution. */ { sindex++; if (expanded_something) *expanded_something = 1; temp = string_extract (string, &sindex, "`", 0); de_backslash (temp); temp1 = command_substitute (temp, quoted); FREE (temp); temp = temp1; goto dollar_add_string; } case '\\': if (string[sindex + 1] == '\n') { sindex += 2; continue; } c = string[++sindex]; if (quoted & Q_HERE_DOCUMENT) tflag = CBSHDOC; else if (quoted & Q_DOUBLE_QUOTES) tflag = CBSDQUOTE; else tflag = 0; if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0)) { twochars[0] = '\\'; twochars[1] = c; } else if (c == 0) { c = CTLNUL; sindex--; /* add_character: label increments sindex */ goto add_character; } else { twochars[0] = CTLESC; twochars[1] = c; } sindex++; add_twochars: /* BEFORE jumping here, we need to increment sindex if appropriate */ RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE); istring[istring_index++] = twochars[0]; istring[istring_index++] = twochars[1]; istring[istring_index] = '\0'; break; case '"': if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT|Q_NOQUOTE)) goto add_character; t_index = ++sindex; temp = string_extract_double_quoted (string, &sindex, 0); /* If the quotes surrounded the entire string, then the whole word was quoted. */ quoted_state = (t_index == 1 && string[sindex] == '\0') ? WHOLLY_QUOTED : PARTIALLY_QUOTED; if (temp && *temp) { tword = make_word (temp); /* XXX */ free (temp); temp = (char *)NULL; has_dollar_at = 0; list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &has_dollar_at, (int *)NULL); if (list == &expand_word_error || list == &expand_word_fatal) { free (istring); free (string); /* expand_word_internal has already freed temp_word->word for us because of the way it prints error messages. */ tword->word = (char *)NULL; dispose_word (tword); return list; } dispose_word (tword); /* "$@" (a double-quoted dollar-at) expands into nothing, not even a NULL word, when there are no positional parameters. */ if (list == 0 && has_dollar_at) { quoted_dollar_at++; break; } /* If we get "$@", we know we have expanded something, so we need to remember it for the final split on $IFS. This is a special case; it's the only case where a quoted string can expand into more than one word. It's going to come back from the above call to expand_word_internal as a list with a single word, in which all characters are quoted and separated by blanks. What we want to do is to turn it back into a list for the next piece of code. */ if (list) dequote_list (list); if (has_dollar_at) { quoted_dollar_at++; if (contains_dollar_at) *contains_dollar_at = 1; if (expanded_something) *expanded_something = 1; } } else { /* What we have is "". This is a minor optimization. */ FREE (temp); list = (WORD_LIST *)NULL; } /* The code above *might* return a list (consider the case of "$@", where it returns "$1", "$2", etc.). We can't throw away the rest of the list, and we have to make sure each word gets added as quoted. We test on tresult->next: if it is non-NULL, we quote the whole list, save it to a string with string_list, and add that string. We don't need to quote the results of this (and it would be wrong, since that would quote the separators as well), so we go directly to add_string. */ if (list) { if (list->next) { /* Testing quoted_dollar_at makes sure that "$@" is split correctly when $IFS does not contain a space. */ temp = quoted_dollar_at ? string_list_dollar_at (list, Q_DOUBLE_QUOTES) : string_list (quote_list (list)); dispose_words (list); goto add_string; } else { temp = savestring (list->word->word); dispose_words (list); #if 1 /* If the string is not a quoted null string, we want to remove any embedded unquoted CTLNUL characters. We do not want to turn quoted null strings back into the empty string, though. We do this because we want to remove any quoted nulls from expansions that contain other characters. For example, if we have x"$*"y or "x$*y" and there are no positional parameters, the $* should expand into nothing. */ if (QUOTED_NULL (temp) == 0) remove_quoted_nulls (temp); /* XXX */ #endif } } else temp = (char *)NULL; /* We do not want to add quoted nulls to strings that are only partially quoted; we can throw them away. */ if (temp == 0 && quoted_state == PARTIALLY_QUOTED) continue; add_quoted_string: if (temp) { temp1 = temp; temp = quote_string (temp); free (temp1); goto add_string; } else { /* Add NULL arg. */ c = CTLNUL; sindex--; /* add_character: label increments sindex */ goto add_character; } /* break; */ case '\'': if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT|Q_NOQUOTE)) goto add_character; t_index = ++sindex; temp = string_extract_single_quoted (string, &sindex); /* If the entire STRING was surrounded by single quotes, then the string is wholly quoted. */ quoted_state = (t_index == 1 && string[sindex] == '\0') ? WHOLLY_QUOTED : PARTIALLY_QUOTED; /* If all we had was '', it is a null expansion. */ if (*temp == '\0') { free (temp); temp = (char *)NULL; } else remove_quoted_escapes (temp); /* We do not want to add quoted nulls to strings that are only partially quoted; such nulls are discarded. */ if (temp == 0 && (quoted_state == PARTIALLY_QUOTED)) continue; /* If we have a quoted null expansion, add a quoted NULL to istring. */ if (temp == 0) { c = CTLNUL; sindex--; /* add_character: label increments sindex */ goto add_character; } else goto add_quoted_string; /* break; */ default: /* This is the fix for " $@ " */ if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && ifscmap[c])) { if (string[sindex]) /* from old goto dollar_add_string */ sindex++; if (c == 0) { c = CTLNUL; goto add_character; } else { twochars[0] = CTLESC; twochars[1] = c; goto add_twochars; } } add_character: RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size, DEFAULT_ARRAY_SIZE); istring[istring_index++] = c; istring[istring_index] = '\0'; /* Next character. */ sindex++; } } finished_with_string: /* OK, we're ready to return. If we have a quoted string, and quoted_dollar_at is not set, we do no splitting at all; otherwise we split on ' '. The routines that call this will handle what to do if nothing has been expanded. */ /* Partially and wholly quoted strings which expand to the empty string are retained as an empty arguments. Unquoted strings which expand to the empty string are discarded. The single exception is the case of expanding "$@" when there are no positional parameters. In that case, we discard the expansion. */ /* Because of how the code that handles "" and '' in partially quoted strings works, we need to make ISTRING into a QUOTED_NULL if we saw quoting characters, but the expansion was empty. "" and '' are tossed away before we get to this point when processing partially quoted strings. This makes "" and $xxx"" equivalent when xxx is unset. We also look to see whether we saw a quoted null from a ${} expansion and add one back if we need to. */ /* If we expand to nothing and there were no single or double quotes in the word, we throw it away. Otherwise, we return a NULL word. The single exception is for $@ surrounded by double quotes when there are no positional parameters. In that case, we also throw the word away. */ if (*istring == '\0') { if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED)) { istring[0] = CTLNUL; istring[1] = '\0'; tword = make_bare_word (istring); list = make_word_list (tword, (WORD_LIST *)NULL); if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) tword->flags |= W_QUOTED; } /* According to sh, ksh, and Posix.2, if a word expands into nothing and a double-quoted "$@" appears anywhere in it, then the entire word is removed. */ else if (quoted_state == UNQUOTED || quoted_dollar_at) list = (WORD_LIST *)NULL; #if 0 else { tword = make_bare_word (istring); list = make_word_list (tword, (WORD_LIST *)NULL); if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) tword->flags |= W_QUOTED; } #else else list = (WORD_LIST *)NULL; #endif } else if (word->flags & W_NOSPLIT) { tword = make_bare_word (istring); list = make_word_list (tword, (WORD_LIST *)NULL); if (word->flags & W_ASSIGNMENT) tword->flags |= W_ASSIGNMENT; /* XXX */ if (word->flags & W_NOGLOB) tword->flags |= W_NOGLOB; /* XXX */ if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) tword->flags |= W_QUOTED; } else { char *ifs_chars; ifs_chars = (quoted_dollar_at || has_dollar_at) ? getifs () : (char *)NULL; /* If we have $@, we need to split the results no matter what. If IFS is unset or NULL, string_list_dollar_at has separated the positional parameters with a space, so we split on space (we have set ifs_chars to " \t\n" above if ifs is unset). If IFS is set, string_list_dollar_at has separated the positional parameters with the first character of $IFS, so we split on $IFS. */ if (has_dollar_at && ifs_chars) list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1); else { tword = make_bare_word (istring); list = make_word_list (tword, (WORD_LIST *)NULL); if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED)) tword->flags |= W_QUOTED; if (word->flags & W_ASSIGNMENT) tword->flags |= W_ASSIGNMENT; if (word->flags & W_NOGLOB) tword->flags |= W_NOGLOB; } } free (istring); return (list); } /* **************************************************************** */ /* */ /* Functions for Quote Removal */ /* */ /* **************************************************************** */ /* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the backslash quoting rules for within double quotes. */ char * string_quote_removal (string, quoted) char *string; int quoted; { char *r, *result_string, *temp; int sindex, tindex, dquote; unsigned char c; /* The result can be no longer than the original string. */ r = result_string = (char *)xmalloc (strlen (string) + 1); for (dquote = sindex = 0; c = string[sindex];) { switch (c) { case '\\': c = string[++sindex]; if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0) *r++ = '\\'; /* FALLTHROUGH */ default: *r++ = c; sindex++; break; case '\'': if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) { *r++ = c; sindex++; break; } tindex = sindex + 1; temp = string_extract_single_quoted (string, &tindex); if (temp) { strcpy (r, temp); r += strlen (r); free (temp); } sindex = tindex; break; case '"': dquote = 1 - dquote; sindex++; break; } } *r = '\0'; return (result_string); } #if 0 /* UNUSED */ /* Perform quote removal on word WORD. This allocates and returns a new WORD_DESC *. */ WORD_DESC * word_quote_removal (word, quoted) WORD_DESC *word; int quoted; { WORD_DESC *w; char *t; t = string_quote_removal (word->word, quoted); w = make_bare_word (t); free (t); return (w); } /* Perform quote removal on all words in LIST. If QUOTED is non-zero, the members of the list are treated as if they are surrounded by double quotes. Return a new list, or NULL if LIST is NULL. */ WORD_LIST * word_list_quote_removal (list, quoted) WORD_LIST *list; int quoted; { WORD_LIST *result, *t, *tresult; for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) { tresult = (WORD_LIST *)xmalloc (sizeof (WORD_LIST)); tresult->word = word_quote_removal (t->word, quoted); tresult->next = (WORD_LIST *)NULL; result = (WORD_LIST *) list_append (result, tresult); } return (result); } #endif /******************************************* * * * Functions to perform word splitting * * * *******************************************/ static char * getifs () { SHELL_VAR *ifs; ifs = find_variable ("IFS"); /* If IFS is unset, it defaults to " \t\n". */ return (ifs ? value_cell (ifs) : " \t\n"); } /* This splits a single word into a WORD LIST on $IFS, but only if the word is not quoted. list_string () performs quote removal for us, even if we don't do any splitting. */ WORD_LIST * word_split (w) WORD_DESC *w; { WORD_LIST *result; SHELL_VAR *ifs; char *ifs_chars; if (w) { ifs = find_variable ("IFS"); /* If IFS is unset, it defaults to " \t\n". */ ifs_chars = ifs ? value_cell (ifs) : " \t\n"; if ((w->flags & W_QUOTED) || !ifs_chars) ifs_chars = ""; result = list_string (w->word, ifs_chars, w->flags & W_QUOTED); if (ifs && tempvar_p (ifs)) /* XXX */ dispose_variable (ifs); /* XXX */ } else result = (WORD_LIST *)NULL; return (result); } /* Perform word splitting on LIST and return the RESULT. It is possible to return (WORD_LIST *)NULL. */ static WORD_LIST * word_list_split (list) WORD_LIST *list; { WORD_LIST *result, *t, *tresult; for (t = list, result = (WORD_LIST *)NULL; t; t = t->next) { tresult = word_split (t->word); result = (WORD_LIST *) list_append (result, tresult); } return (result); } /************************************************** * * * Functions to expand an entire WORD_LIST * * * **************************************************/ /* Put NLIST (which is a WORD_LIST * of only one element) at the front of ELIST, and set ELIST to the new list. */ #define PREPEND_LIST(nlist, elist) \ do { nlist->next = elist; elist = nlist; } while (0) /* Separate out any initial variable assignments from TLIST. If set -k has been executed, remove all assignment statements from TLIST. Initial variable assignments and other environment assignments are placed on SUBST_ASSIGN_VARLIST. */ static WORD_LIST * separate_out_assignments (tlist) WORD_LIST *tlist; { register WORD_LIST *vp, *lp; if (!tlist) return ((WORD_LIST *)NULL); if (subst_assign_varlist) dispose_words (subst_assign_varlist); /* Clean up after previous error */ subst_assign_varlist = (WORD_LIST *)NULL; vp = lp = tlist; /* Separate out variable assignments at the start of the command. Loop invariant: vp->next == lp Loop postcondition: lp = list of words left after assignment statements skipped tlist = original list of words */ while (lp && (lp->word->flags & W_ASSIGNMENT)) { vp = lp; lp = lp->next; } /* If lp != tlist, we have some initial assignment statements. We make SUBST_ASSIGN_VARLIST point to the list of assignment words and TLIST point to the remaining words. */ if (lp != tlist) { subst_assign_varlist = tlist; /* ASSERT(vp->next == lp); */ vp->next = (WORD_LIST *)NULL; /* terminate variable list */ tlist = lp; /* remainder of word list */ } /* vp == end of variable list */ /* tlist == remainder of original word list without variable assignments */ if (!tlist) /* All the words in tlist were assignment statements */ return ((WORD_LIST *)NULL); /* ASSERT(tlist != NULL); */ /* ASSERT((tlist->word->flags & W_ASSIGNMENT) == 0); */ /* If the -k option is in effect, we need to go through the remaining words, separate out the assignment words, and place them on SUBST_ASSIGN_VARLIST. */ if (place_keywords_in_env) { WORD_LIST *tp; /* tp == running pointer into tlist */ tp = tlist; lp = tlist->next; /* Loop Invariant: tp->next == lp */ /* Loop postcondition: tlist == word list without assignment statements */ while (lp) { if (lp->word->flags & W_ASSIGNMENT) { /* Found an assignment statement, add this word to end of subst_assign_varlist (vp). */ if (!subst_assign_varlist) subst_assign_varlist = vp = lp; else { vp->next = lp; vp = lp; } /* Remove the word pointed to by LP from TLIST. */ tp->next = lp->next; /* ASSERT(vp == lp); */ lp->next = (WORD_LIST *)NULL; lp = tp->next; } else { tp = lp; lp = lp->next; } } } return (tlist); } #define WEXP_VARASSIGN 0x001 #define WEXP_BRACEEXP 0x002 #define WEXP_TILDEEXP 0x004 #define WEXP_PARAMEXP 0x008 #define WEXP_PATHEXP 0x010 /* All of the expansions, including variable assignments at the start of the list. */ #define WEXP_ALL (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) /* All of the expansions except variable assignments at the start of the list. */ #define WEXP_NOVARS (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP) /* All of the `shell expansions': brace expansion, tilde expansion, parameter expansion, command substitution, arithmetic expansion, word splitting, and quote removal. */ #define WEXP_SHELLEXP (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP) /* Take the list of words in LIST and do the various substitutions. Return a new list of words which is the expanded list, and without things like variable assignments. */ WORD_LIST * expand_words (list) WORD_LIST *list; { return (expand_word_list_internal (list, WEXP_ALL)); } /* Same as expand_words (), but doesn't hack variable or environment variables. */ WORD_LIST * expand_words_no_vars (list) WORD_LIST *list; { return (expand_word_list_internal (list, WEXP_NOVARS)); } WORD_LIST * expand_words_shellexp (list) WORD_LIST *list; { return (expand_word_list_internal (list, WEXP_SHELLEXP)); } static WORD_LIST * glob_expand_word_list (tlist, eflags) WORD_LIST *tlist; int eflags; { char **glob_array, *temp_string; register int glob_index; WORD_LIST *glob_list, *output_list, *disposables, *next; WORD_DESC *tword; output_list = disposables = (WORD_LIST *)NULL; glob_array = (char **)NULL; while (tlist) { /* For each word, either globbing is attempted or the word is added to orig_list. If globbing succeeds, the results are added to orig_list and the word (tlist) is added to the list of disposable words. If globbing fails and failed glob expansions are left unchanged (the shell default), the original word is added to orig_list. If globbing fails and failed glob expansions are removed, the original word is added to the list of disposable words. orig_list ends up in reverse order and requires a call to reverse_list to be set right. After all words are examined, the disposable words are freed. */ next = tlist->next; /* If the word isn't an assignment and contains an unquoted pattern matching character, then glob it. */ if ((tlist->word->flags & W_NOGLOB) == 0 && unquoted_glob_pattern_p (tlist->word->word)) { glob_array = shell_glob_filename (tlist->word->word); /* Handle error cases. I don't think we should report errors like "No such file or directory". However, I would like to report errors like "Read failed". */ if (GLOB_FAILED (glob_array)) { glob_array = (char **)xmalloc (sizeof (char *)); glob_array[0] = (char *)NULL; } /* Dequote the current word in case we have to use it. */ if (glob_array[0] == NULL) { temp_string = dequote_string (tlist->word->word); free (tlist->word->word); tlist->word->word = temp_string; } /* Make the array into a word list. */ glob_list = (WORD_LIST *)NULL; for (glob_index = 0; glob_array[glob_index]; glob_index++) { tword = make_bare_word (glob_array[glob_index]); tword->flags |= W_GLOBEXP; /* XXX */ glob_list = make_word_list (tword, glob_list); } if (glob_list) { output_list = (WORD_LIST *)list_append (glob_list, output_list); PREPEND_LIST (tlist, disposables); } else if (allow_null_glob_expansion == 0) { /* Failed glob expressions are left unchanged. */ PREPEND_LIST (tlist, output_list); } else { /* Failed glob expressions are removed. */ PREPEND_LIST (tlist, disposables); } } else { /* Dequote the string. */ temp_string = dequote_string (tlist->word->word); free (tlist->word->word); tlist->word->word = temp_string; PREPEND_LIST (tlist, output_list); } free_array (glob_array); glob_array = (char **)NULL; tlist = next; } if (disposables) dispose_words (disposables); if (output_list) output_list = REVERSE_LIST (output_list, WORD_LIST *); return (output_list); } #if defined (BRACE_EXPANSION) static WORD_LIST * brace_expand_word_list (tlist, eflags) WORD_LIST *tlist; int eflags; { register char **expansions; char *temp_string; WORD_LIST *disposables, *output_list, *next; WORD_DESC *w; int eindex; for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next) { next = tlist->next; /* Only do brace expansion if the word has a brace character. If not, just add the word list element to BRACES and continue. In the common case, at least when running shell scripts, this will degenerate to a bunch of calls to `strchr', and then what is basically a reversal of TLIST into BRACES, which is corrected by a call to reverse_list () on BRACES when the end of TLIST is reached. */ if (strchr (tlist->word->word, LBRACE)) { expansions = brace_expand (tlist->word->word); for (eindex = 0; temp_string = expansions[eindex]; eindex++) { w = make_word (temp_string); /* If brace expansion didn't change the word, preserve the flags. We may want to preserve the flags unconditionally someday -- XXX */ if (STREQ (temp_string, tlist->word->word)) w->flags = tlist->word->flags; output_list = make_word_list (w, output_list); free (expansions[eindex]); } free (expansions); /* Add TLIST to the list of words to be freed after brace expansion has been performed. */ PREPEND_LIST (tlist, disposables); } else PREPEND_LIST (tlist, output_list); } if (disposables) dispose_words (disposables); if (output_list) output_list = REVERSE_LIST (output_list, WORD_LIST *); return (output_list); } #endif static WORD_LIST * shell_expand_word_list (tlist, eflags) WORD_LIST *tlist; int eflags; { WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list; int expanded_something, has_dollar_at; char *temp_string; /* We do tilde expansion all the time. This is what 1003.2 says. */ new_list = (WORD_LIST *)NULL; for (orig_list = tlist; tlist; tlist = next) { temp_string = tlist->word->word; next = tlist->next; /* Posix.2 section 3.6.1 says that tildes following `=' in words which are not assignment statements are not expanded. We do this only if POSIXLY_CORRECT is enabled. Essentially, we do tilde expansion on unquoted assignment statements (flags include W_ASSIGNMENT but not W_QUOTED). */ if (temp_string[0] == '~' || (((tlist->word->flags & (W_ASSIGNMENT|W_QUOTED)) == W_ASSIGNMENT) && posixly_correct == 0 && strchr (temp_string, '~') && (unquoted_substring ("=~", temp_string) || unquoted_substring (":~", temp_string)))) { tlist->word->word = bash_tilde_expand (temp_string); free (temp_string); } expanded_something = 0; expanded = expand_word_internal (tlist->word, 0, 0, &has_dollar_at, &expanded_something); if (expanded == &expand_word_error || expanded == &expand_word_fatal) { /* By convention, each time this error is returned, tlist->word->word has already been freed. */ tlist->word->word = (char *)NULL; /* Dispose our copy of the original list. */ dispose_words (orig_list); /* Dispose the new list we're building. */ dispose_words (new_list); last_command_exit_value = EXECUTION_FAILURE; if (expanded == &expand_word_error) jump_to_top_level (DISCARD); else jump_to_top_level (FORCE_EOF); } /* Don't split words marked W_NOSPLIT. */ if (expanded_something && (tlist->word->flags & W_NOSPLIT) == 0) { temp_list = word_list_split (expanded); dispose_words (expanded); } else { /* If no parameter expansion, command substitution, process substitution, or arithmetic substitution took place, then do not do word splitting. We still have to remove quoted null characters from the result. */ word_list_remove_quoted_nulls (expanded); temp_list = expanded; } expanded = REVERSE_LIST (temp_list, WORD_LIST *); new_list = (WORD_LIST *)list_append (expanded, new_list); } if (orig_list) dispose_words (orig_list); if (new_list) new_list = REVERSE_LIST (new_list, WORD_LIST *); return (new_list); } /* The workhorse for expand_words () and expand_words_no_vars (). First arg is LIST, a WORD_LIST of words. Second arg EFLAGS is a flags word controlling which expansions are performed. This does all of the substitutions: brace expansion, tilde expansion, parameter expansion, command substitution, arithmetic expansion, process substitution, word splitting, and pathname expansion, according to the bits set in EFLAGS. Words with the W_QUOTED or W_NOSPLIT bits set, or for which no expansion is done, do not undergo word splitting. Words with the W_NOGLOB bit set do not undergo pathname expansion. */ static WORD_LIST * expand_word_list_internal (list, eflags) WORD_LIST *list; int eflags; { WORD_LIST *new_list, *temp_list; int tint; if (list == 0) return ((WORD_LIST *)NULL); garglist = new_list = copy_word_list (list); if (eflags & WEXP_VARASSIGN) { garglist = new_list = separate_out_assignments (new_list); if (new_list == 0) { if (subst_assign_varlist) { /* All the words were variable assignments, so they are placed into the shell's environment. */ for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) { this_command_name = (char *)NULL; /* no arithmetic errors */ tint = do_assignment (temp_list->word->word); /* Variable assignment errors in non-interactive shells running in Posix.2 mode cause the shell to exit. */ if (tint == 0) { last_command_exit_value = EXECUTION_FAILURE; if (interactive_shell == 0 && posixly_correct) jump_to_top_level (FORCE_EOF); else jump_to_top_level (DISCARD); } } dispose_words (subst_assign_varlist); subst_assign_varlist = (WORD_LIST *)NULL; } return ((WORD_LIST *)NULL); } } /* Begin expanding the words that remain. The expansions take place on things that aren't really variable assignments. */ #if defined (BRACE_EXPANSION) /* Do brace expansion on this word if there are any brace characters in the string. */ if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list) new_list = brace_expand_word_list (new_list, eflags); #endif /* BRACE_EXPANSION */ /* Perform the `normal' shell expansions: tilde expansion, parameter and variable substitution, command substitution, arithmetic expansion, and word splitting. */ new_list = shell_expand_word_list (new_list, eflags); /* Okay, we're almost done. Now let's just do some filename globbing. */ if (new_list) { if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0) /* Glob expand the word list unless globbing has been disabled. */ new_list = glob_expand_word_list (new_list, eflags); else /* Dequote the words, because we're not performing globbing. */ new_list = dequote_list (new_list); } if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist) { sh_assign_func_t *assign_func; /* If the remainder of the words expand to nothing, Posix.2 requires that the variable and environment assignments affect the shell's environment. */ assign_func = new_list ? assign_in_env : do_assignment; for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next) { this_command_name = (char *)NULL; tint = (*assign_func) (temp_list->word->word); /* Variable assignment errors in non-interactive shells running in Posix.2 mode cause the shell to exit. */ if (tint == 0 && assign_func == do_assignment) { last_command_exit_value = EXECUTION_FAILURE; if (interactive_shell == 0 && posixly_correct) jump_to_top_level (FORCE_EOF); else jump_to_top_level (DISCARD); } } dispose_words (subst_assign_varlist); subst_assign_varlist = (WORD_LIST *)NULL; } #if 0 tint = list_length (new_list) + 1; RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16); for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next) glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0'; glob_argv_flags[tint] = '\0'; #endif return (new_list); } /* GNU test program (ksb and mjb) */ /* Modified to run with the GNU shell Apr 25, 1988 by bfox. */ /* Copyright (C) 1987, 1988, 1989, 1990, 1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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. */ /* Define PATTERN_MATCHING to get the csh-like =~ and !~ pattern-matching binary operators. */ /* #define PATTERN_MATCHING */ #if defined (HAVE_CONFIG_H) # include #endif #include #include "bashtypes.h" #if !defined (HAVE_LIMITS_H) # include #endif #if defined (HAVE_UNISTD_H) # include #endif #include #if !defined (errno) extern int errno; #endif /* !errno */ #if !defined (_POSIX_VERSION) # include #endif /* !_POSIX_VERSION */ #include "posixstat.h" #include "filecntl.h" #include "shell.h" #include "pathexp.h" #include "test.h" #include "builtins/common.h" #include #if !defined (STRLEN) # define STRLEN(s) ((s)[0] ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0) #endif #if !defined (STREQ) # define STREQ(a, b) ((a)[0] == (b)[0] && strcmp (a, b) == 0) #endif /* !STREQ */ #if !defined (R_OK) #define R_OK 4 #define W_OK 2 #define X_OK 1 #define F_OK 0 #endif /* R_OK */ #define EQ 0 #define NE 1 #define LT 2 #define GT 3 #define LE 4 #define GE 5 #define NT 0 #define OT 1 #define EF 2 /* The following few defines control the truth and false output of each stage. TRUE and FALSE are what we use to compute the final output value. SHELL_BOOLEAN is the form which returns truth or falseness in shell terms. Default is TRUE = 1, FALSE = 0, SHELL_BOOLEAN = (!value). */ #define TRUE 1 #define FALSE 0 #define SHELL_BOOLEAN(value) (!(value)) #define TEST_ERREXIT_STATUS 2 static procenv_t test_exit_buf; static int test_error_return; #define test_exit(val) \ do { test_error_return = val; longjmp (test_exit_buf, 1); } while (0) /* We have to use access(2) for machines running AFS, because it's not a Unix file system. This may produce incorrect answers for non-AFS files. I hate AFS. */ #if defined (AFS) # define EACCESS(path, mode) access(path, mode) #else # define EACCESS(path, mode) test_eaccess(path, mode) #endif /* AFS */ static int pos; /* The offset of the current argument in ARGV. */ static int argc; /* The number of arguments present in ARGV. */ static char **argv; /* The argument list. */ static int noeval; static void test_syntax_error __P((char *, char *)) __attribute__((__noreturn__)); static void beyond __P((void)) __attribute__((__noreturn__)); static void integer_expected_error __P((char *)) __attribute__((__noreturn__)); static int test_stat __P((char *, struct stat *)); static int unary_operator __P((void)); static int binary_operator __P((void)); static int two_arguments __P((void)); static int three_arguments __P((void)); static int posixtest __P((void)); static int expr __P((void)); static int term __P((void)); static int and __P((void)); static int or __P((void)); static int filecomp __P((char *, char *, int)); static int arithcomp __P((char *, char *, int, int)); static int patcomp __P((char *, char *, int)); static void test_syntax_error (format, arg) char *format, *arg; { if (interactive_shell == 0) fprintf (stderr, "%s: ", get_name_for_error ()); fprintf (stderr, "%s: ", argv[0]); fprintf (stderr, format, arg); fprintf (stderr, "\n"); fflush (stderr); test_exit (TEST_ERREXIT_STATUS); } /* * beyond - call when we're beyond the end of the argument list (an * error condition) */ static void beyond () { test_syntax_error ("argument expected", (char *)NULL); } /* Syntax error for when an integer argument was expected, but something else was found. */ static void integer_expected_error (pch) char *pch; { test_syntax_error ("%s: integer expression expected", pch); } /* A wrapper for stat () which disallows pathnames that are empty strings and handles /dev/fd emulation on systems that don't have it. */ static int test_stat (path, finfo) char *path; struct stat *finfo; { if (*path == '\0') { errno = ENOENT; return (-1); } if (path[0] == '/' && path[1] == 'd' && strncmp (path, "/dev/fd/", 8) == 0) { #if !defined (HAVE_DEV_FD) long fd; int r; if (legal_number (path + 8, &fd) && fd == (int)fd) { r = fstat ((int)fd, finfo); if (r == 0 || errno != EBADF) return (r); } errno = ENOENT; return (-1); #else /* If HAVE_DEV_FD is defined, DEV_FD_PREFIX is defined also, and has a trailing slash. Make sure /dev/fd/xx really uses DEV_FD_PREFIX/xx. On most systems, with the notable exception of linux, this is effectively a no-op. */ char pbuf[32]; strcpy (pbuf, DEV_FD_PREFIX); strcat (pbuf, path + 8); return (stat (pbuf, finfo)); #endif /* !HAVE_DEV_FD */ } #if !defined (HAVE_DEV_STDIN) else if (STREQN (path, "/dev/std", 8)) { if (STREQ (path+8, "in")) return (fstat (0, finfo)); else if (STREQ (path+8, "out")) return (fstat (1, finfo)); else if (STREQ (path+8, "err")) return (fstat (2, finfo)); else return (stat (path, finfo)); } #endif /* !HAVE_DEV_STDIN */ return (stat (path, finfo)); } /* Do the same thing access(2) does, but use the effective uid and gid, and don't make the mistake of telling root that any file is executable. */ int test_eaccess (path, mode) char *path; int mode; { struct stat st; if (test_stat (path, &st) < 0) return (-1); if (current_user.euid == 0) { /* Root can read or write any file. */ if (mode != X_OK) return (0); /* Root can execute any file that has any one of the execute bits set. */ if (st.st_mode & S_IXUGO) return (0); } if (st.st_uid == current_user.euid) /* owner */ mode <<= 6; else if (group_member (st.st_gid)) mode <<= 3; if (st.st_mode & mode) return (0); errno = EACCES; return (-1); } /* Increment our position in the argument list. Check that we're not past the end of the argument list. This check is supressed if the argument is FALSE. Made a macro for efficiency. */ #define advance(f) do { ++pos; if (f && pos >= argc) beyond (); } while (0) #define unary_advance() do { advance (1); ++pos; } while (0) /* * expr: * or */ static int expr () { if (pos >= argc) beyond (); return (FALSE ^ or ()); /* Same with this. */ } /* * or: * and * and '-o' or */ static int or () { int value, v2; value = and (); if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'o' && !argv[pos][2]) { advance (0); v2 = or (); return (value || v2); } return (value); } /* * and: * term * term '-a' and */ static int and () { int value, v2; value = term (); if (pos < argc && argv[pos][0] == '-' && argv[pos][1] == 'a' && !argv[pos][2]) { advance (0); v2 = and (); return (value && v2); } return (value); } /* * term - parse a term and return 1 or 0 depending on whether the term * evaluates to true or false, respectively. * * term ::= * '-'('a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'k'|'p'|'r'|'s'|'u'|'w'|'x') filename * '-'('G'|'L'|'O'|'S'|'N') filename * '-t' [int] * '-'('z'|'n') string * '-o' option * string * string ('!='|'='|'==') string * '-'(eq|ne|le|lt|ge|gt) * file '-'(nt|ot|ef) file * '(' ')' * int ::= * positive and negative integers */ static int term () { int value; if (pos >= argc) beyond (); /* Deal with leading `not's. */ if (argv[pos][0] == '!' && argv[pos][1] == '\0') { value = 0; while (pos < argc && argv[pos][0] == '!' && argv[pos][1] == '\0') { advance (1); value = 1 - value; } return (value ? !term() : term()); } /* A paren-bracketed argument. */ if (argv[pos][0] == '(' && argv[pos][1] == '\0') { advance (1); value = expr (); if (argv[pos] == 0) test_syntax_error ("`)' expected", (char *)NULL); else if (argv[pos][0] != ')' || argv[pos][1]) test_syntax_error ("`)' expected, found %s", argv[pos]); advance (0); return (value); } /* are there enough arguments left that this could be dyadic? */ if ((pos + 3 <= argc) && test_binop (argv[pos + 1])) value = binary_operator (); /* Might be a switch type argument */ else if (argv[pos][0] == '-' && argv[pos][2] == '\0') { if (test_unop (argv[pos])) value = unary_operator (); else test_syntax_error ("%s: unary operator expected", argv[pos]); } else { value = argv[pos][0] != '\0'; advance (0); } return (value); } static int filecomp (s, t, op) char *s, *t; int op; { struct stat st1, st2; if (test_stat (s, &st1) < 0) { st1.st_mtime = 0; if (op == EF) return (FALSE); } if (test_stat (t, &st2) < 0) { st2.st_mtime = 0; if (op == EF) return (FALSE); } switch (op) { case OT: return (st1.st_mtime < st2.st_mtime); case NT: return (st1.st_mtime > st2.st_mtime); case EF: return ((st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino)); } return (FALSE); } static int arithcomp (s, t, op, flags) char *s, *t; int op, flags; { long l, r; int expok; if (flags & TEST_ARITHEXP) { l = evalexp (s, &expok); if (expok == 0) return (FALSE); /* should probably longjmp here */ r = evalexp (t, &expok); if (expok == 0) return (FALSE); /* ditto */ } else { if (legal_number (s, &l) == 0) integer_expected_error (s); if (legal_number (t, &r) == 0) integer_expected_error (t); } switch (op) { case EQ: return (l == r); case NE: return (l != r); case LT: return (l < r); case GT: return (l > r); case LE: return (l <= r); case GE: return (l >= r); } return (FALSE); } static int patcomp (string, pat, op) char *string, *pat; int op; { int m; m = strmatch (pat, string, FNMATCH_EXTFLAG); return ((op == EQ) ? (m == 0) : (m != 0)); } int binary_test (op, arg1, arg2, flags) char *op, *arg1, *arg2; int flags; { int patmatch; patmatch = (flags & TEST_PATMATCH); if (op[0] == '=' && (op[1] == '\0' || (op[1] == '=' && op[2] == '\0'))) return (patmatch ? patcomp (arg1, arg2, EQ) : STREQ (arg1, arg2)); else if ((op[0] == '>' || op[0] == '<') && op[1] == '\0') return ((op[0] == '>') ? (strcmp (arg1, arg2) > 0) : (strcmp (arg1, arg2) < 0)); else if (op[0] == '!' && op[1] == '=' && op[2] == '\0') return (patmatch ? patcomp (arg1, arg2, NE) : (STREQ (arg1, arg2) == 0)); else if (op[2] == 't') { switch (op[1]) { case 'n': return (filecomp (arg1, arg2, NT)); /* -nt */ case 'o': return (filecomp (arg1, arg2, OT)); /* -ot */ case 'l': return (arithcomp (arg1, arg2, LT, flags)); /* -lt */ case 'g': return (arithcomp (arg1, arg2, GT, flags)); /* -gt */ } } else if (op[1] == 'e') { switch (op[2]) { case 'f': return (filecomp (arg1, arg2, EF)); /* -ef */ case 'q': return (arithcomp (arg1, arg2, EQ, flags)); /* -eq */ } } else if (op[2] == 'e') { switch (op[1]) { case 'n': return (arithcomp (arg1, arg2, NE, flags)); /* -ne */ case 'g': return (arithcomp (arg1, arg2, GE, flags)); /* -ge */ case 'l': return (arithcomp (arg1, arg2, LE, flags)); /* -le */ } } return (FALSE); /* should never get here */ } static int binary_operator () { int value; char *w; w = argv[pos + 1]; if ((w[0] == '=' && (w[1] == '\0' || (w[1] == '=' && w[2] == '\0'))) || /* =, == */ ((w[0] == '>' || w[0] == '<') && w[1] == '\0') || /* <, > */ (w[0] == '!' && w[1] == '=' && w[2] == '\0')) /* != */ { value = binary_test (w, argv[pos], argv[pos + 2], 0); pos += 3; return (value); } #if defined (PATTERN_MATCHING) if ((w[0] == '=' || w[0] == '!') && w[1] == '~' && w[2] == '\0') { value = patcomp (argv[pos], argv[pos + 2], w[0] == '=' ? EQ : NE); pos += 3; return (value); } #endif if ((w[0] != '-' || w[3] != '\0') || test_binop (w) == 0) { test_syntax_error ("%s: binary operator expected", w); /* NOTREACHED */ return (FALSE); } value = binary_test (w, argv[pos], argv[pos + 2], 0); pos += 3; return value; } static int unary_operator () { char *op; long r; op = argv[pos]; if (test_unop (op) == 0) return (FALSE); /* the only tricky case is `-t', which may or may not take an argument. */ if (op[1] == 't') { advance (0); if (pos < argc) { if (legal_number (argv[pos], &r)) { advance (0); return (unary_test (op, argv[pos - 1])); } else return (FALSE); } else return (unary_test (op, "1")); } /* All of the unary operators take an argument, so we first call unary_advance (), which checks to make sure that there is an argument, and then advances pos right past it. This means that pos - 1 is the location of the argument. */ unary_advance (); return (unary_test (op, argv[pos - 1])); } int unary_test (op, arg) char *op, *arg; { long r; struct stat stat_buf; switch (op[1]) { case 'a': /* file exists in the file system? */ case 'e': return (test_stat (arg, &stat_buf) == 0); case 'r': /* file is readable? */ return (EACCESS (arg, R_OK) == 0); case 'w': /* File is writeable? */ return (EACCESS (arg, W_OK) == 0); case 'x': /* File is executable? */ return (EACCESS (arg, X_OK) == 0); case 'O': /* File is owned by you? */ return (test_stat (arg, &stat_buf) == 0 && (uid_t) current_user.euid == (uid_t) stat_buf.st_uid); case 'G': /* File is owned by your group? */ return (test_stat (arg, &stat_buf) == 0 && (gid_t) current_user.egid == (gid_t) stat_buf.st_gid); case 'N': return (test_stat (arg, &stat_buf) == 0 && stat_buf.st_atime <= stat_buf.st_mtime); case 'f': /* File is a file? */ if (test_stat (arg, &stat_buf) < 0) return (FALSE); /* -f is true if the given file exists and is a regular file. */ #if defined (S_IFMT) return (S_ISREG (stat_buf.st_mode) || (stat_buf.st_mode & S_IFMT) == 0); #else return (S_ISREG (stat_buf.st_mode)); #endif /* !S_IFMT */ case 'd': /* File is a directory? */ return (test_stat (arg, &stat_buf) == 0 && (S_ISDIR (stat_buf.st_mode))); case 's': /* File has something in it? */ return (test_stat (arg, &stat_buf) == 0 && stat_buf.st_size > (off_t) 0); case 'S': /* File is a socket? */ #if !defined (S_ISSOCK) return (FALSE); #else return (test_stat (arg, &stat_buf) == 0 && S_ISSOCK (stat_buf.st_mode)); #endif /* S_ISSOCK */ case 'c': /* File is character special? */ return (test_stat (arg, &stat_buf) == 0 && S_ISCHR (stat_buf.st_mode)); case 'b': /* File is block special? */ return (test_stat (arg, &stat_buf) == 0 && S_ISBLK (stat_buf.st_mode)); case 'p': /* File is a named pipe? */ #ifndef S_ISFIFO return (FALSE); #else return (test_stat (arg, &stat_buf) == 0 && S_ISFIFO (stat_buf.st_mode)); #endif /* S_ISFIFO */ case 'L': /* Same as -h */ case 'h': /* File is a symbolic link? */ #if !defined (S_ISLNK) || !defined (HAVE_LSTAT) return (FALSE); #else return ((arg[0] != '\0') && (lstat (arg, &stat_buf) == 0) && S_ISLNK (stat_buf.st_mode)); #endif /* S_IFLNK && HAVE_LSTAT */ case 'u': /* File is setuid? */ return (test_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISUID) != 0); case 'g': /* File is setgid? */ return (test_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISGID) != 0); case 'k': /* File has sticky bit set? */ #if !defined (S_ISVTX) /* This is not Posix, and is not defined on some Posix systems. */ return (FALSE); #else return (test_stat (arg, &stat_buf) == 0 && (stat_buf.st_mode & S_ISVTX) != 0); #endif case 't': /* File fd is a terminal? */ if (legal_number (arg, &r) == 0) return (FALSE); return ((r == (int)r) && isatty ((int)r)); case 'n': /* True if arg has some length. */ return (arg[0] != '\0'); case 'z': /* True if arg has no length. */ return (arg[0] == '\0'); case 'o': /* True if option `arg' is set. */ return (minus_o_option_value (arg) == 1); } /* We can't actually get here, but this shuts up gcc. */ return (FALSE); } /* Return TRUE if OP is one of the test command's binary operators. */ int test_binop (op) char *op; { if (op[0] == '=' && op[1] == '\0') return (1); /* '=' */ else if ((op[0] == '<' || op[0] == '>') && op[1] == '\0') /* string <, > */ return (1); else if ((op[0] == '=' || op[0] == '!') && op[1] == '=' && op[2] == '\0') return (1); /* `==' and `!=' */ #if defined (PATTERN_MATCHING) else if (op[2] == '\0' && op[1] == '~' && (op[0] == '=' || op[0] == '!')) return (1); #endif else if (op[0] != '-' || op[2] == '\0' || op[3] != '\0') return (0); else { if (op[2] == 't') switch (op[1]) { case 'n': /* -nt */ case 'o': /* -ot */ case 'l': /* -lt */ case 'g': /* -gt */ return (1); default: return (0); } else if (op[1] == 'e') switch (op[2]) { case 'q': /* -eq */ case 'f': /* -ef */ return (1); default: return (0); } else if (op[2] == 'e') switch (op[1]) { case 'n': /* -ne */ case 'g': /* -ge */ case 'l': /* -le */ return (1); default: return (0); } else return (0); } } /* Return non-zero if OP is one of the test command's unary operators. */ int test_unop (op) char *op; { if (op[0] != '-') return (0); switch (op[1]) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'k': case 'n': case 'o': case 'p': case 'r': case 's': case 't': case 'u': case 'w': case 'x': case 'z': case 'G': case 'L': case 'O': case 'S': case 'N': return (1); } return (0); } static int two_arguments () { if (argv[pos][0] == '!' && argv[pos][1] == '\0') return (argv[pos + 1][0] == '\0'); else if (argv[pos][0] == '-' && argv[pos][2] == '\0') { if (test_unop (argv[pos])) return (unary_operator ()); else test_syntax_error ("%s: unary operator expected", argv[pos]); } else test_syntax_error ("%s: unary operator expected", argv[pos]); return (0); } #define ANDOR(s) (s[0] == '-' && !s[2] && (s[1] == 'a' || s[1] == 'o')) /* This could be augmented to handle `-t' as equivalent to `-t 1', but POSIX requires that `-t' be given an argument. */ #define ONE_ARG_TEST(s) ((s)[0] != '\0') static int three_arguments () { int value; if (test_binop (argv[pos+1])) { value = binary_operator (); pos = argc; } else if (ANDOR (argv[pos+1])) { if (argv[pos+1][1] == 'a') value = ONE_ARG_TEST(argv[pos]) && ONE_ARG_TEST(argv[pos+2]); else value = ONE_ARG_TEST(argv[pos]) || ONE_ARG_TEST(argv[pos+2]); pos = argc; } else if (argv[pos][0] == '!' && argv[pos][1] == '\0') { advance (1); value = !two_arguments (); } else if (argv[pos][0] == '(' && argv[pos+2][0] == ')') { value = ONE_ARG_TEST(argv[pos+1]); pos = argc; } else test_syntax_error ("%s: binary operator expected", argv[pos+1]); return (value); } /* This is an implementation of a Posix.2 proposal by David Korn. */ static int posixtest () { int value; switch (argc - 1) /* one extra passed in */ { case 0: value = FALSE; pos = argc; break; case 1: value = ONE_ARG_TEST(argv[1]); pos = argc; break; case 2: value = two_arguments (); pos = argc; break; case 3: value = three_arguments (); break; case 4: if (argv[pos][0] == '!' && argv[pos][1] == '\0') { advance (1); value = !three_arguments (); break; } /* FALLTHROUGH */ default: value = expr (); } return (value); } /* * [: * '[' expr ']' * test: * test expr */ int test_command (margc, margv) int margc; char **margv; { int value; int code; USE_VAR(margc); code = setjmp (test_exit_buf); if (code) return (test_error_return); argv = margv; if (margv[0] && margv[0][0] == '[' && margv[0][1] == '\0') { --margc; if (margv[margc] && (margv[margc][0] != ']' || margv[margc][1])) test_syntax_error ("missing `]'", (char *)NULL); if (margc < 2) test_exit (SHELL_BOOLEAN (FALSE)); } argc = margc; pos = 1; if (pos >= argc) test_exit (SHELL_BOOLEAN (FALSE)); noeval = 0; value = posixtest (); if (pos != argc) test_syntax_error ("too many arguments", (char *)NULL); test_exit (SHELL_BOOLEAN (value)); } /* trap.c -- Not the trap command, but useful functions for manipulating those objects. The trap command is in builtins/trap.def. */ /* Copyright (C) 1987, 1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 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 (HAVE_UNISTD_H) # include #endif #include "bashtypes.h" #include "bashansi.h" #include #include #include "trap.h" #include "shell.h" #include "input.h" /* for save_token_state, restore_token_state */ #include "signames.h" #include "builtins/common.h" #ifndef errno extern int errno; #endif /* Flags which describe the current handling state of a signal. */ #define SIG_INHERITED 0x0 /* Value inherited from parent. */ #define SIG_TRAPPED 0x1 /* Currently trapped. */ #define SIG_HARD_IGNORE 0x2 /* Signal was ignored on shell entry. */ #define SIG_SPECIAL 0x4 /* Treat this signal specially. */ #define SIG_NO_TRAP 0x8 /* Signal cannot be trapped. */ #define SIG_INPROGRESS 0x10 /* Signal handler currently executing. */ #define SIG_CHANGED 0x20 /* Trap value changed in trap handler. */ #define SIG_IGNORED 0x40 /* The signal is currently being ignored. */ #define SPECIAL_TRAP(s) ((s) == EXIT_TRAP || (s) == DEBUG_TRAP || (s) == ERROR_TRAP) /* An array of such flags, one for each signal, describing what the shell will do with a signal. DEBUG_TRAP == NSIG; some code below assumes this. */ static int sigmodes[BASH_NSIG]; static void free_trap_command __P((int)); static void change_signal __P((int, char *)); static void get_original_signal __P((int)); static void _run_trap_internal __P((int, char *)); static void reset_signal __P((int)); static void restore_signal __P((int)); static void reset_or_restore_signal_handlers __P((sh_resetsig_func_t *)); /* Variables used here but defined in other files. */ extern int interrupt_immediately; extern int last_command_exit_value; extern int line_number; /* The list of things to do originally, before we started trapping. */ SigHandler *original_signals[NSIG]; /* For each signal, a slot for a string, which is a command to be executed when that signal is recieved. The slot can also contain DEFAULT_SIG, which means do whatever you were going to do before you were so rudely interrupted, or IGNORE_SIG, which says ignore this signal. */ char *trap_list[BASH_NSIG]; /* A bitmap of signals received for which we have trap handlers. */ int pending_traps[NSIG]; /* Set to the number of the signal we're running the trap for + 1. Used in execute_cmd.c and builtins/common.c to clean up when parse_and_execute does not return normally after executing the trap command (e.g., when `return' is executed in the trap command). */ int running_trap; /* The value of line_number when the trap started executing, since parse_and_execute resets it to 1 and the trap command might want it. */ int trap_line_number; /* A value which can never be the target of a trap handler. */ #define IMPOSSIBLE_TRAP_HANDLER (SigHandler *)initialize_traps void initialize_traps () { register int i; trap_list[EXIT_TRAP] = trap_list[DEBUG_TRAP] = trap_list[ERROR_TRAP] = (char *)NULL; sigmodes[EXIT_TRAP] = sigmodes[DEBUG_TRAP] = sigmodes[ERROR_TRAP] = SIG_INHERITED; original_signals[EXIT_TRAP] = IMPOSSIBLE_TRAP_HANDLER; for (i = 1; i < NSIG; i++) { pending_traps[i] = 0; trap_list[i] = (char *)DEFAULT_SIG; sigmodes[i] = SIG_INHERITED; original_signals[i] = IMPOSSIBLE_TRAP_HANDLER; } /* Show which signals are treated specially by the shell. */ #if defined (SIGCHLD) original_signals[SIGCHLD] = (SigHandler *) set_signal_handler (SIGCHLD, SIG_DFL); set_signal_handler (SIGCHLD, original_signals[SIGCHLD]); sigmodes[SIGCHLD] |= (SIG_SPECIAL | SIG_NO_TRAP); #endif /* SIGCHLD */ original_signals[SIGINT] = (SigHandler *) set_signal_handler (SIGINT, SIG_DFL); set_signal_handler (SIGINT, original_signals[SIGINT]); sigmodes[SIGINT] |= SIG_SPECIAL; #if defined (__BEOS__) /* BeOS sets SIGINT to SIG_IGN! */ original_signals[SIGINT] = SIG_DFL; #endif original_signals[SIGQUIT] = (SigHandler *) set_signal_handler (SIGQUIT, SIG_DFL); set_signal_handler (SIGQUIT, original_signals[SIGQUIT]); sigmodes[SIGQUIT] |= SIG_SPECIAL; if (interactive) { original_signals[SIGTERM] = (SigHandler *)set_signal_handler (SIGTERM, SIG_DFL); set_signal_handler (SIGTERM, original_signals[SIGTERM]); sigmodes[SIGTERM] |= SIG_SPECIAL; } } #ifdef INCLUDE_UNUSED /* Return a printable representation of the trap handler for SIG. */ static char * trap_handler_string (sig) int sig; { if (trap_list[sig] == (char *)DEFAULT_SIG) return "DEFAULT_SIG"; else if (trap_list[sig] == (char *)IGNORE_SIG) return "IGNORE_SIG"; else if (trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER) return "IMPOSSIBLE_TRAP_HANDLER"; else if (trap_list[sig]) return trap_list[sig]; else return "NULL"; } #endif /* Return the print name of this signal. */ char * signal_name (sig) int sig; { char *ret; /* on cygwin32, signal_names[sig] could be null */ ret = (sig > NSIG || sig < 0) ? "bad signal number" : signal_names[sig]; if (ret == NULL) ret = "unrecognized signal number"; return ret; } /* Turn a string into a signal number, or a number into a signal number. If STRING is "2", "SIGINT", or "INT", then (int)2 is returned. Return NO_SIG if STRING doesn't contain a valid signal descriptor. */ int decode_signal (string) char *string; { long sig; if (legal_number (string, &sig)) return ((sig >= 0 && sig < NSIG) ? (int)sig : NO_SIG); /* A leading `SIG' may be omitted. */ for (sig = 0; sig < BASH_NSIG; sig++) { if (signal_names[sig] == 0 || signal_names[sig][0] == '\0') continue; if (strcasecmp (string, signal_names[sig]) == 0 || (STREQN (signal_names[sig], "SIG", 3) && strcasecmp (string, &(signal_names[sig])[3]) == 0)) return ((int)sig); } return (NO_SIG); } /* Non-zero when we catch a trapped signal. */ static int catch_flag; void run_pending_traps () { register int sig; int old_exit_value, *token_state; if (catch_flag == 0) /* simple optimization */ return; catch_flag = 0; /* Preserve $? when running trap. */ old_exit_value = last_command_exit_value; for (sig = 1; sig < NSIG; sig++) { /* XXX this could be made into a counter by using while (pending_traps[sig]--) instead of the if statement. */ if (pending_traps[sig]) { #if defined (HAVE_POSIX_SIGNALS) sigset_t set, oset; sigemptyset (&set); sigemptyset (&oset); sigaddset (&set, sig); sigprocmask (SIG_BLOCK, &set, &oset); #else # if defined (HAVE_BSD_SIGNALS) int oldmask = sigblock (sigmask (sig)); # endif #endif /* HAVE_POSIX_SIGNALS */ if (sig == SIGINT) { run_interrupt_trap (); CLRINTERRUPT; } else if (trap_list[sig] == (char *)DEFAULT_SIG || trap_list[sig] == (char *)IGNORE_SIG || trap_list[sig] == (char *)IMPOSSIBLE_TRAP_HANDLER) { /* This is possible due to a race condition. Say a bash process has SIGTERM trapped. A subshell is spawned using { list; } & and the parent does something and kills the subshell with SIGTERM. It's possible for the subshell to set pending_traps[SIGTERM] to 1 before the code in execute_cmd.c eventually calls restore_original_signals to reset the SIGTERM signal handler in the subshell. The next time run_pending_traps is called, pending_traps[SIGTERM] will be 1, but the trap handler in trap_list[SIGTERM] will be invalid (probably DEFAULT_SIG, but it could be IGNORE_SIG). Unless we catch this, the subshell will dump core when trap_list[SIGTERM] == DEFAULT_SIG, because DEFAULT_SIG is usually 0x0. */ internal_warning ("run_pending_traps: bad value in trap_list[%d]: %p", sig, trap_list[sig]); if (trap_list[sig] == (char *)DEFAULT_SIG) { internal_warning ("run_pending_traps: signal handler is SIG_DFL, resending %d (%s) to myself", sig, signal_name (sig)); kill (getpid (), sig); } } else { token_state = save_token_state (); parse_and_execute (savestring (trap_list[sig]), "trap", SEVAL_NONINT|SEVAL_NOHIST); restore_token_state (token_state); free (token_state); } pending_traps[sig] = 0; #if defined (HAVE_POSIX_SIGNALS) sigprocmask (SIG_SETMASK, &oset, (sigset_t *)NULL); #else # if defined (HAVE_BSD_SIGNALS) sigsetmask (oldmask); # endif #endif /* POSIX_VERSION */ } } last_command_exit_value = old_exit_value; } sighandler trap_handler (sig) int sig; { int oerrno; if ((sig >= NSIG) || (trap_list[sig] == (char *)DEFAULT_SIG) || (trap_list[sig] == (char *)IGNORE_SIG)) programming_error ("trap_handler: bad signal %d", sig); else { oerrno = errno; #if defined (MUST_REINSTALL_SIGHANDLERS) set_signal_handler (sig, trap_handler); #endif /* MUST_REINSTALL_SIGHANDLERS */ catch_flag = 1; pending_traps[sig]++; if (interrupt_immediately) run_pending_traps (); errno = oerrno; } SIGRETURN (0); } #if defined (JOB_CONTROL) && defined (SIGCHLD) #ifdef INCLUDE_UNUSED /* Make COMMAND_STRING be executed when SIGCHLD is caught. */ void set_sigchld_trap (command_string) char *command_string; { set_signal (SIGCHLD, command_string); } #endif /* Make COMMAND_STRING be executed when SIGCHLD is caught iff the current SIGCHLD trap handler is DEFAULT_SIG. */ void maybe_set_sigchld_trap (command_string) char *command_string; { if ((sigmodes[SIGCHLD] & SIG_TRAPPED) == 0) set_signal (SIGCHLD, command_string); } #endif /* JOB_CONTROL && SIGCHLD */ void set_debug_trap (command) char *command; { set_signal (DEBUG_TRAP, command); } void set_error_trap (command) char *command; { set_signal (ERROR_TRAP, command); } #ifdef INCLUDE_UNUSED void set_sigint_trap (command) char *command; { set_signal (SIGINT, command); } #endif /* Reset the SIGINT handler so that subshells that are doing `shellsy' things, like waiting for command substitution or executing commands in explicit subshells ( ( cmd ) ), can catch interrupts properly. */ SigHandler * set_sigint_handler () { if (sigmodes[SIGINT] & SIG_HARD_IGNORE) return ((SigHandler *)SIG_IGN); else if (sigmodes[SIGINT] & SIG_IGNORED) return ((SigHandler *)set_signal_handler (SIGINT, SIG_IGN)); /* XXX */ else if (sigmodes[SIGINT] & SIG_TRAPPED) return ((SigHandler *)set_signal_handler (SIGINT, trap_handler)); /* The signal is not trapped, so set the handler to the shell's special interrupt handler. */ else if (interactive) /* XXX - was interactive_shell */ return (set_signal_handler (SIGINT, sigint_sighandler)); else return (set_signal_handler (SIGINT, termination_unwind_protect)); } /* Return the correct handler for signal SIG according to the values in sigmodes[SIG]. */ SigHandler * trap_to_sighandler (sig) int sig; { if (sigmodes[sig] & (SIG_IGNORED|SIG_HARD_IGNORE)) return (SIG_IGN); else if (sigmodes[sig] & SIG_TRAPPED) return (trap_handler); else return (SIG_DFL); } /* Set SIG to call STRING as a command. */ void set_signal (sig, string) int sig; char *string; { if (SPECIAL_TRAP (sig)) { change_signal (sig, savestring (string)); if (sig == EXIT_TRAP && interactive == 0) initialize_terminating_signals (); return; } /* A signal ignored on entry to the shell cannot be trapped or reset, but no error is reported when attempting to do so. -- Posix.2 */ if (sigmodes[sig] & SIG_HARD_IGNORE) return; /* Make sure we have original_signals[sig] if the signal has not yet been trapped. */ if ((sigmodes[sig] & SIG_TRAPPED) == 0) { /* If we aren't sure of the original value, check it. */ if (original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) { original_signals[sig] = (SigHandler *)set_signal_handler (sig, SIG_DFL); set_signal_handler (sig, original_signals[sig]); } /* Signals ignored on entry to the shell cannot be trapped or reset. */ if (original_signals[sig] == SIG_IGN) { sigmodes[sig] |= SIG_HARD_IGNORE; return; } } /* Only change the system signal handler if SIG_NO_TRAP is not set. The trap command string is changed in either case. The shell signal handlers for SIGINT and SIGCHLD run the user specified traps in an environment in which it is safe to do so. */ if ((sigmodes[sig] & SIG_NO_TRAP) == 0) { set_signal_handler (sig, SIG_IGN); change_signal (sig, savestring (string)); set_signal_handler (sig, trap_handler); } else change_signal (sig, savestring (string)); } static void free_trap_command (sig) int sig; { if ((sigmodes[sig] & SIG_TRAPPED) && trap_list[sig] && (trap_list[sig] != (char *)IGNORE_SIG) && (trap_list[sig] != (char *)DEFAULT_SIG) && (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER)) free (trap_list[sig]); } /* If SIG has a string assigned to it, get rid of it. Then give it VALUE. */ static void change_signal (sig, value) int sig; char *value; { if ((sigmodes[sig] & SIG_INPROGRESS) == 0) free_trap_command (sig); trap_list[sig] = value; sigmodes[sig] |= SIG_TRAPPED; if (value == (char *)IGNORE_SIG) sigmodes[sig] |= SIG_IGNORED; else sigmodes[sig] &= ~SIG_IGNORED; if (sigmodes[sig] & SIG_INPROGRESS) sigmodes[sig] |= SIG_CHANGED; } #define GET_ORIGINAL_SIGNAL(sig) \ if (sig && sig < NSIG && original_signals[sig] == IMPOSSIBLE_TRAP_HANDLER) \ get_original_signal (sig) static void get_original_signal (sig) int sig; { /* If we aren't sure the of the original value, then get it. */ if (original_signals[sig] == (SigHandler *)IMPOSSIBLE_TRAP_HANDLER) { original_signals[sig] = (SigHandler *) set_signal_handler (sig, SIG_DFL); set_signal_handler (sig, original_signals[sig]); /* Signals ignored on entry to the shell cannot be trapped. */ if (original_signals[sig] == SIG_IGN) sigmodes[sig] |= SIG_HARD_IGNORE; } } /* Restore the default action for SIG; i.e., the action the shell would have taken before you used the trap command. This is called from trap_builtin (), which takes care to restore the handlers for the signals the shell treats specially. */ void restore_default_signal (sig) int sig; { if (SPECIAL_TRAP (sig)) { if ((sig != DEBUG_TRAP && sig != ERROR_TRAP) || (sigmodes[sig] & SIG_INPROGRESS) == 0) free_trap_command (sig); trap_list[sig] = (char *)NULL; sigmodes[sig] &= ~SIG_TRAPPED; if (sigmodes[sig] & SIG_INPROGRESS) sigmodes[sig] |= SIG_CHANGED; return; } GET_ORIGINAL_SIGNAL (sig); /* A signal ignored on entry to the shell cannot be trapped or reset, but no error is reported when attempting to do so. Thanks Posix.2. */ if (sigmodes[sig] & SIG_HARD_IGNORE) return; /* If we aren't trapping this signal, don't bother doing anything else. */ if ((sigmodes[sig] & SIG_TRAPPED) == 0) return; /* Only change the signal handler for SIG if it allows it. */ if ((sigmodes[sig] & SIG_NO_TRAP) == 0) set_signal_handler (sig, original_signals[sig]); /* Change the trap command in either case. */ change_signal (sig, (char *)DEFAULT_SIG); /* Mark the signal as no longer trapped. */ sigmodes[sig] &= ~SIG_TRAPPED; } /* Make this signal be ignored. */ void ignore_signal (sig) int sig; { if (SPECIAL_TRAP (sig) && ((sigmodes[sig] & SIG_IGNORED) == 0)) { change_signal (sig, (char *)IGNORE_SIG); return; } GET_ORIGINAL_SIGNAL (sig); /* A signal ignored on entry to the shell cannot be trapped or reset. No error is reported when the user attempts to do so. */ if (sigmodes[sig] & SIG_HARD_IGNORE) return; /* If already trapped and ignored, no change necessary. */ if (sigmodes[sig] & SIG_IGNORED) return; /* Only change the signal handler for SIG if it allows it. */ if ((sigmodes[sig] & SIG_NO_TRAP) == 0) set_signal_handler (sig, SIG_IGN); /* Change the trap command in either case. */ change_signal (sig, (char *)IGNORE_SIG); } /* Handle the calling of "trap 0". The only sticky situation is when the command to be executed includes an "exit". This is why we have to provide our own place for top_level to jump to. */ int run_exit_trap () { char *trap_command; int code, old_exit_value; old_exit_value = last_command_exit_value; /* Run the trap only if signal 0 is trapped and not ignored, and we are not currently running in the trap handler (call to exit in the list of commands given to trap 0). */ if ((sigmodes[EXIT_TRAP] & SIG_TRAPPED) && (sigmodes[EXIT_TRAP] & (SIG_IGNORED|SIG_INPROGRESS)) == 0) { trap_command = savestring (trap_list[EXIT_TRAP]); sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED; sigmodes[EXIT_TRAP] |= SIG_INPROGRESS; code = setjmp (top_level); if (code == 0) { reset_parser (); parse_and_execute (trap_command, "exit trap", SEVAL_NONINT|SEVAL_NOHIST); } else if (code == EXITPROG) return (last_command_exit_value); else return (old_exit_value); } return (old_exit_value); } void run_trap_cleanup (sig) int sig; { sigmodes[sig] &= ~(SIG_INPROGRESS|SIG_CHANGED); } /* Run a trap command for SIG. SIG is one of the signals the shell treats specially. */ static void _run_trap_internal (sig, tag) int sig; char *tag; { char *trap_command, *old_trap; int old_exit_value, *token_state; /* Run the trap only if SIG is trapped and not ignored, and we are not currently executing in the trap handler. */ if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0) && (trap_list[sig] != (char *)IMPOSSIBLE_TRAP_HANDLER) && ((sigmodes[sig] & SIG_INPROGRESS) == 0)) { old_trap = trap_list[sig]; sigmodes[sig] |= SIG_INPROGRESS; sigmodes[sig] &= ~SIG_CHANGED; /* just to be sure */ trap_command = savestring (old_trap); running_trap = sig + 1; old_exit_value = last_command_exit_value; /* Need to copy the value of line_number because parse_and_execute resets it to 1, and the trap command might want it. */ trap_line_number = line_number; token_state = save_token_state (); parse_and_execute (trap_command, tag, SEVAL_NONINT|SEVAL_NOHIST); restore_token_state (token_state); free (token_state); last_command_exit_value = old_exit_value; running_trap = 0; sigmodes[sig] &= ~SIG_INPROGRESS; if (sigmodes[sig] & SIG_CHANGED) { free (old_trap); sigmodes[sig] &= ~SIG_CHANGED; } } } void run_debug_trap () { if ((sigmodes[DEBUG_TRAP] & SIG_TRAPPED) && (sigmodes[DEBUG_TRAP] & SIG_INPROGRESS) == 0) _run_trap_internal (DEBUG_TRAP, "debug trap"); } void run_error_trap () { if ((sigmodes[ERROR_TRAP] & SIG_TRAPPED) && (sigmodes[ERROR_TRAP] & SIG_INPROGRESS) == 0) _run_trap_internal (ERROR_TRAP, "error trap"); } /* Run a trap set on SIGINT. This is called from throw_to_top_level (), and declared here to localize the trap functions. */ void run_interrupt_trap () { _run_trap_internal (SIGINT, "interrupt trap"); } #ifdef INCLUDE_UNUSED /* Free all the allocated strings in the list of traps and reset the trap values to the default. */ void free_trap_strings () { register int i; for (i = 0; i < BASH_NSIG; i++) { free_trap_command (i); trap_list[i] = (char *)DEFAULT_SIG; sigmodes[i] &= ~SIG_TRAPPED; } trap_list[DEBUG_TRAP] = trap_list[EXIT_TRAP] = trap_list[ERROR_TRAP] = (char *)NULL; } #endif /* Reset the handler for SIG to the original value. */ static void reset_signal (sig) int sig; { set_signal_handler (sig, original_signals[sig]); } /* Set the handler signal SIG to the original and free any trap command associated with it. */ static void restore_signal (sig) int sig; { set_signal_handler (sig, original_signals[sig]); change_signal (sig, (char *)DEFAULT_SIG); sigmodes[sig] &= ~SIG_TRAPPED; } static void reset_or_restore_signal_handlers (reset) sh_resetsig_func_t *reset; { register int i; /* Take care of the exit trap first */ if (sigmodes[EXIT_TRAP] & SIG_TRAPPED) { free_trap_command (EXIT_TRAP); trap_list[EXIT_TRAP] = (char *)NULL; sigmodes[EXIT_TRAP] &= ~SIG_TRAPPED; } for (i = 1; i < NSIG; i++) { if (sigmodes[i] & SIG_TRAPPED) { if (trap_list[i] == (char *)IGNORE_SIG) set_signal_handler (i, SIG_IGN); else (*reset) (i); } else if (sigmodes[i] & SIG_SPECIAL) (*reset) (i); } /* Command substitution and other child processes don't inherit the debug or error traps. */ sigmodes[DEBUG_TRAP] &= ~SIG_TRAPPED; sigmodes[ERROR_TRAP] &= ~SIG_TRAPPED; } /* Reset trapped signals to their original values, but don't free the trap strings. Called by the command substitution code. */ void reset_signal_handlers () { reset_or_restore_signal_handlers (reset_signal); } /* Reset all trapped signals to their original values. Signals set to be ignored with trap '' SIGNAL should be ignored, so we make sure that they are. Called by child processes after they are forked. */ void restore_original_signals () { reset_or_restore_signal_handlers (restore_signal); } /* If a trap handler exists for signal SIG, then call it; otherwise just return failure. */ int maybe_call_trap_handler (sig) int sig; { /* Call the trap handler for SIG if the signal is trapped and not ignored. */ if ((sigmodes[sig] & SIG_TRAPPED) && ((sigmodes[sig] & SIG_IGNORED) == 0)) { switch (sig) { case SIGINT: run_interrupt_trap (); break; case EXIT_TRAP: run_exit_trap (); break; case DEBUG_TRAP: run_debug_trap (); break; case ERROR_TRAP: run_error_trap (); break; default: trap_handler (sig); break; } return (1); } else return (0); } int signal_is_trapped (sig) int sig; { return (sigmodes[sig] & SIG_TRAPPED); } int signal_is_special (sig) int sig; { return (sigmodes[sig] & SIG_SPECIAL); } int signal_is_ignored (sig) int sig; { return (sigmodes[sig] & SIG_IGNORED); } void set_signal_ignored (sig) int sig; { sigmodes[sig] |= SIG_HARD_IGNORE; original_signals[sig] = SIG_IGN; } /* I can't stand it anymore! Please can't we just write the whole Unix system in lisp or something? */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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. */ /* **************************************************************** */ /* */ /* Unwind Protection Scheme for Bash */ /* */ /* **************************************************************** */ #include "config.h" #include "bashtypes.h" #include "bashansi.h" #if defined (HAVE_UNISTD_H) # include #endif #if STDC_HEADERS # include #endif #ifndef offsetof # define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) #endif #include "command.h" #include "general.h" #include "unwind_prot.h" #include "quit.h" #include "sig.h" /* Structure describing a saved variable and the value to restore it to. */ typedef struct { char *variable; int size; char desired_setting[1]; /* actual size is `size' */ } SAVED_VAR; /* If HEAD.CLEANUP is null, then ARG.V contains a tag to throw back to. If HEAD.CLEANUP is restore_variable, then SV.V contains the saved variable. Otherwise, call HEAD.CLEANUP (ARG.V) to clean up. */ typedef union uwp { struct uwp_head { union uwp *next; Function *cleanup; } head; struct { struct uwp_head uwp_head; char *v; } arg; struct { struct uwp_head uwp_head; SAVED_VAR v; } sv; } UNWIND_ELT; static void without_interrupts __P((VFunction *, char *, char *)); static void unwind_frame_discard_internal __P((char *, char *)); static void unwind_frame_run_internal __P((char *, char *)); static void add_unwind_protect_internal __P((Function *, char *)); static void remove_unwind_protect_internal __P((char *, char *)); static void run_unwind_protects_internal __P((char *, char *)); static void clear_unwind_protects_internal __P((char *, char *)); static inline void restore_variable __P((SAVED_VAR *)); static void unwind_protect_mem_internal __P((char *, char *)); static UNWIND_ELT *unwind_protect_list = (UNWIND_ELT *)NULL; extern int interrupt_immediately; /* Run a function without interrupts. This relies on the fact that the FUNCTION cannot change the value of interrupt_immediately. (I.e., does not call QUIT (). */ static void without_interrupts (function, arg1, arg2) VFunction *function; char *arg1, *arg2; { int old_interrupt_immediately; old_interrupt_immediately = interrupt_immediately; interrupt_immediately = 0; (*function)(arg1, arg2); interrupt_immediately = old_interrupt_immediately; } /* Start the beginning of a region. */ void begin_unwind_frame (tag) char *tag; { add_unwind_protect ((Function *)NULL, tag); } /* Discard the unwind protects back to TAG. */ void discard_unwind_frame (tag) char *tag; { if (unwind_protect_list) without_interrupts (unwind_frame_discard_internal, tag, (char *)NULL); } /* Run the unwind protects back to TAG. */ void run_unwind_frame (tag) char *tag; { if (unwind_protect_list) without_interrupts (unwind_frame_run_internal, tag, (char *)NULL); } /* Add the function CLEANUP with ARG to the list of unwindable things. */ void add_unwind_protect (cleanup, arg) Function *cleanup; char *arg; { without_interrupts (add_unwind_protect_internal, (char *)cleanup, arg); } /* Remove the top unwind protect from the list. */ void remove_unwind_protect () { if (unwind_protect_list) without_interrupts (remove_unwind_protect_internal, (char *)NULL, (char *)NULL); } /* Run the list of cleanup functions in unwind_protect_list. */ void run_unwind_protects () { if (unwind_protect_list) without_interrupts (run_unwind_protects_internal, (char *)NULL, (char *)NULL); } /* Erase the unwind-protect list. If flags is 1, free the elements. */ void clear_unwind_protect_list (flags) int flags; { char *flag; if (unwind_protect_list) { flag = flags ? "" : (char *)NULL; without_interrupts (clear_unwind_protects_internal, flag, (char *)NULL); } } /* **************************************************************** */ /* */ /* The Actual Functions */ /* */ /* **************************************************************** */ static void add_unwind_protect_internal (cleanup, arg) Function *cleanup; char *arg; { UNWIND_ELT *elt; elt = (UNWIND_ELT *)xmalloc (sizeof (UNWIND_ELT)); elt->head.next = unwind_protect_list; elt->head.cleanup = cleanup; elt->arg.v = arg; unwind_protect_list = elt; } static void remove_unwind_protect_internal (ignore1, ignore2) char *ignore1, *ignore2; { UNWIND_ELT *elt; elt = unwind_protect_list; if (elt) { unwind_protect_list = unwind_protect_list->head.next; free (elt); } } static void run_unwind_protects_internal (ignore1, ignore2) char *ignore1, *ignore2; { unwind_frame_run_internal ((char *) NULL, (char *) NULL); } static void clear_unwind_protects_internal (flag, ignore) char *flag, *ignore; { if (flag) { while (unwind_protect_list) remove_unwind_protect_internal ((char *)NULL, (char *)NULL); } unwind_protect_list = (UNWIND_ELT *)NULL; } static void unwind_frame_discard_internal (tag, ignore) char *tag, *ignore; { UNWIND_ELT *elt; while (elt = unwind_protect_list) { unwind_protect_list = unwind_protect_list->head.next; if (elt->head.cleanup == 0 && (STREQ (elt->arg.v, tag))) { free (elt); break; } else free (elt); } } /* Restore the value of a variable, based on the contents of SV. sv->desired_setting is a block of memory SIZE bytes long holding the value itself. This block of memory is copied back into the variable. */ static inline void restore_variable (sv) SAVED_VAR *sv; { FASTCOPY (sv->desired_setting, sv->variable, sv->size); } static void unwind_frame_run_internal (tag, ignore) char *tag, *ignore; { UNWIND_ELT *elt; while (elt = unwind_protect_list) { unwind_protect_list = elt->head.next; /* If tag, then compare. */ if (!elt->head.cleanup) { if (tag && STREQ (elt->arg.v, tag)) { free (elt); break; } } else { if (elt->head.cleanup == (Function *) restore_variable) restore_variable (&elt->sv.v); else (*(elt->head.cleanup)) (elt->arg.v); } free (elt); } } static void unwind_protect_mem_internal (var, psize) char *var; char *psize; { int size, allocated; UNWIND_ELT *elt; size = *(int *) psize; allocated = size + offsetof (UNWIND_ELT, sv.v.desired_setting[0]); elt = (UNWIND_ELT *)xmalloc (allocated); elt->head.next = unwind_protect_list; elt->head.cleanup = (Function *) restore_variable; elt->sv.v.variable = var; elt->sv.v.size = size; FASTCOPY (var, elt->sv.v.desired_setting, size); unwind_protect_list = elt; } /* Save the value of a variable so it will be restored when unwind-protects are run. VAR is a pointer to the variable. SIZE is the size in bytes of VAR. */ void unwind_protect_mem (var, size) char *var; int size; { without_interrupts (unwind_protect_mem_internal, var, (char *) &size); } /* variables.c -- Functions for hacking shell variables. */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 "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 /* Variables used here and defined in other files. */ extern int posixly_correct; extern int line_number; extern int subshell_environment, indirection_level; extern int build_version, patch_level; 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 *this_command_name; extern time_t shell_start_time; /* The list of shell variables that the user has created, or that came from the environment. */ HASH_TABLE *shell_variables = (HASH_TABLE *)NULL; /* The list of shell functions that the user has created, or that came from the environment. */ HASH_TABLE *shell_functions = (HASH_TABLE *)NULL; /* The current variable context. This is really a count of how deep into executing functions we are. */ int variable_context = 0; /* The array of shell assignments which are made only in the environment for a single command. */ char **temporary_env = (char **)NULL; /* The array of shell assignments which are in the environment for the execution of a shell function. */ char **function_env = (char **)NULL; /* The array of shell assignments which are made only in the environment for the execution of a shell builtin command which may cause more than one command to be executed (e.g., "eval" or "source"). */ char **builtin_env = (char **)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; static char *have_local_variables; static int local_variable_stack_size; /* 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 void initialize_dynamic_variables __P((void)); static void sbrand __P((unsigned long)); /* set bash random number generator. */ static int qsort_var_comp __P((SHELL_VAR **, SHELL_VAR **)); static SHELL_VAR **all_vars __P((HASH_TABLE *)); static void free_variable_hash_data __P((PTR_T)); static SHELL_VAR *new_shell_variable __P((const char *)); static SHELL_VAR *make_new_variable __P((const char *)); static int visible_var __P((SHELL_VAR *)); static SHELL_VAR **_visible_names __P((HASH_TABLE *)); static int visible_and_exported __P((SHELL_VAR *)); #if defined (ARRAY_VARS) static int visible_array_vars __P((SHELL_VAR *)); #endif static inline char *mk_env_string __P((const char *, const char *)); static SHELL_VAR *shell_var_from_env_string __P((const char *, char *, int)); static SHELL_VAR *bind_name_in_env_array __P((const char *, char *, char **)); static SHELL_VAR *find_name_in_env_array __P((const char *, char **)); static SHELL_VAR *bind_tempenv_variable __P((const char *, char *)); static void dispose_temporary_vars __P((char ***)); static void merge_env_array __P((char **)); /* Make VAR be auto-exported. VAR is a pointer to a SHELL_VAR. */ #define set_auto_export(var) \ do { var->attributes |= att_exported; array_needs_making = 1; } while (0) /* 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 = make_hash_table (0); if (shell_functions == 0) shell_functions = make_hash_table (0); 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] == '[' && strchr (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"); /* 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 /* 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"); 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 (); } 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; long 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]; makunbound ("BASH_VERSINFO", shell_variables); vv = make_new_array_variable ("BASH_VERSINFO"); av = array_cell (vv); strcpy (d, dist_version); s = strchr (d, '.'); if (s) *s++ = '\0'; array_add_element (av, 0, d); array_add_element (av, 1, s); s = inttostr (patch_level, b, sizeof (b)); array_add_element (av, 2, s); s = inttostr (build_version, b, sizeof (b)); array_add_element (av, 3, s); array_add_element (av, 4, release_status); array_add_element (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); } /* 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 (name, value); return (v); } /* Map FUNCTION over the variables in VARIABLES. 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, var_hash_table) sh_var_map_func_t *function; HASH_TABLE *var_hash_table; { register int i; register BUCKET_CONTENTS *tlist; SHELL_VAR *var, **list; int list_index, list_size; list = (SHELL_VAR **)NULL; for (i = list_index = list_size = 0; i < var_hash_table->nbuckets; i++) { tlist = get_hash_bucket (i, var_hash_table); while (tlist) { var = (SHELL_VAR *)tlist->data; if (!function || (*function) (var)) { if (list_index + 1 >= list_size) list = (SHELL_VAR **) xrealloc (list, (list_size += 20) * sizeof (SHELL_VAR *)); list[list_index++] = var; list[list_index] = (SHELL_VAR *)NULL; } tlist = tlist->next; } } return (list); } void sort_variables (array) SHELL_VAR **array; { qsort (array, array_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); } /* Create a NULL terminated array of all the shell variables in TABLE. */ static SHELL_VAR ** all_vars (table) HASH_TABLE *table; { SHELL_VAR **list; list = map_over ((sh_var_map_func_t *)NULL, table); 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 (all_vars (shell_variables)); } /* Create a NULL terminated array of all the shell functions. */ SHELL_VAR ** all_shell_functions () { return (all_vars (shell_functions)); } /* 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)) 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"); } } #if defined (NOTDEF) /* Print LIST (a linked list of shell variables) to stdout by printing the names, without the values. Used to support the `set +' command. */ void print_vars_no_values (list) register SHELL_VAR **list; { register int i; register SHELL_VAR *var; for (i = 0; list && (var = list[i]); i++) if (!invisible_p (var)) printf ("%s\n", var->name); } #endif /* 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 (function_p (var) && var->value) { printf ("%s", var->name); print_var_function (var); printf ("\n"); } #if defined (ARRAY_VARS) else if (array_p (var) && var->value) print_array_assignment (var, 0); #endif /* ARRAY_VARS */ else if (var->value) { 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->value) { if (quote && posixly_correct == 0 && ansic_shouldquote (var->value)) { t = ansic_quote (var->value, 0, (int *)0); printf ("%s", t); free (t); } else if (quote && sh_contains_shell_metas (var->value)) { t = sh_single_quote (var->value); printf ("%s", t); free (t); } else printf ("%s", var->value); } } /* 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->value) printf ("%s", named_function_string ((char *)NULL, function_cell(var), 1)); } /* **************************************************************** */ /* */ /* Dynamic Variable Extension */ /* */ /* **************************************************************** */ /* 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 dynamic_value, which is called from find_variable. assign_func is called from bind_variable, if bind_variable discovers that the variable being assigned to has such a function. The function is called as SHELL_VAR *temp = (*(entry->assign_func)) (entry, value) and the (SHELL_VAR *)temp is returned as the value of bind_variable. It is usually ENTRY (self). dynamic_value is called from find_variable 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, 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) SHELL_VAR *self; char *value; { return (self); } #if defined (ARRAY_VARS) static SHELL_VAR * null_array_assign (self, ind, value) SHELL_VAR *self; arrayind_t ind; char *value; { return (self); } #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 long seconds_value_assigned; static SHELL_VAR * assign_seconds (self, value) SHELL_VAR *self; char *value; { 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 (var->value); VSETATTR (var, att_integer); var->value = 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 ? v->value : (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 on 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) SHELL_VAR *self; char *value; { 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 (var->value); VSETATTR (var, att_integer); var->value = p; 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 (var->value); var->value = p; return (var); } static SHELL_VAR * assign_lineno (var, value) SHELL_VAR *var; char *value; { long new_value; if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0) new_value = 0; line_number = new_value; return var; } #if defined (HISTORY) static SHELL_VAR * get_histcmd (var) SHELL_VAR *var; { char *p; p = itos (history_number ()); FREE (var->value); var->value = p; return (var); } #endif #if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) static SHELL_VAR * get_dirstack (self) SHELL_VAR *self; { ARRAY *a; WORD_LIST *l; l = get_directory_stack (); a = word_list_to_array (l); dispose_array (array_cell (self)); dispose_words (l); self->value = (char *)a; return self; } static SHELL_VAR * assign_dirstack (self, ind, value) SHELL_VAR *self; arrayind_t ind; char *value; { set_dirstack_element (ind, 1, value); return self; } static SHELL_VAR * init_dirstack_var () { SHELL_VAR *v; v = find_variable ("DIRSTACK"); if (v) return v; INIT_DYNAMIC_ARRAY_VAR ("DIRSTACK", get_dirstack, assign_dirstack); return v; } #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_add_element (a, i, group_set[i]); } return (self); } static SHELL_VAR * init_groups_var () { SHELL_VAR *v; v = find_variable ("GROUPS"); if (v) return (v); INIT_DYNAMIC_ARRAY_VAR ("GROUPS", get_groupset, null_array_assign); VSETATTR (v, att_noassign); return v; } #endif /* ARRAY_VARS */ static SHELL_VAR * get_funcname (self) SHELL_VAR *self; { if (variable_context && this_shell_function) { FREE (self->value); self->value = savestring (this_shell_function->name); } 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; INIT_DYNAMIC_VAR ("FUNCNAME", (char *)NULL, get_funcname, null_assign); VSETATTR (v, att_invisible|att_noassign); return v; } static void initialize_dynamic_variables () { SHELL_VAR *v; v = init_seconds_var (); 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, (DYNAMIC_FUNC *)NULL); #endif #if defined (PUSHD_AND_POPD) && defined (ARRAY_VARS) v = init_dirstack_var (); #endif /* PUSHD_AND_POPD && ARRAY_VARS */ #if defined (ARRAY_VARS) v = init_groups_var (); #endif v = init_funcname_var (); } /* 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). */ SHELL_VAR * var_lookup (name, hashed_vars) const char *name; HASH_TABLE *hashed_vars; { BUCKET_CONTENTS *bucket; bucket = find_hash_item (name, hashed_vars); return (bucket ? (SHELL_VAR *)bucket->data : (SHELL_VAR *)NULL); } /* Look up the variable entry named NAME. If SEARCH_TEMPENV is non-zero, then also search the temporarily built list of exported variables. */ SHELL_VAR * find_variable_internal (name, search_tempenv) const char *name; int search_tempenv; { SHELL_VAR *var; 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". */ if ((search_tempenv || subshell_environment) && (temporary_env || builtin_env || function_env)) var = find_tempenv_variable (name); if (!var) var = var_lookup (name, shell_variables); if (!var) 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, (variable_context || this_shell_builtin || builtin_env))); } /* Look up the function entry whose name matches STRING. Returns the entry or NULL. */ SHELL_VAR * find_function (name) const char *name; { return (var_lookup (name, shell_functions)); } /* Return the string value of a variable. Return NULL if the variable doesn't exist, or only has a function as a value. Don't cons a new string. This is a potential memory leak if the variable is found in the temporary environment. */ char * get_string_value (var_name) const char *var_name; { SHELL_VAR *var; var = find_variable (var_name); if (!var) return (char *)NULL; #if defined (ARRAY_VARS) else if (array_p (var)) return (array_reference (array_cell (var), 0)); #endif else return (var->value); } /* 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); } /* Create a local variable referenced by NAME. */ SHELL_VAR * make_local_variable (name) const char *name; { SHELL_VAR *new_var, *old_var; BUCKET_CONTENTS *elt; /* local foo; local foo; is a no-op. */ old_var = find_variable (name); if (old_var && old_var->context == variable_context) return (old_var); /* 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)) builtin_error ("%s: readonly variable", name); return ((SHELL_VAR *)NULL); } elt = remove_hash_item (name, shell_variables); if (elt) { old_var = (SHELL_VAR *)elt->data; free (elt->key); free (elt); } else old_var = (SHELL_VAR *)NULL; /* If a variable does not already exist with this name, then just make a new one. */ if (old_var == 0) new_var = bind_variable (name, ""); else { new_var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); new_var->name = savestring (name); new_var->value = (char *)xmalloc (1); new_var->value[0] = '\0'; CLEAR_EXPORTSTR (new_var); new_var->dynamic_value = (DYNAMIC_FUNC *)NULL; new_var->assign_func = (DYNAMIC_FUNC *)NULL; new_var->attributes = exported_p (old_var) ? att_exported : 0; new_var->prev_context = old_var; elt = add_hash_item (savestring (name), shell_variables); elt->data = (char *)new_var; } new_var->context = variable_context; VSETATTR (new_var, att_local); /* XXX */ if (variable_context >= local_variable_stack_size) { int old_size = local_variable_stack_size; RESIZE_MALLOCED_BUFFER (have_local_variables, variable_context, 1, local_variable_stack_size, 8); bzero ((char *)have_local_variables + old_size, local_variable_stack_size - old_size); } have_local_variables[variable_context] = 1; /* XXX */ 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 = new_array (); FREE (value_cell(var)); var->value = (char *)array; VSETATTR (var, att_array); return var; } #endif /* ARRAY_VARS */ /* Create a new shell variable with name NAME and add it to the hash table of shell variables. */ static SHELL_VAR * make_new_variable (name) const char *name; { SHELL_VAR *entry; BUCKET_CONTENTS *elt; entry = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); entry->attributes = 0; entry->name = savestring (name); entry->value = (char *)NULL; CLEAR_EXPORTSTR (entry); entry->dynamic_value = (DYNAMIC_FUNC *)NULL; entry->assign_func = (DYNAMIC_FUNC *)NULL; /* Always assume variables are to be made at toplevel! make_local_variable has the responsibilty of changing the variable context. */ entry->context = 0; entry->prev_context = (SHELL_VAR *)NULL; /* Make sure we have a shell_variables hash table to add to. */ if (shell_variables == 0) shell_variables = make_hash_table (0); elt = add_hash_item (savestring (name), shell_variables); elt->data = (char *)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); array = new_array (); entry->value = (char *)array; VSETATTR (entry, att_array); return entry; } #endif char * make_variable_value (var, value) SHELL_VAR *var; char *value; { char *retval; long 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. This conses up the name and value strings. */ SHELL_VAR * bind_variable (name, value) const char *name; char *value; { char *newval; SHELL_VAR *entry, *tempenv_entry; entry = (SHELL_VAR *)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 || builtin_env || function_env) { tempenv_entry = find_tempenv_variable (name); if (tempenv_entry) { dispose_variable (tempenv_entry); tempenv_entry = bind_tempenv_variable (name, value); dispose_variable (tempenv_entry); } } entry = var_lookup (name, shell_variables); if (entry == 0) { entry = make_new_variable (name); entry->value = 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)); } else { if (readonly_p (entry) || noassign_p (entry)) { if (readonly_p (entry)) report_error ("%s: readonly variable", 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_add_element (array_cell (entry), 0, newval); free (newval); } else #endif { FREE (entry->value); entry->value = newval; } } if (mark_modified_vars) VSETATTR (entry, att_exported); if (exported_p (entry)) array_needs_making = 1; return (entry); } /* 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 (var->value); var->value = 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; int isint; isint = 0; v = find_variable (lhs); if (v) { isint = integer_p (v); VUNSETATTR (v, att_integer); } v = bind_variable (lhs, rhs); if (isint) VSETATTR (v, att_integer); return (v); } SHELL_VAR * bind_var_to_int (var, val) char *var; long val; { char ibuf[INT_STRLEN_BOUND (long) + 1], *p; p = fmtulong (val, 10, ibuf, sizeof (ibuf), 0); return (bind_int_variable (var, p)); } /* Dispose of the information attached to VAR. */ void dispose_variable (var) SHELL_VAR *var; { if (!var) return; if (function_p (var)) dispose_command (function_cell (var)); #if defined (ARRAY_VARS) else if (array_p (var)) dispose_array (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 variable referenced by NAME. */ int unbind_variable (name) const char *name; { SHELL_VAR *var; var = find_variable (name); if (!var) return (-1); /* This function should never be called with an array variable name. */ #if defined (ARRAY_VARS) if (array_p (var) == 0 && var->value) #else if (var->value) #endif { free (var->value); var->value = (char *)NULL; } makunbound (name, shell_variables); 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, hash_list) const char *name; HASH_TABLE *hash_list; { BUCKET_CONTENTS *elt, *new_elt; SHELL_VAR *old_var, *new_var; char *t; elt = remove_hash_item (name, hash_list); if (elt == 0) return (-1); old_var = (SHELL_VAR *)elt->data; new_var = old_var->prev_context; if (old_var && exported_p (old_var)) array_needs_making++; #if defined (PROGRAMMABLE_COMPLETION) if (hash_list == shell_functions) set_itemlist_dirty (&it_functions); #endif /* If we're unsetting a local variable and we're still executing inside the function, just mark the variable as invisible. kill_all_local_variables 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) { VSETATTR (old_var, att_invisible); INVALIDATE_EXPORTSTR (old_var); new_elt = add_hash_item (savestring (old_var->name), hash_list); new_elt->data = (char *)old_var; stupidly_hack_special_variables (old_var->name); free (elt->key); free (elt); return (0); } if (new_var) { /* Has to be a variable, functions don't have previous contexts. */ new_elt = add_hash_item (savestring (new_var->name), hash_list); new_elt->data = (char *)new_var; if (exported_p (new_var)) set_auto_export (new_var); } /* 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); } #ifdef INCLUDE_UNUSED /* Remove the variable with NAME if it is a local variable in the current context. */ int kill_local_variable (name) const char *name; { SHELL_VAR *temp; temp = find_variable (name); if (temp && temp->context == variable_context) { makunbound (name, shell_variables); return (0); } return (-1); } #endif /* Get rid of all of the variables in the current context. */ int variable_in_context (var) SHELL_VAR *var; { return (var && var->context == variable_context); } void kill_all_local_variables () { register int i, pass; register SHELL_VAR *var, **list; HASH_TABLE *varlist; /* If HAVE_LOCAL_VARIABLES == 0, it means that we don't have any local variables at all. If VARIABLE_CONTEXT >= LOCAL_VARIABLE_STACK_SIZE, it means that we have some local variables, but not in this variable context (level of function nesting). Also, if HAVE_LOCAL_VARIABLES[VARIABLE_CONTEXT] == 0, we have no local variables at this context. */ if (have_local_variables == 0 || variable_context >= local_variable_stack_size || have_local_variables[variable_context] == 0) return; for (pass = 0; pass < 2; pass++) { varlist = pass ? shell_functions : shell_variables; list = map_over (variable_in_context, varlist); if (list) { for (i = 0; var = list[i]; i++) { VUNSETATTR (var, att_local); makunbound (var->name, varlist); } free (list); } } have_local_variables[variable_context] = 0; /* XXX */ } static void free_variable_hash_data (data) PTR_T data; { SHELL_VAR *var, *prev; var = (SHELL_VAR *)data; while (var) { prev = var->prev_context; dispose_variable (var); var = prev; } } /* Delete the entire contents of the hash table. */ void delete_all_variables (hashed_vars) HASH_TABLE *hashed_vars; { flush_hash_table (hashed_vars, free_variable_hash_data); } static SHELL_VAR * new_shell_variable (name) const char *name; { SHELL_VAR *var; var = (SHELL_VAR *)xmalloc (sizeof (SHELL_VAR)); bzero ((char *)var, sizeof (SHELL_VAR)); var->name = savestring (name); return (var); } /* 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) { BUCKET_CONTENTS *elt; elt = add_hash_item (savestring (name), shell_functions); entry = new_shell_variable (name); entry->dynamic_value = entry->assign_func = (DYNAMIC_FUNC *)NULL; CLEAR_EXPORTSTR (entry); /* Functions are always made at the top level. This allows a function to define another function (like autoload). */ entry->context = 0; elt->data = (char *)entry; } INVALIDATE_EXPORTSTR (entry); if (entry->value) dispose_command ((COMMAND *)entry->value); entry->value = value ? (char *)copy_command (value) : (char *)NULL; 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); } #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)) copy->value = (char *)copy_command (function_cell (var)); #if defined (ARRAY_VARS) else if (array_p (var)) copy->value = (char *)dup_array (array_cell (var)); #endif else if (value_cell (var)) copy->value = savestring (value_cell (var)); else copy->value = (char *)NULL; copy->dynamic_value = var->dynamic_value; copy->assign_func = var->assign_func; copy->exportstr = COPY_EXPORTSTR (var); copy->context = var->context; /* Don't bother copying previous contexts along with this variable. */ copy->prev_context = (SHELL_VAR *)NULL; } return (copy); } #endif #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 /* Returns non-zero if STRING is an assignment statement. The returned value is the index of the `=' sign. */ int assignment (string) const char *string; { register unsigned char c; register int newi, indx; c = string[indx = 0]; if (legal_variable_starter (c) == 0) 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); } static int visible_var (var) SHELL_VAR *var; { return (invisible_p (var) == 0); } static SHELL_VAR ** _visible_names (table) HASH_TABLE *table; { SHELL_VAR **list; list = map_over (visible_var, table); if (list /* && posixly_correct */) sort_variables (list); return (list); } SHELL_VAR ** all_visible_functions () { return (_visible_names (shell_functions)); } SHELL_VAR ** all_visible_variables () { return (_visible_names (shell_variables)); } /* 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)); } SHELL_VAR ** all_exported_variables () { SHELL_VAR **list; list = map_over (visible_and_exported, shell_variables); if (list) sort_variables (list); return (list); } #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 () { SHELL_VAR **list; list = map_over (visible_array_vars, shell_variables); if (list) sort_variables (list); return (list); } #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 = alloc_array (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; } 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 /* Make an array of assignment statements from the hash table HASHED_VARS which contains SHELL_VARs. Only visible, exported variables are eligible. */ char ** make_var_array (hashed_vars) HASH_TABLE *hashed_vars; { register int i, list_index; register SHELL_VAR *var; char **list, *value; SHELL_VAR **vars; vars = map_over (visible_and_exported, hashed_vars); if (vars == 0) return (char **)NULL; list = alloc_array ((1 + array_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 && function_p (var)) { 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 } } free (vars); list[list_index] = (char *)NULL; return (list); } /* Add STRING to the array of foo=bar strings that we already have to add to the environment. */ int assign_in_env (string) const char *string; { int size, offset; char *name, *temp, *value; SHELL_VAR *var; offset = assignment (string); 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)) report_error ("%s: readonly variable", name); free (name); return (0); } temp = name + offset + 1; temp = (strchr (temp, '~') != 0) ? bash_tilde_expand (temp) : savestring (temp); value = expand_string_unsplit_to_string (temp, 0); free (temp); } temp = mk_env_string (name, value); FREE (value); free (name); if (temporary_env == 0) { temporary_env = (char **)xmalloc (sizeof (char *)); temporary_env [0] = (char *)NULL; } size = array_len (temporary_env); temporary_env = (char **) xrealloc (temporary_env, (size + 2) * (sizeof (char *))); temporary_env[size] = temp; temporary_env[size + 1] = (char *)NULL; array_needs_making = 1; if (echo_command_at_execute) { /* The Korn shell prints the `+ ' in front of assignment statements, so we do too. */ fprintf (stderr, "%s%s\n", indirection_level_string (), temp); fflush (stderr); } return 1; } /* Create a SHELL_VAR from a `name=value' string as in the environment, taking the variable name, the environment string, and an index into the string which is the offset of the `=' (the char before the value begins). */ static SHELL_VAR * shell_var_from_env_string (name, env_string, l) const char *name; char *env_string; int l; { SHELL_VAR *temp; char *w; /* This is a potential memory leak. The code should really save the created variables in some auxiliary data structure, which can be disposed of at the appropriate time. */ temp = new_shell_variable (name); w = env_string + l + 1; temp->value = *w ? savestring (w) : (char *)NULL; temp->attributes = att_exported|att_tempvar; temp->context = 0; temp->prev_context = (SHELL_VAR *)NULL; temp->dynamic_value = temp->assign_func = (DYNAMIC_FUNC *)NULL; CLEAR_EXPORTSTR (temp); return (temp); } /* Bind NAME to VALUE in ARRAY, an array of strings in the same format as the environment array (i.e, name=value). If NAME is present, change the value, cons up a new SHELL_VAR and return it. Otherwise return (SHELL_VAR *)NULL. */ static SHELL_VAR * bind_name_in_env_array (name, value, array) const char *name; char *value; char **array; { register int i, l; char *new_env_string; SHELL_VAR *temp; if (array == 0) return ((SHELL_VAR *)NULL); for (i = 0, l = strlen (name); array[i]; i++) { if (STREQN (array[i], name, l) && array[i][l] == '=') { new_env_string = mk_env_string (name, value); temp = shell_var_from_env_string (name, new_env_string, l); free (array[i]); array[i] = new_env_string; return (temp); } } return ((SHELL_VAR *)NULL); } /* Search for NAME in ARRAY, an array of strings in the same format as the environment array (i.e, name=value). If NAME is present, make a new variable and return it. Otherwise, return NULL. */ static SHELL_VAR * find_name_in_env_array (name, array) const char *name; char **array; { register int i, l; SHELL_VAR *temp; if (array == 0) return ((SHELL_VAR *)NULL); for (i = 0, l = strlen (name); array[i]; i++) { if (STREQN (array[i], name, l) && array[i][l] == '=') { temp = shell_var_from_env_string (name, array[i], l); return (temp); } } return ((SHELL_VAR *)NULL); } #define FIND_AND_BIND_IN_ENV_ARRAY(N, V, A) \ do \ { \ var = find_name_in_env_array (N, A); \ if (var) \ { \ dispose_variable (var); \ var = bind_name_in_env_array (N, V, A); \ return (var); \ } \ } \ while (0) /* Make variable NAME have VALUE in one of the temporary environments. */ static SHELL_VAR * bind_tempenv_variable (name, value) const char *name; char *value; { SHELL_VAR *var; var = (SHELL_VAR *)NULL; if (temporary_env) FIND_AND_BIND_IN_ENV_ARRAY (name, value, temporary_env); /* We don't check this_shell_builtin because the command that needs the value from builtin_env may be a disk command run inside a script run with `.' and a temporary env. */ if (builtin_env) FIND_AND_BIND_IN_ENV_ARRAY (name, value, builtin_env); if (variable_context && function_env) FIND_AND_BIND_IN_ENV_ARRAY (name, value, function_env); return (SHELL_VAR *)NULL; } /* Find a variable in the temporary environment that is named NAME. The temporary environment can be either the environment provided to a simple command, or the environment provided to a shell function. We only search the function environment if we are currently executing a shell function body (variable_context > 0). Return a consed variable, or NULL if not found. */ SHELL_VAR * find_tempenv_variable (name) const char *name; { SHELL_VAR *var; var = (SHELL_VAR *)NULL; if (temporary_env) var = find_name_in_env_array (name, temporary_env); /* We don't check this_shell_builtin because the command that needs the value from builtin_env may be a disk command run inside a script run with `.' and a temporary env. */ if (!var && builtin_env) var = find_name_in_env_array (name, builtin_env); if (!var && variable_context && function_env) var = find_name_in_env_array (name, function_env); return (var); } /* Free the storage allocated to the string array pointed to by ARRAYP, and make that variable have a null pointer as a value. */ static void dispose_temporary_vars (arrayp) char ***arrayp; { if (!*arrayp) return; free_array (*arrayp); *arrayp = (char **)NULL; array_needs_making = 1; } /* Free the storage used in the variable array for temporary environment variables. */ void dispose_used_env_vars () { dispose_temporary_vars (&temporary_env); } /* Free the storage used for temporary environment variables given to commands when executing inside of a function body. */ void dispose_function_env () { dispose_temporary_vars (&function_env); } /* Free the storage used for temporary environment variables given to commands when executing a builtin command such as "source". */ void dispose_builtin_env () { dispose_temporary_vars (&builtin_env); } /* Take all of the shell variables in ENV_ARRAY and make shell variables from them at the current variable context. */ static void merge_env_array (env_array) char **env_array; { register int i, l; SHELL_VAR *temp; char *val, *name; if (env_array == 0) return; for (i = 0; env_array[i]; i++) { l = assignment (env_array[i]); name = env_array[i]; val = env_array[i] + l + 1; name[l] = '\0'; temp = bind_variable (name, val); name[l] = '='; } } void merge_temporary_env () { merge_env_array (temporary_env); } void merge_builtin_env () { merge_env_array (builtin_env); } void merge_function_env () { merge_env_array (function_env); } #ifdef INCLUDE_UNUSED int any_temporary_variables () { return (temporary_env || function_env); } #endif /* 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 = (char **)xrealloc (export_env, export_env_size * sizeof (char *)); \ } \ export_env[export_env_index++] = (do_alloc) ? savestring (envstr) : envstr; \ export_env[export_env_index] = (char *)NULL; \ } while (0) #define ISFUNCTION(s, o) ((s[o + 1] == '(') && (s[o + 2] == ')')) /* 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); if (equal_offset == 0) return (export_env); /* If this is a function, then only supercede the function definition. We do this by including the `=(' in the comparison. */ if (assign[equal_offset + 1] == '(') equal_offset++; 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); } /* 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++'. */ void maybe_make_export_env () { register int i; register char **temp_array; int new_size; if (array_needs_making) { if (export_env) free_array_members (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, without any temporary or function environments. */ new_size = HASH_ENTRIES (shell_variables) + HASH_ENTRIES (shell_functions) + 1; if (new_size > export_env_size) { export_env_size = new_size; export_env = (char **)xrealloc (export_env, export_env_size * sizeof (char *)); } export_env[export_env_index = 0] = (char *)NULL; temp_array = make_var_array (shell_variables); if (temp_array) { for (i = 0; temp_array[i]; i++) add_to_export_env (temp_array[i], 0); free (temp_array); } #if defined (RESTRICTED_SHELL) /* Restricted shells may not export shell functions. */ temp_array = restricted ? (char **)0 : make_var_array (shell_functions); #else temp_array = make_var_array (shell_functions); #endif if (temp_array) { for (i = 0; temp_array[i]; i++) add_to_export_env (temp_array[i], 0); free (temp_array); } if (function_env) for (i = 0; function_env[i]; i++) export_env = add_or_supercede_exported_var (function_env[i], 1); if (builtin_env) for (i = 0; builtin_env[i]; i++) export_env = add_or_supercede_exported_var (builtin_env[i], 1); if (temporary_env) for (i = 0; temporary_env[i]; i++) export_env = add_or_supercede_exported_var (temporary_env[i], 1); #if 0 /* If we changed the array, then sort it alphabetically. */ if (posixly_correct == 0 && (temporary_env || function_env)) sort_char_array (export_env); #endif 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) long 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 /* Return a string denoting what our indirection level is. */ static char indirection_string[100]; 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); ps4 = decode_prompt_string (ps4); 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); } /************************************************* * * * 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 struct name_and_function { char *name; sh_sv_func_t *function; } special_vars[] = { { "PATH", sv_path }, { "MAIL", sv_mail }, { "MAILPATH", sv_mail }, { "MAILCHECK", sv_mail }, { "POSIXLY_CORRECT", sv_strict_posix }, { "GLOBIGNORE", sv_globignore }, /* Variables which only do something special when READLINE is defined. */ #if defined (READLINE) { "TERM", sv_terminal }, { "TERMCAP", sv_terminal }, { "TERMINFO", sv_terminal }, { "HOSTFILE", sv_hostfile }, #endif /* READLINE */ /* Variables which only do something special when HISTORY is defined. */ #if defined (HISTORY) { "HISTIGNORE", sv_histignore }, { "HISTSIZE", sv_histsize }, { "HISTFILESIZE", sv_histsize }, { "HISTCONTROL", sv_history_control }, # if defined (BANG_HISTORY) { "histchars", sv_histchars }, # endif /* BANG_HISTORY */ #endif /* HISTORY */ { "IGNOREEOF", sv_ignoreeof }, { "ignoreeof", sv_ignoreeof }, { "OPTIND", sv_optind }, { "OPTERR", sv_opterr }, { "TEXTDOMAIN", sv_locale }, { "TEXTDOMAINDIR", sv_locale }, { "LC_ALL", sv_locale }, { "LC_COLLATE", sv_locale }, { "LC_CTYPE", sv_locale }, { "LC_MESSAGES", sv_locale }, { "LC_NUMERIC", sv_locale }, { "LANG", sv_locale }, #if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) { "TZ", sv_tz }, #endif { (char *)0, (sh_sv_func_t *)0 } }; /* 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; { int i; for (i = 0; special_vars[i].name; i++) { if (STREQ (special_vars[i].name, name)) { (*(special_vars[i].function)) (name); return; } } } /* What to do just after the PATH variable has changed. */ void sv_path (name) char *name; { /* hash -r */ flush_hashed_filenames (); } /* 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; long 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; history_control = 0; temp = get_string_value (name); if (temp && *temp && STREQN (temp, "ignore", 6)) { if (temp[6] == 's') /* ignorespace */ history_control = 1; else if (temp[6] == 'd') /* ignoredups */ history_control = 2; else if (temp[6] == 'b') /* ignoreboth */ history_control = 3; } } #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 */ #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) int *ps; { SHELL_VAR *v; ARRAY *a; 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) empty_array (a); for (i = 0; ps[i] != -1; i++) { t = inttostr (ps[i], tbuf, sizeof (tbuf)); array_add_element (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); #endif } /* version.c -- distribution and version numbers. */ /* Copyright (C) 1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can 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 #include #include "stdc.h" #include "version.h" #include "patchlevel.h" #include "conftypes.h" extern char *shell_name; /* Defines from version.h */ const char *dist_version = DISTVERSION; int patch_level = PATCHLEVEL; int build_version = BUILDVERSION; #ifdef RELSTATUS const char *release_status = RELSTATUS; #else const char *release_status = (char *)0; #endif const char *sccs_version = SCCSVERSION; /* Functions for getting, setting, and displaying the shell version. */ /* Give version information about this shell. */ char * shell_version_string () { static char tt[32] = { '\0' }; if (tt[0] == '\0') { if (release_status) sprintf (tt, "%s.%d(%d)-%s", dist_version, patch_level, build_version, release_status); else sprintf (tt, "%s.%d(%d)", dist_version, patch_level, build_version); } return tt; } void show_shell_version (extended) int extended; { printf ("GNU bash, version %s (%s)\n", shell_version_string (), MACHTYPE); if (extended) printf ("Copyright 2001 Free Software Foundation, Inc.\n"); } /* xmalloc.c -- safe versions of malloc and realloc */ /* Copyright (C) 1991 Free Software Foundation, Inc. This file is part of GNU Readline, a library for reading lines of text with interactive input and history editing. Readline is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Readline is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Readline; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if defined (HAVE_CONFIG_H) #include #endif #include "bashtypes.h" #include #if defined (HAVE_UNISTD_H) # include #endif #if defined (HAVE_STDLIB_H) # include #else # include "ansi_stdlib.h" #endif /* HAVE_STDLIB_H */ #include "error.h" #if !defined (PTR_T) # if defined (__STDC__) # define PTR_T void * # else # define PTR_T char * # endif /* !__STDC__ */ #endif /* !PTR_T */ #if defined (HAVE_SBRK) && !HAVE_DECL_SBRK extern char *sbrk(); #endif static PTR_T lbreak; static int brkfound; static size_t allocated; /* **************************************************************** */ /* */ /* Memory Allocation and Deallocation. */ /* */ /* **************************************************************** */ #if defined (HAVE_SBRK) static size_t findbrk () { if (brkfound == 0) { lbreak = (PTR_T)sbrk (0); brkfound++; } return (char *)sbrk (0) - (char *)lbreak; } #endif /* Return a pointer to free()able block of memory large enough to hold BYTES number of bytes. If the memory cannot be allocated, print an error message and abort. */ PTR_T xmalloc (bytes) size_t bytes; { PTR_T temp; temp = malloc (bytes); if (temp == 0) { #if defined (HAVE_SBRK) allocated = findbrk (); fatal_error ("xmalloc: cannot allocate %lu bytes (%lu bytes allocated)", (unsigned long)bytes, (unsigned long)allocated); #else fatal_error ("xmalloc: cannot allocate %lu bytes", (unsigned long)bytes); #endif /* !HAVE_SBRK */ } return (temp); } PTR_T xrealloc (pointer, bytes) PTR_T pointer; size_t bytes; { PTR_T temp; temp = pointer ? realloc (pointer, bytes) : malloc (bytes); if (temp == 0) { #if defined (HAVE_SBRK) allocated = findbrk (); fatal_error ("xrealloc: cannot reallocate %lu bytes (%lu bytes allocated)", (unsigned long)bytes, (unsigned long)allocated); #else fatal_error ("xmalloc: cannot allocate %lu bytes", (unsigned long)bytes); #endif /* !HAVE_SBRK */ } return (temp); } /* Use this as the function to call when adding unwind protects so we don't need to know what free() returns. */ void xfree (string) PTR_T string; { if (string) free (string); } #ifdef USING_BASH_MALLOC #include PTR_T sh_xmalloc (bytes, file, line) size_t bytes; char *file; int line; { PTR_T temp; temp = sh_malloc (bytes, file, line); if (temp == 0) { #if defined (HAVE_SBRK) allocated = findbrk (); fatal_error ("xmalloc: %s:%d: cannot allocate %lu bytes (%lu bytes allocated)", file, line, (unsigned long)bytes, (unsigned long)allocated); #else fatal_error ("xmalloc: %s:%d: cannot allocate %lu bytes", file, line, (unsigned long)bytes); #endif /* !HAVE_SBRK */ } return (temp); } PTR_T sh_xrealloc (pointer, bytes, file, line) PTR_T pointer; size_t bytes; char *file; int line; { PTR_T temp; temp = pointer ? sh_realloc (pointer, bytes, file, line) : sh_malloc (bytes, file, line); if (temp == 0) { #if defined (HAVE_SBRK) allocated = findbrk (); fatal_error ("xrealloc: %s:%d: cannot reallocate %lu bytes (%lu bytes allocated)", file, line, (unsigned long)bytes, (unsigned long)allocated); #else fatal_error ("xrealloc: %s:%d: cannot allocate %lu bytes", file, line, (unsigned long)bytes); #endif /* !HAVE_SBRK */ } return (temp); } void sh_xfree (string, file, line) PTR_T string; char *file; int line; { if (string) sh_free (string, file, line); } #endif /* A Bison parser, made from /usr/homes/chet/src/bash/src/parse.y by GNU Bison version 1.28 */ #define YYBISON 1 /* Identify Bison output. */ #define IF 257 #define THEN 258 #define ELSE 259 #define ELIF 260 #define FI 261 #define CASE 262 #define ESAC 263 #define FOR 264 #define SELECT 265 #define WHILE 266 #define UNTIL 267 #define DO 268 #define DONE 269 #define FUNCTION 270 #define COND_START 271 #define COND_END 272 #define COND_ERROR 273 #define IN 274 #define BANG 275 #define TIME 276 #define TIMEOPT 277 #define WORD 278 #define ASSIGNMENT_WORD 279 #define NUMBER 280 #define ARITH_CMD 281 #define ARITH_FOR_EXPRS 282 #define COND_CMD 283 #define AND_AND 284 #define OR_OR 285 #define GREATER_GREATER 286 #define LESS_LESS 287 #define LESS_AND 288 #define GREATER_AND 289 #define SEMI_SEMI 290 #define LESS_LESS_MINUS 291 #define AND_GREATER 292 #define LESS_GREATER 293 #define GREATER_BAR 294 #define yacc_EOF 295 #line 21 "/usr/homes/chet/src/bash/src/parse.y" #include "config.h" #include "bashtypes.h" #include "bashansi.h" #include "filecntl.h" #if defined (HAVE_UNISTD_H) # include #endif #if defined (HAVE_LOCALE_H) # include #endif #include #include "chartypes.h" #include #include "memalloc.h" #include "shell.h" #include "trap.h" #include "flags.h" #include "parser.h" #include "mailcheck.h" #include "test.h" #include "builtins/common.h" #include "builtins/builtext.h" #if defined (READLINE) # include "bashline.h" # include #endif /* READLINE */ #if defined (HISTORY) # include "bashhist.h" # include #endif /* HISTORY */ #if defined (JOB_CONTROL) # include "jobs.h" #endif /* JOB_CONTROL */ #if defined (ALIAS) # include "alias.h" #endif /* ALIAS */ #if defined (PROMPT_STRING_DECODE) # ifndef _MINIX # include # endif # include # include "maxpath.h" #endif /* PROMPT_STRING_DECODE */ #define RE_READ_TOKEN -99 #define NO_EXPANSION -100 #define YYDEBUG 0 #if defined (EXTENDED_GLOB) extern int extended_glob; #endif extern int eof_encountered; extern int no_line_editing, running_under_emacs; extern int current_command_number; extern int sourcelevel; extern int posixly_correct; extern int last_command_exit_value; extern int interrupt_immediately; extern char *shell_name, *current_host_name; extern char *dist_version; extern int patch_level; extern int dump_translatable_strings, dump_po_strings; extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin; #if defined (BUFFERED_INPUT) extern int bash_input_fd_changed; #endif extern int errno; /* **************************************************************** */ /* */ /* "Forward" declarations */ /* */ /* **************************************************************** */ #ifdef DEBUG static void debug_parser __P((int)); #endif static int yy_getc __P((void)); static int yy_ungetc __P((int)); #if defined (READLINE) static int yy_readline_get __P((void)); static int yy_readline_unget __P((int)); #endif static int yy_string_get __P((void)); static int yy_string_unget __P((int)); static int yy_stream_get __P((void)); static int yy_stream_unget __P((int)); static int shell_getc __P((int)); static void shell_ungetc __P((int)); static void discard_until __P((int)); #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) static void push_string __P((char *, int, alias_t *)); static void pop_string __P((void)); static void free_string_list __P((void)); #endif static char *read_a_line __P((int)); static char *ansiexpand __P((char *, int, int, int *)); static char *mk_msgstr __P((char *, int *)); static char *localeexpand __P((char *, int, int, int, int *)); static int reserved_word_acceptable __P((int)); static int yylex __P((void)); static int alias_expand_token __P((char *)); static int time_command_acceptable __P((void)); static int special_case_tokens __P((char *)); static int read_token __P((int)); static char *parse_matched_pair __P((int, int, int, int *, int)); #if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) static int parse_arith_cmd __P((char **)); #endif #if defined (COND_COMMAND) static COND_COM *cond_expr __P((void)); static COND_COM *cond_or __P((void)); static COND_COM *cond_and __P((void)); static COND_COM *cond_term __P((void)); static int cond_skip_newlines __P((void)); static COMMAND *parse_cond_command __P((void)); #endif static int read_token_word __P((int)); static void discard_parser_constructs __P((int)); static void report_syntax_error __P((char *)); static void handle_eof_input_unit __P((void)); static void prompt_again __P((void)); #if 0 static void reset_readline_prompt __P((void)); #endif static void print_prompt __P((void)); #if defined (HISTORY) char *history_delimiting_chars __P((void)); #endif extern int yyerror __P((const char *)); /* Default prompt strings */ char *primary_prompt = PPROMPT; char *secondary_prompt = SPROMPT; /* PROMPT_STRING_POINTER points to one of these, never to an actual string. */ char *ps1_prompt, *ps2_prompt; /* Handle on the current prompt string. Indirectly points through ps1_ or ps2_prompt. */ char **prompt_string_pointer = (char **)NULL; char *current_prompt_string; /* Non-zero means we expand aliases in commands. */ int expand_aliases = 0; /* If non-zero, the decoded prompt string undergoes parameter and variable substitution, command substitution, arithmetic substitution, string expansion, process substitution, and quote removal in decode_prompt_string. */ int promptvars = 1; /* The decoded prompt string. Used if READLINE is not defined or if editing is turned off. Analogous to current_readline_prompt. */ static char *current_decoded_prompt; /* The number of lines read from input while creating the current command. */ int current_command_line_count; /* Variables to manage the task of reading here documents, because we need to defer the reading until after a complete command has been collected. */ static REDIRECT *redir_stack[10]; int need_here_doc; /* Where shell input comes from. History expansion is performed on each line when the shell is interactive. */ static char *shell_input_line = (char *)NULL; static int shell_input_line_index; static int shell_input_line_size; /* Amount allocated for shell_input_line. */ static int shell_input_line_len; /* strlen (shell_input_line) */ /* Either zero or EOF. */ static int shell_input_line_terminator; /* The line number in a script on which a function definition starts. */ static int function_dstart; /* The line number in a script on which a function body starts. */ static int function_bstart; /* The line number in a script at which an arithmetic for command starts. */ static int arith_for_lineno; static REDIRECTEE redir; #line 232 "/usr/homes/chet/src/bash/src/parse.y" typedef union { WORD_DESC *word; /* the word that we read. */ int number; /* the number that we read. */ WORD_LIST *word_list; COMMAND *command; REDIRECT *redirect; ELEMENT element; PATTERN_LIST *pattern; } YYSTYPE; #include #ifndef __cplusplus #ifndef __STDC__ #define const #endif #endif #define YYFINAL 295 #define YYFLAG -32768 #define YYNTBASE 53 #define YYTRANSLATE(x) ((unsigned)(x) <= 295 ? yytranslate[x] : 88) static const char yytranslate[] = { 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 43, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 41, 2, 51, 52, 2, 2, 2, 48, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 42, 47, 2, 46, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 49, 45, 50, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 44 }; #if YYDEBUG != 0 static const short yyprhs[] = { 0, 0, 3, 5, 8, 10, 12, 15, 18, 21, 25, 29, 32, 36, 39, 43, 46, 50, 53, 57, 60, 64, 67, 71, 74, 78, 81, 85, 88, 92, 95, 99, 102, 105, 109, 111, 113, 115, 117, 120, 122, 125, 127, 129, 132, 134, 136, 138, 144, 150, 152, 154, 156, 158, 160, 162, 164, 171, 178, 186, 194, 205, 216, 226, 236, 244, 252, 258, 264, 271, 278, 286, 294, 305, 316, 323, 331, 338, 344, 351, 356, 358, 361, 365, 371, 379, 386, 390, 392, 396, 401, 408, 414, 416, 419, 424, 429, 435, 441, 444, 448, 450, 454, 457, 459, 462, 466, 470, 474, 479, 484, 489, 494, 499, 501, 503, 505, 507, 508, 511, 513, 516, 519, 524, 529, 533, 537, 539, 541, 544, 547, 551, 555, 560, 562, 564 }; static const short yyrhs[] = { 83, 43, 0, 43, 0, 1, 43, 0, 44, 0, 24, 0, 54, 24, 0, 46, 24, 0, 47, 24, 0, 26, 46, 24, 0, 26, 47, 24, 0, 32, 24, 0, 26, 32, 24, 0, 33, 24, 0, 26, 33, 24, 0, 34, 26, 0, 26, 34, 26, 0, 35, 26, 0, 26, 35, 26, 0, 34, 24, 0, 26, 34, 24, 0, 35, 24, 0, 26, 35, 24, 0, 37, 24, 0, 26, 37, 24, 0, 35, 48, 0, 26, 35, 48, 0, 34, 48, 0, 26, 34, 48, 0, 38, 24, 0, 26, 39, 24, 0, 39, 24, 0, 40, 24, 0, 26, 40, 24, 0, 24, 0, 25, 0, 55, 0, 55, 0, 57, 55, 0, 56, 0, 58, 56, 0, 58, 0, 60, 0, 60, 57, 0, 65, 0, 61, 0, 64, 0, 12, 78, 14, 78, 15, 0, 13, 78, 14, 78, 15, 0, 63, 0, 68, 0, 67, 0, 69, 0, 70, 0, 71, 0, 62, 0, 10, 24, 82, 14, 78, 15, 0, 10, 24, 82, 49, 78, 50, 0, 10, 24, 42, 82, 14, 78, 15, 0, 10, 24, 42, 82, 49, 78, 50, 0, 10, 24, 82, 20, 54, 81, 82, 14, 78, 15, 0, 10, 24, 82, 20, 54, 81, 82, 49, 78, 50, 0, 10, 24, 82, 20, 81, 82, 14, 78, 15, 0, 10, 24, 82, 20, 81, 82, 49, 78, 50, 0, 10, 28, 81, 82, 14, 78, 15, 0, 10, 28, 81, 82, 49, 78, 50, 0, 10, 28, 14, 78, 15, 0, 10, 28, 49, 78, 50, 0, 11, 24, 82, 14, 77, 15, 0, 11, 24, 82, 49, 77, 50, 0, 11, 24, 42, 82, 14, 77, 15, 0, 11, 24, 42, 82, 49, 77, 50, 0, 11, 24, 82, 20, 54, 81, 82, 14, 77, 15, 0, 11, 24, 82, 20, 54, 81, 82, 49, 77, 50, 0, 8, 24, 82, 20, 82, 9, 0, 8, 24, 82, 20, 75, 82, 9, 0, 8, 24, 82, 20, 73, 9, 0, 24, 51, 52, 82, 66, 0, 16, 24, 51, 52, 82, 66, 0, 16, 24, 82, 66, 0, 60, 0, 60, 57, 0, 51, 78, 52, 0, 3, 78, 4, 78, 7, 0, 3, 78, 4, 78, 5, 78, 7, 0, 3, 78, 4, 78, 72, 7, 0, 49, 78, 50, 0, 27, 0, 17, 29, 18, 0, 6, 78, 4, 78, 0, 6, 78, 4, 78, 5, 78, 0, 6, 78, 4, 78, 72, 0, 74, 0, 75, 74, 0, 82, 76, 52, 78, 0, 82, 76, 52, 82, 0, 82, 51, 76, 52, 78, 0, 82, 51, 76, 52, 82, 0, 74, 36, 0, 75, 74, 36, 0, 24, 0, 76, 45, 24, 0, 82, 79, 0, 77, 0, 82, 80, 0, 80, 43, 82, 0, 80, 41, 82, 0, 80, 42, 82, 0, 80, 30, 82, 80, 0, 80, 31, 82, 80, 0, 80, 41, 82, 80, 0, 80, 42, 82, 80, 0, 80, 43, 82, 80, 0, 85, 0, 43, 0, 42, 0, 44, 0, 0, 82, 43, 0, 84, 0, 84, 41, 0, 84, 42, 0, 84, 30, 82, 84, 0, 84, 31, 82, 84, 0, 84, 41, 84, 0, 84, 42, 84, 0, 85, 0, 86, 0, 21, 86, 0, 87, 86, 0, 87, 21, 86, 0, 21, 87, 86, 0, 86, 45, 82, 86, 0, 59, 0, 22, 0, 22, 23, 0 }; #endif #if YYDEBUG != 0 static const short yyrline[] = { 0, 282, 291, 298, 313, 323, 325, 329, 334, 339, 344, 349, 354, 359, 365, 371, 376, 381, 386, 391, 396, 401, 406, 411, 418, 425, 430, 435, 440, 445, 450, 455, 460, 465, 472, 474, 476, 480, 484, 495, 497, 501, 503, 505, 521, 525, 527, 529, 531, 533, 535, 537, 539, 541, 543, 545, 549, 551, 553, 555, 557, 559, 561, 563, 567, 569, 571, 573, 577, 581, 585, 589, 593, 597, 603, 605, 607, 611, 614, 617, 622, 624, 655, 662, 664, 666, 671, 675, 679, 683, 685, 687, 691, 692, 696, 698, 700, 702, 706, 707, 711, 713, 722, 730, 731, 737, 738, 745, 749, 751, 753, 760, 762, 764, 768, 769, 770, 773, 774, 783, 789, 798, 806, 808, 810, 817, 820, 824, 826, 831, 836, 841, 848, 851, 855, 857 }; #endif #if YYDEBUG != 0 || defined (YYERROR_VERBOSE) static const char * const yytname[] = { "$","error","$undefined.","IF","THEN", "ELSE","ELIF","FI","CASE","ESAC","FOR","SELECT","WHILE","UNTIL","DO","DONE", "FUNCTION","COND_START","COND_END","COND_ERROR","IN","BANG","TIME","TIMEOPT", "WORD","ASSIGNMENT_WORD","NUMBER","ARITH_CMD","ARITH_FOR_EXPRS","COND_CMD","AND_AND", "OR_OR","GREATER_GREATER","LESS_LESS","LESS_AND","GREATER_AND","SEMI_SEMI","LESS_LESS_MINUS", "AND_GREATER","LESS_GREATER","GREATER_BAR","'&'","';'","'\\n'","yacc_EOF","'|'", "'>'","'<'","'-'","'{'","'}'","'('","')'","inputunit","word_list","redirection", "simple_command_element","redirection_list","simple_command","command","shell_command", "for_command","arith_for_command","select_command","case_command","function_def", "function_body","subshell","if_command","group_command","arith_command","cond_command", "elif_clause","case_clause","pattern_list","case_clause_sequence","pattern", "list","compound_list","list0","list1","list_terminator","newline_list","simple_list", "simple_list1","pipeline_command","pipeline","timespec", NULL }; #endif static const short yyr1[] = { 0, 53, 53, 53, 53, 54, 54, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 56, 56, 56, 57, 57, 58, 58, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 64, 64, 64, 65, 65, 65, 66, 66, 67, 68, 68, 68, 69, 70, 71, 72, 72, 72, 73, 73, 74, 74, 74, 74, 75, 75, 76, 76, 77, 78, 78, 79, 79, 79, 80, 80, 80, 80, 80, 80, 81, 81, 81, 82, 82, 83, 83, 83, 84, 84, 84, 84, 84, 85, 85, 85, 85, 85, 86, 86, 87, 87 }; static const short yyr2[] = { 0, 2, 1, 2, 1, 1, 2, 2, 2, 3, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 1, 6, 6, 7, 7, 10, 10, 9, 9, 7, 7, 5, 5, 6, 6, 7, 7, 10, 10, 6, 7, 6, 5, 6, 4, 1, 2, 3, 5, 7, 6, 3, 1, 3, 4, 6, 5, 1, 2, 4, 4, 5, 5, 2, 3, 1, 3, 2, 1, 2, 3, 3, 3, 4, 4, 4, 4, 4, 1, 1, 1, 1, 0, 2, 1, 2, 2, 4, 4, 3, 3, 1, 1, 2, 2, 3, 3, 4, 1, 1, 2 }; static const short yydefact[] = { 0, 0, 117, 0, 0, 0, 117, 117, 0, 0, 0, 134, 34, 35, 0, 87, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 117, 117, 36, 39, 41, 133, 42, 45, 55, 49, 46, 44, 51, 50, 52, 53, 54, 0, 119, 126, 127, 0, 3, 103, 0, 0, 117, 117, 0, 117, 0, 0, 117, 0, 128, 0, 135, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 13, 19, 15, 27, 21, 17, 25, 23, 29, 31, 32, 7, 8, 0, 0, 34, 40, 37, 43, 1, 117, 117, 120, 121, 117, 0, 129, 117, 118, 102, 104, 113, 0, 117, 0, 117, 115, 114, 116, 117, 117, 117, 0, 117, 117, 0, 0, 88, 131, 117, 12, 14, 20, 16, 28, 22, 18, 26, 24, 30, 33, 9, 10, 86, 82, 38, 0, 0, 124, 125, 0, 130, 0, 117, 117, 117, 117, 117, 117, 0, 117, 0, 117, 0, 0, 0, 0, 117, 0, 117, 0, 0, 117, 80, 79, 0, 122, 123, 0, 0, 132, 117, 117, 83, 0, 0, 0, 106, 107, 105, 0, 92, 117, 0, 117, 117, 0, 5, 0, 117, 0, 66, 67, 117, 117, 117, 117, 0, 0, 0, 0, 47, 48, 0, 81, 77, 0, 0, 85, 108, 109, 110, 111, 112, 76, 98, 93, 0, 74, 100, 0, 0, 0, 0, 56, 6, 117, 0, 57, 0, 0, 0, 0, 68, 0, 117, 69, 78, 84, 117, 117, 117, 117, 99, 75, 0, 0, 117, 58, 59, 0, 117, 117, 64, 65, 70, 71, 0, 89, 0, 0, 0, 117, 101, 94, 95, 117, 117, 0, 0, 117, 117, 117, 91, 96, 97, 0, 0, 62, 63, 0, 0, 90, 60, 61, 72, 73, 0, 0, 0 }; static const short yydefgoto[] = { 293, 194, 30, 31, 94, 32, 33, 34, 35, 36, 37, 38, 39, 170, 40, 41, 42, 43, 44, 180, 186, 187, 188, 227, 51, 52, 105, 106, 116, 53, 45, 144, 107, 48, 49 }; static const short yypact[] = { 267, -30,-32768, 2, 1, 7,-32768,-32768, 13, 25, 393, 17, 29,-32768, 557,-32768, 44, 47, 48, 82, 61, 78, 83, 105,-32768,-32768, 111, 125,-32768,-32768,-32768, -32768, 178,-32768, 541,-32768,-32768,-32768,-32768,-32768,-32768, -32768,-32768,-32768,-32768, 50, 34,-32768, 113, 435,-32768, -32768, 155, 309,-32768, 118, 35, 120, 150, 151, 115, 149, 113, 519,-32768, 117, 146, 152, 107, 108, 153, 159, 164, 171, 173,-32768,-32768,-32768,-32768,-32768,-32768, -32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768, 123, 156, -32768,-32768,-32768, 541,-32768,-32768,-32768, 351, 351,-32768, 519, 113,-32768,-32768,-32768, 201,-32768, 0,-32768, 72, -32768,-32768,-32768,-32768,-32768,-32768,-32768, 97,-32768,-32768, 175, 179,-32768, 113,-32768,-32768,-32768,-32768,-32768,-32768, -32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768, -32768, 309, 309, 4, 4, 477, 113, 137,-32768,-32768, -32768,-32768,-32768,-32768, -2,-32768, 157,-32768, 183, 184, 18, 38,-32768, 185,-32768, 192, 208,-32768, 541,-32768, 179,-32768,-32768, 351, 351, 113,-32768,-32768,-32768, 222, 309, 309, 309, 309, 309, 224, 200,-32768, 12,-32768, -32768, 223,-32768, 211,-32768, 187,-32768,-32768,-32768,-32768, -32768,-32768, 225, 309, 211, 195,-32768,-32768, 179, 541, -32768, 239, 244,-32768,-32768,-32768, 20, 20, 20,-32768, -32768, 220, 15,-32768,-32768, 233, -38, 243, 209,-32768, -32768,-32768, 69,-32768, 245, 213, 246, 214,-32768, 201, -32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768, 43, 241,-32768,-32768,-32768, 102,-32768,-32768,-32768,-32768, -32768,-32768, 114, 54, 309, 309, 309,-32768,-32768,-32768, 309,-32768,-32768, 254, 221,-32768,-32768,-32768,-32768,-32768, 309, 258, 226,-32768,-32768, 259, 231,-32768,-32768,-32768, -32768,-32768, 282, 285,-32768 }; static const short yypgoto[] = {-32768, 122, -32, 255, 121,-32768,-32768, -118,-32768,-32768,-32768, -32768,-32768, -161,-32768,-32768,-32768,-32768,-32768, 31,-32768, 109,-32768, 70, -157, -6,-32768, -166, -148, -27,-32768, 11, 5, -7, 288 }; #define YYLAST 604 static const short yytable[] = { 58, 59, 93, 62, 169, 47, 203, 252, 206, 195, 211, 46, 190, 50, 253, 215, 216, 217, 218, 219, 154, 224, 89, 90, 250, 55, 54, 108, 110, 56, 118, 57, 199, 122, 96, 97, 225, 60, 240, 225, 64, 104, 102, 104, 237, 238, 232, 191, 243, 111, 149, 150, 201, 169, 61, 104, 124, 241, 104, 278, 178, 104, 141, 226, 96, 97, 226, 200, 75, 142, 143, 76, 77, 146, 78, 98, 99, 112, 113, 114, 65, 104, 155, 257, 115, 83, 156, 202, 252, 161, 162, 169, 157, 95, 147, 268, 79, 148, 171, 217, 218, 219, 84, 47, 47, 159, 80, 85, 81, 160, 145, 163, 104, 166, 167, 104, 272, 164, 258, 286, 287, 158, 181, 182, 183, 184, 185, 189, 276, 86, 82, 128, 131, 129, 132, 87, 204, 93, 204, 176, 104, 209, 177, 178, 179, 104, 165, 47, 47, 88, 192, 273, 196, 172, 173, 130, 133, 104, 100, 103, 109, 223, 117, 277, 119, 120, 121, 123, 233, 125, 126, 212, 213, 139, 204, 204, 127, 134, 141, 47, 47, 193, 2, 135, 228, 229, 145, 3, 136, 4, 5, 6, 7, 235, 236, 137, 9, 138, 197, 112, 113, 114, 91, 13, 14, 256, 15, 207, 140, 193, 16, 17, 18, 19, 263, 20, 21, 22, 23, 265, 266, 267, 104, 208, 26, 27, 271, 168, 28, 214, 29, 149, 150, 220, 198, 231, 221, 234, 230, 264, 239, 281, 151, 152, 153, 242, 244, 270, 245, 204, 204, 274, 275, 112, 113, 114, 249, 225, 254, 255, 259, 261, 280, 260, 262, 269, 282, 283, 1, 284, 2, 285, 288, 289, 291, 3, 290, 4, 5, 6, 7, 292, 294, 8, 9, 295, 205, 92, 10, 11, 210, 12, 13, 14, 15, 279, 251, 222, 63, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 0, 24, 25, 2, 26, 27, 0, 28, 3, 29, 4, 5, 6, 7, 0, 0, 8, 9, 0, 0, 0, 10, 11, 0, 12, 13, 14, 15, 0, 0, 0, 0, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 0, 104, 0, 2, 26, 27, 0, 28, 3, 29, 4, 5, 6, 7, 0, 0, 8, 9, 0, 0, 0, 10, 11, 0, 12, 13, 14, 15, 0, 0, 0, 0, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 0, 0, 0, 2, 26, 27, 0, 28, 3, 29, 4, 5, 6, 7, 0, 0, 8, 9, 0, 0, 0, 0, 11, 0, 12, 13, 14, 15, 0, 0, 0, 0, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 0, 0, 0, 2, 26, 27, 0, 28, 3, 29, 4, 5, 6, 7, 0, 0, 8, 9, 0, 0, 0, 101, 0, 0, 12, 13, 14, 15, 0, 0, 0, 0, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 0, 0, 0, 2, 26, 27, 0, 28, 3, 29, 4, 5, 6, 7, 0, 0, 8, 9, 0, 0, 0, 0, 0, 0, 12, 13, 14, 15, 0, 0, 0, 0, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 0, 104, 0, 2, 26, 27, 0, 28, 3, 29, 4, 5, 6, 7, 0, 0, 8, 9, 0, 0, 0, 0, 0, 0, 12, 13, 14, 15, 0, 0, 0, 0, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 0, 0, 0, 0, 26, 27, 14, 28, 0, 29, 0, 0, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 0, 0, 0, 0, 26, 27, 66, 67, 68, 69, 0, 70, 0, 71, 72, 0, 0, 0, 0, 0, 73, 74 }; static const short yycheck[] = { 6, 7, 34, 10, 122, 0, 163, 45, 165, 157, 171, 0, 14, 43, 52, 181, 182, 183, 184, 185, 20, 9, 28, 29, 9, 24, 24, 54, 55, 28, 57, 24, 14, 60, 30, 31, 24, 24, 204, 24, 23, 43, 49, 43, 201, 202, 194, 49, 209, 14, 30, 31, 14, 171, 29, 43, 63, 205, 43, 5, 6, 43, 94, 51, 30, 31, 51, 49, 24, 96, 97, 24, 24, 100, 26, 41, 42, 42, 43, 44, 51, 43, 109, 14, 49, 24, 14, 49, 45, 116, 117, 209, 20, 43, 101, 52, 48, 103, 125, 265, 266, 267, 24, 98, 99, 111, 24, 24, 26, 115, 99, 14, 43, 119, 120, 43, 14, 20, 49, 276, 277, 49, 149, 150, 151, 152, 153, 154, 14, 24, 48, 24, 24, 26, 26, 24, 163, 169, 165, 146, 43, 168, 5, 6, 7, 43, 49, 142, 143, 24, 156, 49, 158, 142, 143, 48, 48, 43, 45, 4, 42, 188, 42, 49, 14, 14, 51, 18, 195, 52, 24, 177, 178, 50, 201, 202, 24, 24, 210, 174, 175, 24, 3, 24, 190, 191, 175, 8, 24, 10, 11, 12, 13, 199, 200, 24, 17, 24, 15, 42, 43, 44, 24, 25, 26, 232, 27, 15, 52, 24, 32, 33, 34, 35, 241, 37, 38, 39, 40, 246, 247, 248, 43, 15, 46, 47, 253, 52, 49, 7, 51, 30, 31, 9, 50, 24, 36, 50, 15, 245, 15, 268, 41, 42, 43, 50, 7, 253, 4, 276, 277, 257, 258, 42, 43, 44, 36, 24, 15, 50, 15, 15, 268, 50, 50, 24, 272, 273, 1, 15, 3, 50, 278, 15, 15, 8, 50, 10, 11, 12, 13, 50, 0, 16, 17, 0, 164, 32, 21, 22, 169, 24, 25, 26, 27, 264, 226, 188, 10, 32, 33, 34, 35, -1, 37, 38, 39, 40, -1, -1, 43, 44, 3, 46, 47, -1, 49, 8, 51, 10, 11, 12, 13, -1, -1, 16, 17, -1, -1, -1, 21, 22, -1, 24, 25, 26, 27, -1, -1, -1, -1, 32, 33, 34, 35, -1, 37, 38, 39, 40, -1, -1, 43, -1, 3, 46, 47, -1, 49, 8, 51, 10, 11, 12, 13, -1, -1, 16, 17, -1, -1, -1, 21, 22, -1, 24, 25, 26, 27, -1, -1, -1, -1, 32, 33, 34, 35, -1, 37, 38, 39, 40, -1, -1, -1, -1, 3, 46, 47, -1, 49, 8, 51, 10, 11, 12, 13, -1, -1, 16, 17, -1, -1, -1, -1, 22, -1, 24, 25, 26, 27, -1, -1, -1, -1, 32, 33, 34, 35, -1, 37, 38, 39, 40, -1, -1, -1, -1, 3, 46, 47, -1, 49, 8, 51, 10, 11, 12, 13, -1, -1, 16, 17, -1, -1, -1, 21, -1, -1, 24, 25, 26, 27, -1, -1, -1, -1, 32, 33, 34, 35, -1, 37, 38, 39, 40, -1, -1, -1, -1, 3, 46, 47, -1, 49, 8, 51, 10, 11, 12, 13, -1, -1, 16, 17, -1, -1, -1, -1, -1, -1, 24, 25, 26, 27, -1, -1, -1, -1, 32, 33, 34, 35, -1, 37, 38, 39, 40, -1, -1, 43, -1, 3, 46, 47, -1, 49, 8, 51, 10, 11, 12, 13, -1, -1, 16, 17, -1, -1, -1, -1, -1, -1, 24, 25, 26, 27, -1, -1, -1, -1, 32, 33, 34, 35, -1, 37, 38, 39, 40, -1, -1, -1, -1, -1, 46, 47, 26, 49, -1, 51, -1, -1, 32, 33, 34, 35, -1, 37, 38, 39, 40, -1, -1, -1, -1, -1, 46, 47, 32, 33, 34, 35, -1, 37, -1, 39, 40, -1, -1, -1, -1, -1, 46, 47 }; /* -*-C-*- Note some compilers choke on comments on `#line' lines. */ #line 3 "/usr/local/share/bison.simple" /* This file comes from bison-1.28. */ /* Skeleton output parser for bison, Copyright (C) 1984, 1989, 1990 Free Software Foundation, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* As a special exception, when this file is copied by Bison into a Bison output file, you may use that output file without restriction. This special exception was added by the Free Software Foundation in version 1.24 of Bison. */ /* This is the parser code that is written into each bison parser when the %semantic_parser declaration is not specified in the grammar. It was written by Richard Stallman by simplifying the hairy parser used when %semantic_parser is specified. */ #ifndef YYSTACK_USE_ALLOCA #ifdef alloca #define YYSTACK_USE_ALLOCA #else /* alloca not defined */ #ifdef __GNUC__ #define YYSTACK_USE_ALLOCA #define alloca __builtin_alloca #else /* not GNU C. */ #if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi) || (defined (__sun) && defined (__i386)) #define YYSTACK_USE_ALLOCA #include #else /* not sparc */ /* We think this test detects Watcom and Microsoft C. */ /* This used to test MSDOS, but that is a bad idea since that symbol is in the user namespace. */ #if (defined (_MSDOS) || defined (_MSDOS_)) && !defined (__TURBOC__) #if 0 /* No need for malloc.h, which pollutes the namespace; instead, just don't use alloca. */ #include #endif #else /* not MSDOS, or __TURBOC__ */ #if defined(_AIX) /* I don't know what this was needed for, but it pollutes the namespace. So I turned it off. rms, 2 May 1997. */ /* #include */ #pragma alloca #define YYSTACK_USE_ALLOCA #else /* not MSDOS, or __TURBOC__, or _AIX */ #if 0 #ifdef __hpux /* haible@ilog.fr says this works for HPUX 9.05 and up, and on HPUX 10. Eventually we can turn this on. */ #define YYSTACK_USE_ALLOCA #define alloca __builtin_alloca #endif /* __hpux */ #endif #endif /* not _AIX */ #endif /* not MSDOS, or __TURBOC__ */ #endif /* not sparc */ #endif /* not GNU C */ #endif /* alloca not defined */ #endif /* YYSTACK_USE_ALLOCA not defined */ #ifdef YYSTACK_USE_ALLOCA #define YYSTACK_ALLOC alloca #else #define YYSTACK_ALLOC malloc #endif /* Note: there must be only one dollar sign in this file. It is replaced by the list of actions, each action as one case of the switch. */ #define yyerrok (yyerrstatus = 0) #define yyclearin (yychar = YYEMPTY) #define YYEMPTY -2 #define YYEOF 0 #define YYACCEPT goto yyacceptlab #define YYABORT goto yyabortlab #define YYERROR goto yyerrlab1 /* Like YYERROR except do call yyerror. This remains here temporarily to ease the transition to the new meaning of YYERROR, for GCC. Once GCC version 2 has supplanted version 1, this can go. */ #define YYFAIL goto yyerrlab #define YYRECOVERING() (!!yyerrstatus) #define YYBACKUP(token, value) \ do \ if (yychar == YYEMPTY && yylen == 1) \ { yychar = (token), yylval = (value); \ yychar1 = YYTRANSLATE (yychar); \ YYPOPSTACK; \ goto yybackup; \ } \ else \ { yyerror ("syntax error: cannot back up"); YYERROR; } \ while (0) #define YYTERROR 1 #define YYERRCODE 256 #ifndef YYPURE #define YYLEX yylex() #endif #ifdef YYPURE #ifdef YYLSP_NEEDED #ifdef YYLEX_PARAM #define YYLEX yylex(&yylval, &yylloc, YYLEX_PARAM) #else #define YYLEX yylex(&yylval, &yylloc) #endif #else /* not YYLSP_NEEDED */ #ifdef YYLEX_PARAM #define YYLEX yylex(&yylval, YYLEX_PARAM) #else #define YYLEX yylex(&yylval) #endif #endif /* not YYLSP_NEEDED */ #endif /* If nonreentrant, generate the variables here */ #ifndef YYPURE int yychar; /* the lookahead symbol */ YYSTYPE yylval; /* the semantic value of the */ /* lookahead symbol */ #ifdef YYLSP_NEEDED YYLTYPE yylloc; /* location data for the lookahead */ /* symbol */ #endif int yynerrs; /* number of parse errors so far */ #endif /* not YYPURE */ #if YYDEBUG != 0 int yydebug; /* nonzero means print parse trace */ /* Since this is uninitialized, it does not stop multiple parsers from coexisting. */ #endif /* YYINITDEPTH indicates the initial size of the parser's stacks */ #ifndef YYINITDEPTH #define YYINITDEPTH 200 #endif /* YYMAXDEPTH is the maximum size the stacks can grow to (effective only if the built-in stack extension method is used). */ #if YYMAXDEPTH == 0 #undef YYMAXDEPTH #endif #ifndef YYMAXDEPTH #define YYMAXDEPTH 10000 #endif /* Define __yy_memcpy. Note that the size argument should be passed with type unsigned int, because that is what the non-GCC definitions require. With GCC, __builtin_memcpy takes an arg of type size_t, but it can handle unsigned int. */ #if __GNUC__ > 1 /* GNU C and GNU C++ define this. */ #define __yy_memcpy(TO,FROM,COUNT) __builtin_memcpy(TO,FROM,COUNT) #else /* not GNU C or C++ */ #ifndef __cplusplus /* This is the most reliable way to avoid incompatibilities in available built-in functions on various systems. */ static void __yy_memcpy (to, from, count) char *to; char *from; unsigned int count; { register char *f = from; register char *t = to; register int i = count; while (i-- > 0) *t++ = *f++; } #else /* __cplusplus */ /* This is the most reliable way to avoid incompatibilities in available built-in functions on various systems. */ static void __yy_memcpy (char *to, char *from, unsigned int count) { register char *t = to; register char *f = from; register int i = count; while (i-- > 0) *t++ = *f++; } #endif #endif #line 217 "/usr/local/share/bison.simple" /* The user can define YYPARSE_PARAM as the name of an argument to be passed into yyparse. The argument should have type void *. It should actually point to an object. Grammar actions can access the variable by casting it to the proper pointer type. */ #ifdef YYPARSE_PARAM #ifdef __cplusplus #define YYPARSE_PARAM_ARG void *YYPARSE_PARAM #define YYPARSE_PARAM_DECL #else /* not __cplusplus */ #define YYPARSE_PARAM_ARG YYPARSE_PARAM #define YYPARSE_PARAM_DECL void *YYPARSE_PARAM; #endif /* not __cplusplus */ #else /* not YYPARSE_PARAM */ #define YYPARSE_PARAM_ARG #define YYPARSE_PARAM_DECL #endif /* not YYPARSE_PARAM */ /* Prevent warning if -Wstrict-prototypes. */ #ifdef __GNUC__ #ifdef YYPARSE_PARAM int yyparse (void *); #else int yyparse (void); #endif #endif int yyparse(YYPARSE_PARAM_ARG) YYPARSE_PARAM_DECL { register int yystate; register int yyn; register short *yyssp; register YYSTYPE *yyvsp; int yyerrstatus; /* number of tokens to shift before error messages enabled */ int yychar1 = 0; /* lookahead token as an internal (translated) token number */ short yyssa[YYINITDEPTH]; /* the state stack */ YYSTYPE yyvsa[YYINITDEPTH]; /* the semantic value stack */ short *yyss = yyssa; /* refer to the stacks thru separate pointers */ YYSTYPE *yyvs = yyvsa; /* to allow yyoverflow to reallocate them elsewhere */ #ifdef YYLSP_NEEDED YYLTYPE yylsa[YYINITDEPTH]; /* the location stack */ YYLTYPE *yyls = yylsa; YYLTYPE *yylsp; #define YYPOPSTACK (yyvsp--, yyssp--, yylsp--) #else #define YYPOPSTACK (yyvsp--, yyssp--) #endif int yystacksize = YYINITDEPTH; int yyfree_stacks = 0; #ifdef YYPURE int yychar; YYSTYPE yylval; int yynerrs; #ifdef YYLSP_NEEDED YYLTYPE yylloc; #endif #endif YYSTYPE yyval; /* the variable used to return */ /* semantic values from the action */ /* routines */ int yylen; #if YYDEBUG != 0 if (yydebug) fprintf(stderr, "Starting parse\n"); #endif yystate = 0; yyerrstatus = 0; yynerrs = 0; yychar = YYEMPTY; /* Cause a token to be read. */ /* Initialize stack pointers. Waste one element of value and location stack so that they stay on the same level as the state stack. The wasted elements are never initialized. */ yyssp = yyss - 1; yyvsp = yyvs; #ifdef YYLSP_NEEDED yylsp = yyls; #endif /* Push a new state, which is found in yystate . */ /* In all cases, when you get here, the value and location stacks have just been pushed. so pushing a state here evens the stacks. */ yynewstate: *++yyssp = yystate; if (yyssp >= yyss + yystacksize - 1) { /* Give user a chance to reallocate the stack */ /* Use copies of these so that the &'s don't force the real ones into memory. */ YYSTYPE *yyvs1 = yyvs; short *yyss1 = yyss; #ifdef YYLSP_NEEDED YYLTYPE *yyls1 = yyls; #endif /* Get the current used size of the three stacks, in elements. */ int size = yyssp - yyss + 1; #ifdef yyoverflow /* Each stack pointer address is followed by the size of the data in use in that stack, in bytes. */ #ifdef YYLSP_NEEDED /* This used to be a conditional around just the two extra args, but that might be undefined if yyoverflow is a macro. */ yyoverflow("parser stack overflow", &yyss1, size * sizeof (*yyssp), &yyvs1, size * sizeof (*yyvsp), &yyls1, size * sizeof (*yylsp), &yystacksize); #else yyoverflow("parser stack overflow", &yyss1, size * sizeof (*yyssp), &yyvs1, size * sizeof (*yyvsp), &yystacksize); #endif yyss = yyss1; yyvs = yyvs1; #ifdef YYLSP_NEEDED yyls = yyls1; #endif #else /* no yyoverflow */ /* Extend the stack our own way. */ if (yystacksize >= YYMAXDEPTH) { yyerror("parser stack overflow"); if (yyfree_stacks) { free (yyss); free (yyvs); #ifdef YYLSP_NEEDED free (yyls); #endif } return 2; } yystacksize *= 2; if (yystacksize > YYMAXDEPTH) yystacksize = YYMAXDEPTH; #ifndef YYSTACK_USE_ALLOCA yyfree_stacks = 1; #endif yyss = (short *) YYSTACK_ALLOC (yystacksize * sizeof (*yyssp)); __yy_memcpy ((char *)yyss, (char *)yyss1, size * (unsigned int) sizeof (*yyssp)); yyvs = (YYSTYPE *) YYSTACK_ALLOC (yystacksize * sizeof (*yyvsp)); __yy_memcpy ((char *)yyvs, (char *)yyvs1, size * (unsigned int) sizeof (*yyvsp)); #ifdef YYLSP_NEEDED yyls = (YYLTYPE *) YYSTACK_ALLOC (yystacksize * sizeof (*yylsp)); __yy_memcpy ((char *)yyls, (char *)yyls1, size * (unsigned int) sizeof (*yylsp)); #endif #endif /* no yyoverflow */ yyssp = yyss + size - 1; yyvsp = yyvs + size - 1; #ifdef YYLSP_NEEDED yylsp = yyls + size - 1; #endif #if YYDEBUG != 0 if (yydebug) fprintf(stderr, "Stack size increased to %d\n", yystacksize); #endif if (yyssp >= yyss + yystacksize - 1) YYABORT; } #if YYDEBUG != 0 if (yydebug) fprintf(stderr, "Entering state %d\n", yystate); #endif goto yybackup; yybackup: /* Do appropriate processing given the current state. */ /* Read a lookahead token if we need one and don't already have one. */ /* yyresume: */ /* First try to decide what to do without reference to lookahead token. */ yyn = yypact[yystate]; if (yyn == YYFLAG) goto yydefault; /* Not known => get a lookahead token if don't already have one. */ /* yychar is either YYEMPTY or YYEOF or a valid token in external form. */ if (yychar == YYEMPTY) { #if YYDEBUG != 0 if (yydebug) fprintf(stderr, "Reading a token: "); #endif yychar = YYLEX; } /* Convert token to internal form (in yychar1) for indexing tables with */ if (yychar <= 0) /* This means end of input. */ { yychar1 = 0; yychar = YYEOF; /* Don't call YYLEX any more */ #if YYDEBUG != 0 if (yydebug) fprintf(stderr, "Now at end of input.\n"); #endif } else { yychar1 = YYTRANSLATE(yychar); #if YYDEBUG != 0 if (yydebug) { fprintf (stderr, "Next token is %d (%s", yychar, yytname[yychar1]); /* Give the individual parser a way to print the precise meaning of a token, for further debugging info. */ #ifdef YYPRINT YYPRINT (stderr, yychar, yylval); #endif fprintf (stderr, ")\n"); } #endif } yyn += yychar1; if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1) goto yydefault; yyn = yytable[yyn]; /* yyn is what to do for this token type in this state. Negative => reduce, -yyn is rule number. Positive => shift, yyn is new state. New state is final state => don't bother to shift, just return success. 0, or most negative number => error. */ if (yyn < 0) { if (yyn == YYFLAG) goto yyerrlab; yyn = -yyn; goto yyreduce; } else if (yyn == 0) goto yyerrlab; if (yyn == YYFINAL) YYACCEPT; /* Shift the lookahead token. */ #if YYDEBUG != 0 if (yydebug) fprintf(stderr, "Shifting token %d (%s), ", yychar, yytname[yychar1]); #endif /* Discard the token being shifted unless it is eof. */ if (yychar != YYEOF) yychar = YYEMPTY; *++yyvsp = yylval; #ifdef YYLSP_NEEDED *++yylsp = yylloc; #endif /* count tokens shifted since error; after three, turn off error status. */ if (yyerrstatus) yyerrstatus--; yystate = yyn; goto yynewstate; /* Do the default action for the current state. */ yydefault: yyn = yydefact[yystate]; if (yyn == 0) goto yyerrlab; /* Do a reduction. yyn is the number of a rule to reduce with. */ yyreduce: yylen = yyr2[yyn]; if (yylen > 0) yyval = yyvsp[1-yylen]; /* implement default value of the action */ #if YYDEBUG != 0 if (yydebug) { int i; fprintf (stderr, "Reducing via rule %d (line %d), ", yyn, yyrline[yyn]); /* Print the symbols being reduced, and their result. */ for (i = yyprhs[yyn]; yyrhs[i] > 0; i++) fprintf (stderr, "%s ", yytname[yyrhs[i]]); fprintf (stderr, " -> %s\n", yytname[yyr1[yyn]]); } #endif switch (yyn) { case 1: #line 283 "/usr/homes/chet/src/bash/src/parse.y" { /* Case of regular command. Discard the error safety net,and return the command just parsed. */ global_command = yyvsp[-1].command; eof_encountered = 0; discard_parser_constructs (0); YYACCEPT; ; break;} case 2: #line 292 "/usr/homes/chet/src/bash/src/parse.y" { /* Case of regular command, but not a very interesting one. Return a NULL command. */ global_command = (COMMAND *)NULL; YYACCEPT; ; break;} case 3: #line 299 "/usr/homes/chet/src/bash/src/parse.y" { /* Error during parsing. Return NULL command. */ global_command = (COMMAND *)NULL; eof_encountered = 0; discard_parser_constructs (1); if (interactive) { YYACCEPT; } else { YYABORT; } ; break;} case 4: #line 314 "/usr/homes/chet/src/bash/src/parse.y" { /* Case of EOF seen by itself. Do ignoreeof or not. */ global_command = (COMMAND *)NULL; handle_eof_input_unit (); YYACCEPT; ; break;} case 5: #line 324 "/usr/homes/chet/src/bash/src/parse.y" { yyval.word_list = make_word_list (yyvsp[0].word, (WORD_LIST *)NULL); ; break;} case 6: #line 326 "/usr/homes/chet/src/bash/src/parse.y" { yyval.word_list = make_word_list (yyvsp[0].word, yyvsp[-1].word_list); ; break;} case 7: #line 330 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (1, r_output_direction, redir); ; break;} case 8: #line 335 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (0, r_input_direction, redir); ; break;} case 9: #line 340 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (yyvsp[-2].number, r_output_direction, redir); ; break;} case 10: #line 345 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (yyvsp[-2].number, r_input_direction, redir); ; break;} case 11: #line 350 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (1, r_appending_to, redir); ; break;} case 12: #line 355 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (yyvsp[-2].number, r_appending_to, redir); ; break;} case 13: #line 360 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (0, r_reading_until, redir); redir_stack[need_here_doc++] = yyval.redirect; ; break;} case 14: #line 366 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (yyvsp[-2].number, r_reading_until, redir); redir_stack[need_here_doc++] = yyval.redirect; ; break;} case 15: #line 372 "/usr/homes/chet/src/bash/src/parse.y" { redir.dest = yyvsp[0].number; yyval.redirect = make_redirection (0, r_duplicating_input, redir); ; break;} case 16: #line 377 "/usr/homes/chet/src/bash/src/parse.y" { redir.dest = yyvsp[0].number; yyval.redirect = make_redirection (yyvsp[-2].number, r_duplicating_input, redir); ; break;} case 17: #line 382 "/usr/homes/chet/src/bash/src/parse.y" { redir.dest = yyvsp[0].number; yyval.redirect = make_redirection (1, r_duplicating_output, redir); ; break;} case 18: #line 387 "/usr/homes/chet/src/bash/src/parse.y" { redir.dest = yyvsp[0].number; yyval.redirect = make_redirection (yyvsp[-2].number, r_duplicating_output, redir); ; break;} case 19: #line 392 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (0, r_duplicating_input_word, redir); ; break;} case 20: #line 397 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (yyvsp[-2].number, r_duplicating_input_word, redir); ; break;} case 21: #line 402 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (1, r_duplicating_output_word, redir); ; break;} case 22: #line 407 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (yyvsp[-2].number, r_duplicating_output_word, redir); ; break;} case 23: #line 412 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (0, r_deblank_reading_until, redir); redir_stack[need_here_doc++] = yyval.redirect; ; break;} case 24: #line 419 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (yyvsp[-2].number, r_deblank_reading_until, redir); redir_stack[need_here_doc++] = yyval.redirect; ; break;} case 25: #line 426 "/usr/homes/chet/src/bash/src/parse.y" { redir.dest = 0; yyval.redirect = make_redirection (1, r_close_this, redir); ; break;} case 26: #line 431 "/usr/homes/chet/src/bash/src/parse.y" { redir.dest = 0; yyval.redirect = make_redirection (yyvsp[-2].number, r_close_this, redir); ; break;} case 27: #line 436 "/usr/homes/chet/src/bash/src/parse.y" { redir.dest = 0; yyval.redirect = make_redirection (0, r_close_this, redir); ; break;} case 28: #line 441 "/usr/homes/chet/src/bash/src/parse.y" { redir.dest = 0; yyval.redirect = make_redirection (yyvsp[-2].number, r_close_this, redir); ; break;} case 29: #line 446 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (1, r_err_and_out, redir); ; break;} case 30: #line 451 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (yyvsp[-2].number, r_input_output, redir); ; break;} case 31: #line 456 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (0, r_input_output, redir); ; break;} case 32: #line 461 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (1, r_output_force, redir); ; break;} case 33: #line 466 "/usr/homes/chet/src/bash/src/parse.y" { redir.filename = yyvsp[0].word; yyval.redirect = make_redirection (yyvsp[-2].number, r_output_force, redir); ; break;} case 34: #line 473 "/usr/homes/chet/src/bash/src/parse.y" { yyval.element.word = yyvsp[0].word; yyval.element.redirect = 0; ; break;} case 35: #line 475 "/usr/homes/chet/src/bash/src/parse.y" { yyval.element.word = yyvsp[0].word; yyval.element.redirect = 0; ; break;} case 36: #line 477 "/usr/homes/chet/src/bash/src/parse.y" { yyval.element.redirect = yyvsp[0].redirect; yyval.element.word = 0; ; break;} case 37: #line 481 "/usr/homes/chet/src/bash/src/parse.y" { yyval.redirect = yyvsp[0].redirect; ; break;} case 38: #line 485 "/usr/homes/chet/src/bash/src/parse.y" { register REDIRECT *t; for (t = yyvsp[-1].redirect; t->next; t = t->next) ; t->next = yyvsp[0].redirect; yyval.redirect = yyvsp[-1].redirect; ; break;} case 39: #line 496 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_simple_command (yyvsp[0].element, (COMMAND *)NULL); ; break;} case 40: #line 498 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_simple_command (yyvsp[0].element, yyvsp[-1].command); ; break;} case 41: #line 502 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = clean_simple_command (yyvsp[0].command); ; break;} case 42: #line 504 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 43: #line 506 "/usr/homes/chet/src/bash/src/parse.y" { COMMAND *tc; tc = yyvsp[-1].command; if (tc->redirects) { register REDIRECT *t; for (t = tc->redirects; t->next; t = t->next) ; t->next = yyvsp[0].redirect; } else tc->redirects = yyvsp[0].redirect; yyval.command = yyvsp[-1].command; ; break;} case 44: #line 522 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 45: #line 526 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 46: #line 528 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 47: #line 530 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_while_command (yyvsp[-3].command, yyvsp[-1].command); ; break;} case 48: #line 532 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_until_command (yyvsp[-3].command, yyvsp[-1].command); ; break;} case 49: #line 534 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 50: #line 536 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 51: #line 538 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 52: #line 540 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 53: #line 542 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 54: #line 544 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 55: #line 546 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 56: #line 550 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_for_command (yyvsp[-4].word, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), yyvsp[-1].command); ; break;} case 57: #line 552 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_for_command (yyvsp[-4].word, add_string_to_list ("$@", (WORD_LIST *)NULL), yyvsp[-1].command); ; break;} case 58: #line 554 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_for_command (yyvsp[-5].word, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), yyvsp[-1].command); ; break;} case 59: #line 556 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_for_command (yyvsp[-5].word, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), yyvsp[-1].command); ; break;} case 60: #line 558 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_for_command (yyvsp[-8].word, REVERSE_LIST (yyvsp[-5].word_list, WORD_LIST *), yyvsp[-1].command); ; break;} case 61: #line 560 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_for_command (yyvsp[-8].word, REVERSE_LIST (yyvsp[-5].word_list, WORD_LIST *), yyvsp[-1].command); ; break;} case 62: #line 562 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_for_command (yyvsp[-7].word, (WORD_LIST *)NULL, yyvsp[-1].command); ; break;} case 63: #line 564 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_for_command (yyvsp[-7].word, (WORD_LIST *)NULL, yyvsp[-1].command); ; break;} case 64: #line 568 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_arith_for_command (yyvsp[-5].word_list, yyvsp[-1].command, arith_for_lineno); ; break;} case 65: #line 570 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_arith_for_command (yyvsp[-5].word_list, yyvsp[-1].command, arith_for_lineno); ; break;} case 66: #line 572 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_arith_for_command (yyvsp[-3].word_list, yyvsp[-1].command, arith_for_lineno); ; break;} case 67: #line 574 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_arith_for_command (yyvsp[-3].word_list, yyvsp[-1].command, arith_for_lineno); ; break;} case 68: #line 578 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_select_command (yyvsp[-4].word, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), yyvsp[-1].command); ; break;} case 69: #line 582 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_select_command (yyvsp[-4].word, add_string_to_list ("$@", (WORD_LIST *)NULL), yyvsp[-1].command); ; break;} case 70: #line 586 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_select_command (yyvsp[-5].word, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), yyvsp[-1].command); ; break;} case 71: #line 590 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_select_command (yyvsp[-5].word, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), yyvsp[-1].command); ; break;} case 72: #line 594 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_select_command (yyvsp[-8].word, (WORD_LIST *)reverse_list (yyvsp[-5].word_list), yyvsp[-1].command); ; break;} case 73: #line 598 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_select_command (yyvsp[-8].word, (WORD_LIST *)reverse_list (yyvsp[-5].word_list), yyvsp[-1].command); ; break;} case 74: #line 604 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_case_command (yyvsp[-4].word, (PATTERN_LIST *)NULL); ; break;} case 75: #line 606 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_case_command (yyvsp[-5].word, yyvsp[-2].pattern); ; break;} case 76: #line 608 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_case_command (yyvsp[-4].word, yyvsp[-1].pattern); ; break;} case 77: #line 612 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_function_def (yyvsp[-4].word, yyvsp[0].command, function_dstart, function_bstart); ; break;} case 78: #line 615 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_function_def (yyvsp[-4].word, yyvsp[0].command, function_dstart, function_bstart); ; break;} case 79: #line 618 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_function_def (yyvsp[-2].word, yyvsp[0].command, function_dstart, function_bstart); ; break;} case 80: #line 623 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 81: #line 625 "/usr/homes/chet/src/bash/src/parse.y" { COMMAND *tc; tc = yyvsp[-1].command; /* According to Posix.2 3.9.5, redirections specified after the body of a function should be attached to the function and performed when the function is executed, not as part of the function definition command. */ /* XXX - I don't think it matters, but we might want to change this in the future to avoid problems differentiating between a function definition with a redirection and a function definition containing a single command with a redirection. The two are semantically equivalent, though -- the only difference is in how the command printing code displays the redirections. */ if (tc->redirects) { register REDIRECT *t; for (t = tc->redirects; t->next; t = t->next) ; t->next = yyvsp[0].redirect; } else tc->redirects = yyvsp[0].redirect; yyval.command = yyvsp[-1].command; ; break;} case 82: #line 656 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_subshell_command (yyvsp[-1].command); yyval.command->flags |= CMD_WANT_SUBSHELL; ; break;} case 83: #line 663 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_if_command (yyvsp[-3].command, yyvsp[-1].command, (COMMAND *)NULL); ; break;} case 84: #line 665 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_if_command (yyvsp[-5].command, yyvsp[-3].command, yyvsp[-1].command); ; break;} case 85: #line 667 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_if_command (yyvsp[-4].command, yyvsp[-2].command, yyvsp[-1].command); ; break;} case 86: #line 672 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_group_command (yyvsp[-1].command); ; break;} case 87: #line 676 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_arith_command (yyvsp[0].word_list); ; break;} case 88: #line 680 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[-1].command; ; break;} case 89: #line 684 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_if_command (yyvsp[-2].command, yyvsp[0].command, (COMMAND *)NULL); ; break;} case 90: #line 686 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_if_command (yyvsp[-4].command, yyvsp[-2].command, yyvsp[0].command); ; break;} case 91: #line 688 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = make_if_command (yyvsp[-3].command, yyvsp[-1].command, yyvsp[0].command); ; break;} case 93: #line 693 "/usr/homes/chet/src/bash/src/parse.y" { yyvsp[0].pattern->next = yyvsp[-1].pattern; yyval.pattern = yyvsp[0].pattern; ; break;} case 94: #line 697 "/usr/homes/chet/src/bash/src/parse.y" { yyval.pattern = make_pattern_list (yyvsp[-2].word_list, yyvsp[0].command); ; break;} case 95: #line 699 "/usr/homes/chet/src/bash/src/parse.y" { yyval.pattern = make_pattern_list (yyvsp[-2].word_list, (COMMAND *)NULL); ; break;} case 96: #line 701 "/usr/homes/chet/src/bash/src/parse.y" { yyval.pattern = make_pattern_list (yyvsp[-2].word_list, yyvsp[0].command); ; break;} case 97: #line 703 "/usr/homes/chet/src/bash/src/parse.y" { yyval.pattern = make_pattern_list (yyvsp[-2].word_list, (COMMAND *)NULL); ; break;} case 99: #line 708 "/usr/homes/chet/src/bash/src/parse.y" { yyvsp[-1].pattern->next = yyvsp[-2].pattern; yyval.pattern = yyvsp[-1].pattern; ; break;} case 100: #line 712 "/usr/homes/chet/src/bash/src/parse.y" { yyval.word_list = make_word_list (yyvsp[0].word, (WORD_LIST *)NULL); ; break;} case 101: #line 714 "/usr/homes/chet/src/bash/src/parse.y" { yyval.word_list = make_word_list (yyvsp[0].word, yyvsp[-2].word_list); ; break;} case 102: #line 723 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; if (need_here_doc) gather_here_documents (); ; break;} case 104: #line 732 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 106: #line 739 "/usr/homes/chet/src/bash/src/parse.y" { if (yyvsp[-2].command->type == cm_connection) yyval.command = connect_async_list (yyvsp[-2].command, (COMMAND *)NULL, '&'); else yyval.command = command_connect (yyvsp[-2].command, (COMMAND *)NULL, '&'); ; break;} case 108: #line 750 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = command_connect (yyvsp[-3].command, yyvsp[0].command, AND_AND); ; break;} case 109: #line 752 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = command_connect (yyvsp[-3].command, yyvsp[0].command, OR_OR); ; break;} case 110: #line 754 "/usr/homes/chet/src/bash/src/parse.y" { if (yyvsp[-3].command->type == cm_connection) yyval.command = connect_async_list (yyvsp[-3].command, yyvsp[0].command, '&'); else yyval.command = command_connect (yyvsp[-3].command, yyvsp[0].command, '&'); ; break;} case 111: #line 761 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = command_connect (yyvsp[-3].command, yyvsp[0].command, ';'); ; break;} case 112: #line 763 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = command_connect (yyvsp[-3].command, yyvsp[0].command, ';'); ; break;} case 113: #line 765 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 119: #line 784 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; if (need_here_doc) gather_here_documents (); ; break;} case 120: #line 790 "/usr/homes/chet/src/bash/src/parse.y" { if (yyvsp[-1].command->type == cm_connection) yyval.command = connect_async_list (yyvsp[-1].command, (COMMAND *)NULL, '&'); else yyval.command = command_connect (yyvsp[-1].command, (COMMAND *)NULL, '&'); if (need_here_doc) gather_here_documents (); ; break;} case 121: #line 799 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[-1].command; if (need_here_doc) gather_here_documents (); ; break;} case 122: #line 807 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = command_connect (yyvsp[-3].command, yyvsp[0].command, AND_AND); ; break;} case 123: #line 809 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = command_connect (yyvsp[-3].command, yyvsp[0].command, OR_OR); ; break;} case 124: #line 811 "/usr/homes/chet/src/bash/src/parse.y" { if (yyvsp[-2].command->type == cm_connection) yyval.command = connect_async_list (yyvsp[-2].command, yyvsp[0].command, '&'); else yyval.command = command_connect (yyvsp[-2].command, yyvsp[0].command, '&'); ; break;} case 125: #line 818 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = command_connect (yyvsp[-2].command, yyvsp[0].command, ';'); ; break;} case 126: #line 821 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 127: #line 825 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 128: #line 827 "/usr/homes/chet/src/bash/src/parse.y" { yyvsp[0].command->flags |= CMD_INVERT_RETURN; yyval.command = yyvsp[0].command; ; break;} case 129: #line 832 "/usr/homes/chet/src/bash/src/parse.y" { yyvsp[0].command->flags |= yyvsp[-1].number; yyval.command = yyvsp[0].command; ; break;} case 130: #line 837 "/usr/homes/chet/src/bash/src/parse.y" { yyvsp[0].command->flags |= yyvsp[-2].number|CMD_INVERT_RETURN; yyval.command = yyvsp[0].command; ; break;} case 131: #line 842 "/usr/homes/chet/src/bash/src/parse.y" { yyvsp[0].command->flags |= yyvsp[-1].number|CMD_INVERT_RETURN; yyval.command = yyvsp[0].command; ; break;} case 132: #line 850 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = command_connect (yyvsp[-3].command, yyvsp[0].command, '|'); ; break;} case 133: #line 852 "/usr/homes/chet/src/bash/src/parse.y" { yyval.command = yyvsp[0].command; ; break;} case 134: #line 856 "/usr/homes/chet/src/bash/src/parse.y" { yyval.number = CMD_TIME_PIPELINE; ; break;} case 135: #line 858 "/usr/homes/chet/src/bash/src/parse.y" { yyval.number = CMD_TIME_PIPELINE|CMD_TIME_POSIX; ; break;} } /* the action file gets copied in in place of this dollarsign */ #line 543 "/usr/local/share/bison.simple" yyvsp -= yylen; yyssp -= yylen; #ifdef YYLSP_NEEDED yylsp -= yylen; #endif #if YYDEBUG != 0 if (yydebug) { short *ssp1 = yyss - 1; fprintf (stderr, "state stack now"); while (ssp1 != yyssp) fprintf (stderr, " %d", *++ssp1); fprintf (stderr, "\n"); } #endif *++yyvsp = yyval; #ifdef YYLSP_NEEDED yylsp++; if (yylen == 0) { yylsp->first_line = yylloc.first_line; yylsp->first_column = yylloc.first_column; yylsp->last_line = (yylsp-1)->last_line; yylsp->last_column = (yylsp-1)->last_column; yylsp->text = 0; } else { yylsp->last_line = (yylsp+yylen-1)->last_line; yylsp->last_column = (yylsp+yylen-1)->last_column; } #endif /* Now "shift" the result of the reduction. Determine what state that goes to, based on the state we popped back to and the rule number reduced by. */ yyn = yyr1[yyn]; yystate = yypgoto[yyn - YYNTBASE] + *yyssp; if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp) yystate = yytable[yystate]; else yystate = yydefgoto[yyn - YYNTBASE]; goto yynewstate; yyerrlab: /* here on detecting error */ if (! yyerrstatus) /* If not already recovering from an error, report this error. */ { ++yynerrs; #ifdef YYERROR_VERBOSE yyn = yypact[yystate]; if (yyn > YYFLAG && yyn < YYLAST) { int size = 0; char *msg; int x, count; count = 0; /* Start X at -yyn if nec to avoid negative indexes in yycheck. */ for (x = (yyn < 0 ? -yyn : 0); x < (sizeof(yytname) / sizeof(char *)); x++) if (yycheck[x + yyn] == x) size += strlen(yytname[x]) + 15, count++; msg = (char *) malloc(size + 15); if (msg != 0) { strcpy(msg, "parse error"); if (count < 5) { count = 0; for (x = (yyn < 0 ? -yyn : 0); x < (sizeof(yytname) / sizeof(char *)); x++) if (yycheck[x + yyn] == x) { strcat(msg, count == 0 ? ", expecting `" : " or `"); strcat(msg, yytname[x]); strcat(msg, "'"); count++; } } yyerror(msg); free(msg); } else yyerror ("parse error; also virtual memory exceeded"); } else #endif /* YYERROR_VERBOSE */ yyerror("parse error"); } goto yyerrlab1; yyerrlab1: /* here on error raised explicitly by an action */ if (yyerrstatus == 3) { /* if just tried and failed to reuse lookahead token after an error, discard it. */ /* return failure if at end of input */ if (yychar == YYEOF) YYABORT; #if YYDEBUG != 0 if (yydebug) fprintf(stderr, "Discarding token %d (%s).\n", yychar, yytname[yychar1]); #endif yychar = YYEMPTY; } /* Else will try to reuse lookahead token after shifting the error token. */ yyerrstatus = 3; /* Each real token shifted decrements this */ goto yyerrhandle; yyerrdefault: /* current state does not do anything special for the error token. */ #if 0 /* This is wrong; only states that explicitly want error tokens should shift them. */ yyn = yydefact[yystate]; /* If its default is to accept any token, ok. Otherwise pop it.*/ if (yyn) goto yydefault; #endif yyerrpop: /* pop the current state because it cannot handle the error token */ if (yyssp == yyss) YYABORT; yyvsp--; yystate = *--yyssp; #ifdef YYLSP_NEEDED yylsp--; #endif #if YYDEBUG != 0 if (yydebug) { short *ssp1 = yyss - 1; fprintf (stderr, "Error: state stack now"); while (ssp1 != yyssp) fprintf (stderr, " %d", *++ssp1); fprintf (stderr, "\n"); } #endif yyerrhandle: yyn = yypact[yystate]; if (yyn == YYFLAG) goto yyerrdefault; yyn += YYTERROR; if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR) goto yyerrdefault; yyn = yytable[yyn]; if (yyn < 0) { if (yyn == YYFLAG) goto yyerrpop; yyn = -yyn; goto yyreduce; } else if (yyn == 0) goto yyerrpop; if (yyn == YYFINAL) YYACCEPT; #if YYDEBUG != 0 if (yydebug) fprintf(stderr, "Shifting error token, "); #endif *++yyvsp = yylval; #ifdef YYLSP_NEEDED *++yylsp = yylloc; #endif yystate = yyn; goto yynewstate; yyacceptlab: /* YYACCEPT comes here. */ if (yyfree_stacks) { free (yyss); free (yyvs); #ifdef YYLSP_NEEDED free (yyls); #endif } return 0; yyabortlab: /* YYABORT comes here. */ if (yyfree_stacks) { free (yyss); free (yyvs); #ifdef YYLSP_NEEDED free (yyls); #endif } return 1; } #line 860 "/usr/homes/chet/src/bash/src/parse.y" /* Possible states for the parser that require it to do special things. */ #define PST_CASEPAT 0x001 /* in a case pattern list */ #define PST_ALEXPNEXT 0x002 /* expand next word for aliases */ #define PST_ALLOWOPNBRC 0x004 /* allow open brace for function def */ #define PST_NEEDCLOSBRC 0x008 /* need close brace */ #define PST_DBLPAREN 0x010 /* double-paren parsing */ #define PST_SUBSHELL 0x020 /* ( ... ) subshell */ #define PST_CMDSUBST 0x040 /* $( ... ) command substitution */ #define PST_CASESTMT 0x080 /* parsing a case statement */ #define PST_CONDCMD 0x100 /* parsing a [[...]] command */ #define PST_CONDEXPR 0x200 /* parsing the guts of [[...]] */ #define PST_ARITHFOR 0x400 /* parsing an arithmetic for command */ /* Initial size to allocate for tokens, and the amount to grow them by. */ #define TOKEN_DEFAULT_INITIAL_SIZE 496 #define TOKEN_DEFAULT_GROW_SIZE 512 /* The token currently being read. */ static int current_token; /* The last read token, or NULL. read_token () uses this for context checking. */ static int last_read_token; /* The token read prior to last_read_token. */ static int token_before_that; /* The token read prior to token_before_that. */ static int two_tokens_ago; /* If non-zero, it is the token that we want read_token to return regardless of what text is (or isn't) present to be read. This is reset by read_token. If token_to_read == WORD or ASSIGNMENT_WORD, yylval.word should be set to word_desc_to_read. */ static int token_to_read; static WORD_DESC *word_desc_to_read; /* The current parser state. */ static int parser_state; /* Global var is non-zero when end of file has been reached. */ int EOF_Reached = 0; #ifdef DEBUG static void debug_parser (i) int i; { #if YYDEBUG != 0 yydebug = i; #endif } #endif /* yy_getc () returns the next available character from input or EOF. yy_ungetc (c) makes `c' the next character to read. init_yy_io (get, unget, type, location) makes the function GET the installed function for getting the next character, makes UNGET the installed function for un-getting a character, sets the type of stream (either string or file) from TYPE, and makes LOCATION point to where the input is coming from. */ /* Unconditionally returns end-of-file. */ int return_EOF () { return (EOF); } /* Variable containing the current get and unget functions. See ./input.h for a clearer description. */ BASH_INPUT bash_input; /* Set all of the fields in BASH_INPUT to NULL. Free bash_input.name if it is non-null, avoiding a memory leak. */ void initialize_bash_input () { bash_input.type = st_none; FREE (bash_input.name); bash_input.name = (char *)NULL; bash_input.location.file = (FILE *)NULL; bash_input.location.string = (char *)NULL; bash_input.getter = (sh_cget_func_t *)NULL; bash_input.ungetter = (sh_cunget_func_t *)NULL; } /* Set the contents of the current bash input stream from GET, UNGET, TYPE, NAME, and LOCATION. */ void init_yy_io (get, unget, type, name, location) sh_cget_func_t *get; sh_cunget_func_t *unget; enum stream_type type; const char *name; INPUT_STREAM location; { bash_input.type = type; FREE (bash_input.name); bash_input.name = name ? savestring (name) : (char *)NULL; /* XXX */ #if defined (CRAY) memcpy((char *)&bash_input.location.string, (char *)&location.string, sizeof(location)); #else bash_input.location = location; #endif bash_input.getter = get; bash_input.ungetter = unget; } /* Call this to get the next character of input. */ static int yy_getc () { return (*(bash_input.getter)) (); } /* Call this to unget C. That is, to make C the next character to be read. */ static int yy_ungetc (c) int c; { return (*(bash_input.ungetter)) (c); } #if defined (BUFFERED_INPUT) #ifdef INCLUDE_UNUSED int input_file_descriptor () { switch (bash_input.type) { case st_stream: return (fileno (bash_input.location.file)); case st_bstream: return (bash_input.location.buffered_fd); case st_stdin: default: return (fileno (stdin)); } } #endif #endif /* BUFFERED_INPUT */ /* **************************************************************** */ /* */ /* Let input be read from readline (). */ /* */ /* **************************************************************** */ #if defined (READLINE) char *current_readline_prompt = (char *)NULL; char *current_readline_line = (char *)NULL; int current_readline_line_index = 0; static int yy_readline_get () { SigHandler *old_sigint; int line_len; unsigned char c; if (!current_readline_line) { if (!bash_readline_initialized) initialize_readline (); #if defined (JOB_CONTROL) if (job_control) give_terminal_to (shell_pgrp, 0); #endif /* JOB_CONTROL */ old_sigint = (SigHandler *)NULL; if (signal_is_ignored (SIGINT) == 0) { old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler); interrupt_immediately++; } current_readline_line = readline (current_readline_prompt ? current_readline_prompt : ""); if (signal_is_ignored (SIGINT) == 0 && old_sigint) { interrupt_immediately--; set_signal_handler (SIGINT, old_sigint); } #if 0 /* Reset the prompt to the decoded value of prompt_string_pointer. */ reset_readline_prompt (); #endif if (current_readline_line == 0) return (EOF); current_readline_line_index = 0; line_len = strlen (current_readline_line); current_readline_line = (char *)xrealloc (current_readline_line, 2 + line_len); current_readline_line[line_len++] = '\n'; current_readline_line[line_len] = '\0'; } if (current_readline_line[current_readline_line_index] == 0) { free (current_readline_line); current_readline_line = (char *)NULL; return (yy_readline_get ()); } else { c = current_readline_line[current_readline_line_index++]; return (c); } } static int yy_readline_unget (c) int c; { if (current_readline_line_index && current_readline_line) current_readline_line[--current_readline_line_index] = c; return (c); } void with_input_from_stdin () { INPUT_STREAM location; if (bash_input.type != st_stdin && stream_on_stack (st_stdin) == 0) { location.string = current_readline_line; init_yy_io (yy_readline_get, yy_readline_unget, st_stdin, "readline stdin", location); } } #else /* !READLINE */ void with_input_from_stdin () { with_input_from_stream (stdin, "stdin"); } #endif /* !READLINE */ /* **************************************************************** */ /* */ /* Let input come from STRING. STRING is zero terminated. */ /* */ /* **************************************************************** */ static int yy_string_get () { register char *string; register unsigned char c; string = bash_input.location.string; /* If the string doesn't exist, or is empty, EOF found. */ if (string && *string) { c = *string++; bash_input.location.string = string; return (c); } else return (EOF); } static int yy_string_unget (c) int c; { *(--bash_input.location.string) = c; return (c); } void with_input_from_string (string, name) char *string; const char *name; { INPUT_STREAM location; location.string = string; init_yy_io (yy_string_get, yy_string_unget, st_string, name, location); } /* **************************************************************** */ /* */ /* Let input come from STREAM. */ /* */ /* **************************************************************** */ /* These two functions used to test the value of the HAVE_RESTARTABLE_SYSCALLS define, and just use getc/ungetc if it was defined, but since bash installs its signal handlers without the SA_RESTART flag, some signals (like SIGCHLD, SIGWINCH, etc.) received during a read(2) will not cause the read to be restarted. We need to restart it ourselves. */ static int yy_stream_get () { int result; result = EOF; if (bash_input.location.file) result = getc_with_restart (bash_input.location.file); return (result); } static int yy_stream_unget (c) int c; { return (ungetc_with_restart (c, bash_input.location.file)); } void with_input_from_stream (stream, name) FILE *stream; const char *name; { INPUT_STREAM location; location.file = stream; init_yy_io (yy_stream_get, yy_stream_unget, st_stream, name, location); } typedef struct stream_saver { struct stream_saver *next; BASH_INPUT bash_input; int line; #if defined (BUFFERED_INPUT) BUFFERED_STREAM *bstream; #endif /* BUFFERED_INPUT */ } STREAM_SAVER; /* The globally known line number. */ int line_number = 0; #if defined (COND_COMMAND) static int cond_lineno; static int cond_token; #endif STREAM_SAVER *stream_list = (STREAM_SAVER *)NULL; void push_stream (reset_lineno) int reset_lineno; { STREAM_SAVER *saver = (STREAM_SAVER *)xmalloc (sizeof (STREAM_SAVER)); xbcopy ((char *)&bash_input, (char *)&(saver->bash_input), sizeof (BASH_INPUT)); #if defined (BUFFERED_INPUT) saver->bstream = (BUFFERED_STREAM *)NULL; /* If we have a buffered stream, clear out buffers[fd]. */ if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0) saver->bstream = set_buffered_stream (bash_input.location.buffered_fd, (BUFFERED_STREAM *)NULL); #endif /* BUFFERED_INPUT */ saver->line = line_number; bash_input.name = (char *)NULL; saver->next = stream_list; stream_list = saver; EOF_Reached = 0; if (reset_lineno) line_number = 0; } void pop_stream () { if (!stream_list) EOF_Reached = 1; else { STREAM_SAVER *saver = stream_list; EOF_Reached = 0; stream_list = stream_list->next; init_yy_io (saver->bash_input.getter, saver->bash_input.ungetter, saver->bash_input.type, saver->bash_input.name, saver->bash_input.location); #if defined (BUFFERED_INPUT) /* If we have a buffered stream, restore buffers[fd]. */ /* If the input file descriptor was changed while this was on the save stack, update the buffered fd to the new file descriptor and re-establish the buffer <-> bash_input fd correspondence. */ if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0) { if (bash_input_fd_changed) { bash_input_fd_changed = 0; if (default_buffered_input >= 0) { bash_input.location.buffered_fd = default_buffered_input; saver->bstream->b_fd = default_buffered_input; SET_CLOSE_ON_EXEC (default_buffered_input); } } /* XXX could free buffered stream returned as result here. */ set_buffered_stream (bash_input.location.buffered_fd, saver->bstream); } #endif /* BUFFERED_INPUT */ line_number = saver->line; FREE (saver->bash_input.name); free (saver); } } /* Return 1 if a stream of type TYPE is saved on the stack. */ int stream_on_stack (type) enum stream_type type; { register STREAM_SAVER *s; for (s = stream_list; s; s = s->next) if (s->bash_input.type == type) return 1; return 0; } /* Save the current token state and return it in a malloced array. */ int * save_token_state () { int *ret; ret = (int *)xmalloc (3 * sizeof (int)); ret[0] = last_read_token; ret[1] = token_before_that; ret[2] = two_tokens_ago; return ret; } void restore_token_state (ts) int *ts; { if (ts == 0) return; last_read_token = ts[0]; token_before_that = ts[1]; two_tokens_ago = ts[2]; } /* * This is used to inhibit alias expansion and reserved word recognition * inside case statement pattern lists. A `case statement pattern list' is: * * everything between the `in' in a `case word in' and the next ')' * or `esac' * everything between a `;;' and the next `)' or `esac' */ #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) #if !defined (ALIAS) typedef void *alias_t; #endif #define END_OF_ALIAS 0 /* * Pseudo-global variables used in implementing token-wise alias expansion. */ /* * Pushing and popping strings. This works together with shell_getc to * implement alias expansion on a per-token basis. */ typedef struct string_saver { struct string_saver *next; int expand_alias; /* Value to set expand_alias to when string is popped. */ char *saved_line; #if defined (ALIAS) alias_t *expander; /* alias that caused this line to be pushed. */ #endif int saved_line_size, saved_line_index, saved_line_terminator; } STRING_SAVER; STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL; /* * Push the current shell_input_line onto a stack of such lines and make S * the current input. Used when expanding aliases. EXPAND is used to set * the value of expand_next_token when the string is popped, so that the * word after the alias in the original line is handled correctly when the * alias expands to multiple words. TOKEN is the token that was expanded * into S; it is saved and used to prevent infinite recursive expansion. */ static void push_string (s, expand, ap) char *s; int expand; alias_t *ap; { STRING_SAVER *temp = (STRING_SAVER *)xmalloc (sizeof (STRING_SAVER)); temp->expand_alias = expand; temp->saved_line = shell_input_line; temp->saved_line_size = shell_input_line_size; temp->saved_line_index = shell_input_line_index; temp->saved_line_terminator = shell_input_line_terminator; #if defined (ALIAS) temp->expander = ap; #endif temp->next = pushed_string_list; pushed_string_list = temp; #if defined (ALIAS) if (ap) ap->flags |= AL_BEINGEXPANDED; #endif shell_input_line = s; shell_input_line_size = strlen (s); shell_input_line_index = 0; shell_input_line_terminator = '\0'; parser_state &= ~PST_ALEXPNEXT; } /* * Make the top of the pushed_string stack be the current shell input. * Only called when there is something on the stack. Called from shell_getc * when it thinks it has consumed the string generated by an alias expansion * and needs to return to the original input line. */ static void pop_string () { STRING_SAVER *t; FREE (shell_input_line); shell_input_line = pushed_string_list->saved_line; shell_input_line_index = pushed_string_list->saved_line_index; shell_input_line_size = pushed_string_list->saved_line_size; shell_input_line_terminator = pushed_string_list->saved_line_terminator; if (pushed_string_list->expand_alias) parser_state |= PST_ALEXPNEXT; else parser_state &= ~PST_ALEXPNEXT; t = pushed_string_list; pushed_string_list = pushed_string_list->next; #if defined (ALIAS) if (t->expander) t->expander->flags &= ~AL_BEINGEXPANDED; #endif free ((char *)t); } static void free_string_list () { register STRING_SAVER *t, *t1; for (t = pushed_string_list; t; ) { t1 = t->next; FREE (t->saved_line); #if defined (ALIAS) if (t->expander) t->expander->flags &= ~AL_BEINGEXPANDED; #endif free ((char *)t); t = t1; } pushed_string_list = (STRING_SAVER *)NULL; } #endif /* ALIAS || DPAREN_ARITHMETIC */ /* Return a line of text, taken from wherever yylex () reads input. If there is no more input, then we return NULL. If REMOVE_QUOTED_NEWLINE is non-zero, we remove unquoted \ pairs. This is used by read_secondary_line to read here documents. */ static char * read_a_line (remove_quoted_newline) int remove_quoted_newline; { static char *line_buffer = (char *)NULL; static int buffer_size = 0; int indx = 0, c, peekc, pass_next; #if defined (READLINE) if (interactive && bash_input.type != st_string && no_line_editing) #else if (interactive && bash_input.type != st_string) #endif print_prompt (); pass_next = 0; while (1) { c = yy_getc (); /* Allow immediate exit if interrupted during input. */ QUIT; /* Ignore null bytes in input. */ if (c == 0) { #if 0 internal_warning ("read_a_line: ignored null byte in input"); #endif continue; } /* If there is no more input, then we return NULL. */ if (c == EOF) { if (interactive && bash_input.type == st_stream) clearerr (stdin); if (indx == 0) return ((char *)NULL); c = '\n'; } /* `+2' in case the final character in the buffer is a newline. */ RESIZE_MALLOCED_BUFFER (line_buffer, indx, 2, buffer_size, 128); /* IF REMOVE_QUOTED_NEWLINES is non-zero, we are reading a here document with an unquoted delimiter. In this case, the line will be expanded as if it were in double quotes. We allow a backslash to escape the next character, but we need to treat the backslash specially only if a backslash quoting a backslash-newline pair appears in the line. */ if (pass_next) { line_buffer[indx++] = c; pass_next = 0; } else if (c == '\\' && remove_quoted_newline) { peekc = yy_getc (); if (peekc == '\n') continue; /* Make the unquoted \ pair disappear. */ else { yy_ungetc (peekc); pass_next = 1; line_buffer[indx++] = c; /* Preserve the backslash. */ } } else line_buffer[indx++] = c; if (c == '\n') { line_buffer[indx] = '\0'; return (line_buffer); } } } /* Return a line as in read_a_line (), but insure that the prompt is the secondary prompt. This is used to read the lines of a here document. REMOVE_QUOTED_NEWLINE is non-zero if we should remove newlines quoted with backslashes while reading the line. It is non-zero unless the delimiter of the here document was quoted. */ char * read_secondary_line (remove_quoted_newline) int remove_quoted_newline; { prompt_string_pointer = &ps2_prompt; prompt_again (); return (read_a_line (remove_quoted_newline)); } /* **************************************************************** */ /* */ /* YYLEX () */ /* */ /* **************************************************************** */ /* Reserved words. These are only recognized as the first word of a command. */ STRING_INT_ALIST word_token_alist[] = { { "if", IF }, { "then", THEN }, { "else", ELSE }, { "elif", ELIF }, { "fi", FI }, { "case", CASE }, { "esac", ESAC }, { "for", FOR }, #if defined (SELECT_COMMAND) { "select", SELECT }, #endif { "while", WHILE }, { "until", UNTIL }, { "do", DO }, { "done", DONE }, { "in", IN }, { "function", FUNCTION }, #if defined (COMMAND_TIMING) { "time", TIME }, #endif { "{", '{' }, { "}", '}' }, { "!", BANG }, #if defined (COND_COMMAND) { "[[", COND_START }, { "]]", COND_END }, #endif { (char *)NULL, 0} }; /* XXX - we should also have an alist with strings for other tokens, so we can give more descriptive error messages. Look at y.tab.h for the other tokens. */ /* These are used by read_token_word, but appear up here so that shell_getc can use them to decide when to add otherwise blank lines to the history. */ /* The primary delimiter stack. */ struct dstack dstack = { (char *)NULL, 0, 0 }; /* A temporary delimiter stack to be used when decoding prompt strings. This is needed because command substitutions in prompt strings (e.g., PS2) can screw up the parser's quoting state. */ static struct dstack temp_dstack = { (char *)NULL, 0, 0 }; /* Macro for accessing the top delimiter on the stack. Returns the delimiter or zero if none. */ #define current_delimiter(ds) \ (ds.delimiter_depth ? ds.delimiters[ds.delimiter_depth - 1] : 0) #define push_delimiter(ds, character) \ do \ { \ if (ds.delimiter_depth + 2 > ds.delimiter_space) \ ds.delimiters = (char *)xrealloc \ (ds.delimiters, (ds.delimiter_space += 10) * sizeof (char)); \ ds.delimiters[ds.delimiter_depth] = character; \ ds.delimiter_depth++; \ } \ while (0) #define pop_delimiter(ds) ds.delimiter_depth-- /* Return the next shell input character. This always reads characters from shell_input_line; when that line is exhausted, it is time to read the next line. This is called by read_token when the shell is processing normal command input. */ /* This implements one-character lookahead/lookbehind across physical input lines, to avoid something being lost because it's pushed back with shell_ungetc when we're at the start of a line. */ static int eol_ungetc_lookahead = 0; static int shell_getc (remove_quoted_newline) int remove_quoted_newline; { register int i; int c; unsigned char uc; static int mustpop = 0; QUIT; if (eol_ungetc_lookahead) { c = eol_ungetc_lookahead; eol_ungetc_lookahead = 0; return (c); } #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) /* If shell_input_line[shell_input_line_index] == 0, but there is something on the pushed list of strings, then we don't want to go off and get another line. We let the code down below handle it. */ if (!shell_input_line || ((!shell_input_line[shell_input_line_index]) && (pushed_string_list == (STRING_SAVER *)NULL))) #else /* !ALIAS && !DPAREN_ARITHMETIC */ if (!shell_input_line || !shell_input_line[shell_input_line_index]) #endif /* !ALIAS && !DPAREN_ARITHMETIC */ { line_number++; restart_read: /* Allow immediate exit if interrupted during input. */ QUIT; i = 0; shell_input_line_terminator = 0; #if defined (JOB_CONTROL) /* This can cause a problem when reading a command as the result of a trap, when the trap is called from flush_child. This call had better not cause jobs to disappear from the job table in that case, or we will have big trouble. */ notify_and_cleanup (); #else /* !JOB_CONTROL */ cleanup_dead_jobs (); #endif /* !JOB_CONTROL */ #if defined (READLINE) if (interactive && bash_input.type != st_string && no_line_editing) #else if (interactive && bash_input.type != st_string) #endif print_prompt (); if (bash_input.type == st_stream) clearerr (stdin); while (1) { c = yy_getc (); /* Allow immediate exit if interrupted during input. */ QUIT; if (c == '\0') { #if 0 internal_warning ("shell_getc: ignored null byte in input"); #endif continue; } RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256); if (c == EOF) { if (bash_input.type == st_stream) clearerr (stdin); if (i == 0) shell_input_line_terminator = EOF; shell_input_line[i] = '\0'; break; } shell_input_line[i++] = c; if (c == '\n') { shell_input_line[--i] = '\0'; current_command_line_count++; break; } } shell_input_line_index = 0; shell_input_line_len = i; /* == strlen (shell_input_line) */ #if defined (HISTORY) if (remember_on_history && shell_input_line && shell_input_line[0]) { char *expansions; # if defined (BANG_HISTORY) int old_hist; /* If the current delimiter is a single quote, we should not be performing history expansion, even if we're on a different line from the original single quote. */ old_hist = history_expansion_inhibited; if (current_delimiter (dstack) == '\'') history_expansion_inhibited = 1; # endif expansions = pre_process_line (shell_input_line, 1, 1); # if defined (BANG_HISTORY) history_expansion_inhibited = old_hist; # endif if (expansions != shell_input_line) { free (shell_input_line); shell_input_line = expansions; shell_input_line_len = shell_input_line ? strlen (shell_input_line) : 0; if (!shell_input_line_len) current_command_line_count--; /* We have to force the xrealloc below because we don't know the true allocated size of shell_input_line anymore. */ shell_input_line_size = shell_input_line_len; } } /* Try to do something intelligent with blank lines encountered while entering multi-line commands. XXX - this is grotesque */ else if (remember_on_history && shell_input_line && shell_input_line[0] == '\0' && current_command_line_count > 1) { if (current_delimiter (dstack)) /* We know shell_input_line[0] == 0 and we're reading some sort of quoted string. This means we've got a line consisting of only a newline in a quoted string. We want to make sure this line gets added to the history. */ maybe_add_history (shell_input_line); else { char *hdcs; hdcs = history_delimiting_chars (); if (hdcs && hdcs[0] == ';') maybe_add_history (shell_input_line); } } #endif /* HISTORY */ if (shell_input_line) { /* Lines that signify the end of the shell's input should not be echoed. */ if (echo_input_at_read && (shell_input_line[0] || shell_input_line_terminator != EOF)) fprintf (stderr, "%s\n", shell_input_line); } else { shell_input_line_size = 0; prompt_string_pointer = ¤t_prompt_string; prompt_again (); goto restart_read; } /* Add the newline to the end of this string, iff the string does not already end in an EOF character. */ if (shell_input_line_terminator != EOF) { if (shell_input_line_len + 3 > shell_input_line_size) shell_input_line = (char *)xrealloc (shell_input_line, 1 + (shell_input_line_size += 2)); shell_input_line[shell_input_line_len] = '\n'; shell_input_line[shell_input_line_len + 1] = '\0'; } } uc = shell_input_line[shell_input_line_index]; if (uc) shell_input_line_index++; if (uc == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n') { prompt_again (); line_number++; goto restart_read; } #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) /* If UC is NULL, we have reached the end of the current input string. If pushed_string_list is non-empty, it's time to pop to the previous string because we have fully consumed the result of the last alias expansion. Do it transparently; just return the next character of the string popped to. */ if (!uc && (pushed_string_list != (STRING_SAVER *)NULL)) { if (mustpop) { pop_string (); uc = shell_input_line[shell_input_line_index]; if (uc) shell_input_line_index++; mustpop--; } else { mustpop++; uc = ' '; } } #endif /* ALIAS || DPAREN_ARITHMETIC */ if (!uc && shell_input_line_terminator == EOF) return ((shell_input_line_index != 0) ? '\n' : EOF); return (uc); } /* Put C back into the input for the shell. */ static void shell_ungetc (c) int c; { if (shell_input_line && shell_input_line_index) shell_input_line[--shell_input_line_index] = c; else eol_ungetc_lookahead = c; } #ifdef INCLUDE_UNUSED /* Back the input pointer up by one, effectively `ungetting' a character. */ static void shell_ungetchar () { if (shell_input_line && shell_input_line_index) shell_input_line_index--; } #endif /* Discard input until CHARACTER is seen, then push that character back onto the input stream. */ static void discard_until (character) int character; { int c; while ((c = shell_getc (0)) != EOF && c != character) ; if (c != EOF) shell_ungetc (c); } void execute_prompt_command (command) char *command; { sh_builtin_func_t *temp_last, *temp_this; char *last_lastarg; int temp_exit_value, temp_eof_encountered; temp_last = last_shell_builtin; temp_this = this_shell_builtin; temp_exit_value = last_command_exit_value; temp_eof_encountered = eof_encountered; last_lastarg = get_string_value ("_"); if (last_lastarg) last_lastarg = savestring (last_lastarg); parse_and_execute (savestring (command), "PROMPT_COMMAND", SEVAL_NONINT|SEVAL_NOHIST); last_shell_builtin = temp_last; this_shell_builtin = temp_this; last_command_exit_value = temp_exit_value; eof_encountered = temp_eof_encountered; bind_variable ("_", last_lastarg); FREE (last_lastarg); if (token_to_read == '\n') /* reset_parser was called */ token_to_read = 0; } /* Place to remember the token. We try to keep the buffer at a reasonable size, but it can grow. */ static char *token = (char *)NULL; /* Current size of the token buffer. */ static int token_buffer_size; /* Command to read_token () explaining what we want it to do. */ #define READ 0 #define RESET 1 #define prompt_is_ps1 \ (!prompt_string_pointer || prompt_string_pointer == &ps1_prompt) /* Function for yyparse to call. yylex keeps track of the last two tokens read, and calls read_token. */ static int yylex () { if (interactive && (current_token == 0 || current_token == '\n')) { /* Before we print a prompt, we might have to check mailboxes. We do this only if it is time to do so. Notice that only here is the mail alarm reset; nothing takes place in check_mail () except the checking of mail. Please don't change this. */ if (prompt_is_ps1 && time_to_check_mail ()) { check_mail (); reset_mail_timer (); } /* Avoid printing a prompt if we're not going to read anything, e.g. after resetting the parser with read_token (RESET). */ if (token_to_read == 0 && interactive) prompt_again (); } two_tokens_ago = token_before_that; token_before_that = last_read_token; last_read_token = current_token; current_token = read_token (READ); return (current_token); } /* When non-zero, we have read the required tokens which allow ESAC to be the next one read. */ static int esacs_needed_count; void gather_here_documents () { int r = 0; while (need_here_doc) { make_here_document (redir_stack[r++]); need_here_doc--; } } /* When non-zero, an open-brace used to create a group is awaiting a close brace partner. */ static int open_brace_count; #define command_token_position(token) \ (((token) == ASSIGNMENT_WORD) || \ ((token) != SEMI_SEMI && reserved_word_acceptable(token))) #define assignment_acceptable(token) command_token_position(token) && \ ((parser_state & PST_CASEPAT) == 0) /* Check to see if TOKEN is a reserved word and return the token value if it is. */ #define CHECK_FOR_RESERVED_WORD(tok) \ do { \ if (!dollar_present && !quoted && \ reserved_word_acceptable (last_read_token)) \ { \ int i; \ for (i = 0; word_token_alist[i].word != (char *)NULL; i++) \ if (STREQ (tok, word_token_alist[i].word)) \ { \ if ((parser_state & PST_CASEPAT) && (word_token_alist[i].token != ESAC)) \ break; \ if (word_token_alist[i].token == TIME) \ break; \ if (word_token_alist[i].token == ESAC) \ parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \ else if (word_token_alist[i].token == CASE) \ parser_state |= PST_CASESTMT; \ else if (word_token_alist[i].token == COND_END) \ parser_state &= ~(PST_CONDCMD|PST_CONDEXPR); \ else if (word_token_alist[i].token == COND_START) \ parser_state |= PST_CONDCMD; \ else if (word_token_alist[i].token == '{') \ open_brace_count++; \ else if (word_token_alist[i].token == '}' && open_brace_count) \ open_brace_count--; \ return (word_token_alist[i].token); \ } \ } \ } while (0) #if defined (ALIAS) /* OK, we have a token. Let's try to alias expand it, if (and only if) it's eligible. It is eligible for expansion if the shell is in interactive mode, and the token is unquoted and the last token read was a command separator (or expand_next_token is set), and we are currently processing an alias (pushed_string_list is non-empty) and this token is not the same as the current or any previously processed alias. Special cases that disqualify: In a pattern list in a case statement (parser_state & PST_CASEPAT). */ static int alias_expand_token (tokstr) char *tokstr; { char *expanded; alias_t *ap; if (((parser_state & PST_ALEXPNEXT) || command_token_position (last_read_token)) && (parser_state & PST_CASEPAT) == 0) { ap = find_alias (tokstr); /* Currently expanding this token. */ if (ap && (ap->flags & AL_BEINGEXPANDED)) return (NO_EXPANSION); expanded = ap ? savestring (ap->value) : (char *)NULL; if (expanded) { push_string (expanded, ap->flags & AL_EXPANDNEXT, ap); return (RE_READ_TOKEN); } else /* This is an eligible token that does not have an expansion. */ return (NO_EXPANSION); } return (NO_EXPANSION); } #endif /* ALIAS */ static int time_command_acceptable () { #if defined (COMMAND_TIMING) switch (last_read_token) { case 0: case ';': case '\n': case AND_AND: case OR_OR: case '&': case DO: case THEN: case ELSE: case '{': /* } */ case '(': /* ) */ return 1; default: return 0; } #else return 0; #endif /* COMMAND_TIMING */ } /* Handle special cases of token recognition: IN is recognized if the last token was WORD and the token before that was FOR or CASE or SELECT. DO is recognized if the last token was WORD and the token before that was FOR or SELECT. ESAC is recognized if the last token caused `esacs_needed_count' to be set `{' is recognized if the last token as WORD and the token before that was FUNCTION, or if we just parsed an arithmetic `for' command. `}' is recognized if there is an unclosed `{' present. `-p' is returned as TIMEOPT if the last read token was TIME. ']]' is returned as COND_END if the parser is currently parsing a conditional expression ((parser_state & PST_CONDEXPR) != 0) `time' is returned as TIME if and only if it is immediately preceded by one of `;', `\n', `||', `&&', or `&'. */ static int special_case_tokens (tokstr) char *tokstr; { if ((last_read_token == WORD) && #if defined (SELECT_COMMAND) ((token_before_that == FOR) || (token_before_that == CASE) || (token_before_that == SELECT)) && #else ((token_before_that == FOR) || (token_before_that == CASE)) && #endif (tokstr[0] == 'i' && tokstr[1] == 'n' && tokstr[2] == 0)) { if (token_before_that == CASE) { parser_state |= PST_CASEPAT; esacs_needed_count++; } return (IN); } if (last_read_token == WORD && #if defined (SELECT_COMMAND) (token_before_that == FOR || token_before_that == SELECT) && #else (token_before_that == FOR) && #endif (tokstr[0] == 'd' && tokstr[1] == 'o' && tokstr[2] == '\0')) return (DO); /* Ditto for ESAC in the CASE case. Specifically, this handles "case word in esac", which is a legal construct, certainly because someone will pass an empty arg to the case construct, and we don't want it to barf. Of course, we should insist that the case construct has at least one pattern in it, but the designers disagree. */ if (esacs_needed_count) { esacs_needed_count--; if (STREQ (tokstr, "esac")) { parser_state &= ~PST_CASEPAT; return (ESAC); } } /* The start of a shell function definition. */ if (parser_state & PST_ALLOWOPNBRC) { parser_state &= ~PST_ALLOWOPNBRC; if (tokstr[0] == '{' && tokstr[1] == '\0') /* } */ { open_brace_count++; function_bstart = line_number; return ('{'); /* } */ } } /* We allow a `do' after a for ((...)) without an intervening list_terminator */ if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == 'd' && tokstr[1] == 'o' && !tokstr[2]) return (DO); if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == '{' && tokstr[1] == '\0') /* } */ { open_brace_count++; return ('{'); /* } */ } if (open_brace_count && reserved_word_acceptable (last_read_token) && tokstr[0] == '}' && !tokstr[1]) { open_brace_count--; /* { */ return ('}'); } #if defined (COMMAND_TIMING) /* Handle -p after `time'. */ if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2]) return (TIMEOPT); #endif #if defined (COMMAND_TIMING) if (STREQ (token, "time") && ((parser_state & PST_CASEPAT) == 0) && time_command_acceptable ()) return (TIME); #endif /* COMMAND_TIMING */ #if defined (COND_COMMAND) /* [[ */ if ((parser_state & PST_CONDEXPR) && tokstr[0] == ']' && tokstr[1] == ']' && tokstr[2] == '\0') return (COND_END); #endif return (-1); } /* Called from shell.c when Control-C is typed at top level. Or by the error rule at top level. */ void reset_parser () { dstack.delimiter_depth = 0; /* No delimiters found so far. */ open_brace_count = 0; parser_state = 0; #if defined (ALIAS) || defined (DPAREN_ARITHMETIC) if (pushed_string_list) free_string_list (); #endif /* ALIAS || DPAREN_ARITHMETIC */ if (shell_input_line) { free (shell_input_line); shell_input_line = (char *)NULL; shell_input_line_size = shell_input_line_index = 0; } FREE (word_desc_to_read); word_desc_to_read = (WORD_DESC *)NULL; last_read_token = '\n'; token_to_read = '\n'; } /* Read the next token. Command can be READ (normal operation) or RESET (to normalize state). */ static int read_token (command) int command; { int character; /* Current character. */ int peek_char; /* Temporary look-ahead character. */ int result; /* The thing to return. */ if (command == RESET) { reset_parser (); return ('\n'); } if (token_to_read) { result = token_to_read; if (token_to_read == WORD || token_to_read == ASSIGNMENT_WORD) { yylval.word = word_desc_to_read; word_desc_to_read = (WORD_DESC *)NULL; } token_to_read = 0; return (result); } #if defined (COND_COMMAND) if ((parser_state & (PST_CONDCMD|PST_CONDEXPR)) == PST_CONDCMD) { cond_lineno = line_number; parser_state |= PST_CONDEXPR; yylval.command = parse_cond_command (); if (cond_token != COND_END) { if (EOF_Reached && cond_token != COND_ERROR) /* [[ */ parser_error (cond_lineno, "unexpected EOF while looking for `]]'"); else if (cond_token != COND_ERROR) parser_error (cond_lineno, "syntax error in conditional expression"); return (-1); } token_to_read = COND_END; parser_state &= ~(PST_CONDEXPR|PST_CONDCMD); return (COND_CMD); } #endif #if defined (ALIAS) /* This is a place to jump back to once we have successfully expanded a token with an alias and pushed the string with push_string () */ re_read_token: #endif /* ALIAS */ /* Read a single word from input. Start by skipping blanks. */ while ((character = shell_getc (1)) != EOF && whitespace (character)) ; if (character == EOF) { EOF_Reached = 1; return (yacc_EOF); } if (character == '#' && (!interactive || interactive_comments)) { /* A comment. Discard until EOL or EOF, and then return a newline. */ discard_until ('\n'); shell_getc (0); character = '\n'; /* this will take the next if statement and return. */ } if (character == '\n') { /* If we're about to return an unquoted newline, we can go and collect the text of any pending here document. */ if (need_here_doc) gather_here_documents (); #if defined (ALIAS) parser_state &= ~PST_ALEXPNEXT; #endif /* ALIAS */ return (character); } /* Shell meta-characters. */ if (shellmeta (character) && ((parser_state & PST_DBLPAREN) == 0)) { #if defined (ALIAS) /* Turn off alias tokenization iff this character sequence would not leave us ready to read a command. */ if (character == '<' || character == '>') parser_state &= ~PST_ALEXPNEXT; #endif /* ALIAS */ peek_char = shell_getc (1); if (character == peek_char) { switch (character) { case '<': /* If '<' then we could be at "<<" or at "<<-". We have to look ahead one more character. */ peek_char = shell_getc (1); if (peek_char == '-') return (LESS_LESS_MINUS); else { shell_ungetc (peek_char); return (LESS_LESS); } case '>': return (GREATER_GREATER); case ';': parser_state |= PST_CASEPAT; #if defined (ALIAS) parser_state &= ~PST_ALEXPNEXT; #endif /* ALIAS */ return (SEMI_SEMI); case '&': return (AND_AND); case '|': return (OR_OR); #if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) case '(': /* ) */ # if defined (ARITH_FOR_COMMAND) if (last_read_token == FOR) { int cmdtyp, len; char *wval, *wv2; WORD_DESC *wd; arith_for_lineno = line_number; cmdtyp = parse_arith_cmd (&wval); if (cmdtyp == 1) { /* parse_arith_cmd adds quotes at the beginning and end of the string it returns; we need to take those out. */ len = strlen (wval); wv2 = (char *)xmalloc (len); strncpy (wv2, wval + 1, len - 2); wv2[len - 2] = '\0'; wd = make_word (wv2); yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL); free (wval); free (wv2); return (ARITH_FOR_EXPRS); } else return -1; /* ERROR */ } # endif # if defined (DPAREN_ARITHMETIC) if (reserved_word_acceptable (last_read_token)) { int cmdtyp, sline; char *wval; WORD_DESC *wd; sline = line_number; cmdtyp = parse_arith_cmd (&wval); if (cmdtyp == 1) /* arithmetic command */ { wd = make_word (wval); wd->flags = W_QUOTED; yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL); free (wval); /* make_word copies it */ return (ARITH_CMD); } else if (cmdtyp == 0) /* nested subshell */ { push_string (wval, 0, (alias_t *)NULL); if ((parser_state & PST_CASEPAT) == 0) parser_state |= PST_SUBSHELL; return (character); } else /* ERROR */ return -1; } break; # endif #endif } } else if (character == '<' && peek_char == '&') return (LESS_AND); else if (character == '>' && peek_char == '&') return (GREATER_AND); else if (character == '<' && peek_char == '>') return (LESS_GREATER); else if (character == '>' && peek_char == '|') return (GREATER_BAR); else if (peek_char == '>' && character == '&') return (AND_GREATER); shell_ungetc (peek_char); /* If we look like we are reading the start of a function definition, then let the reader know about it so that we will do the right thing with `{'. */ if (character == ')' && last_read_token == '(' && token_before_that == WORD) { parser_state |= PST_ALLOWOPNBRC; #if defined (ALIAS) parser_state &= ~PST_ALEXPNEXT; #endif /* ALIAS */ function_dstart = line_number; } /* case pattern lists may be preceded by an optional left paren. If we're not trying to parse a case pattern list, the left paren indicates a subshell. */ if (character == '(' && (parser_state & PST_CASEPAT) == 0) /* ) */ parser_state |= PST_SUBSHELL; /*(*/ else if ((parser_state & PST_CASEPAT) && character == ')') parser_state &= ~PST_CASEPAT; /*(*/ else if ((parser_state & PST_SUBSHELL) && character == ')') parser_state &= ~PST_SUBSHELL; #if defined (PROCESS_SUBSTITUTION) /* Check for the constructs which introduce process substitution. Shells running in `posix mode' don't do process substitution. */ if (posixly_correct || ((character != '>' && character != '<') || peek_char != '(')) #endif /* PROCESS_SUBSTITUTION */ return (character); } /* Hack <&- (close stdin) case. */ if (character == '-' && (last_read_token == LESS_AND || last_read_token == GREATER_AND)) return (character); /* Okay, if we got this far, we have to read a word. Read one, and then check it against the known ones. */ result = read_token_word (character); #if defined (ALIAS) if (result == RE_READ_TOKEN) goto re_read_token; #endif return result; } /* Match a $(...) or other grouping construct. This has to handle embedded quoted strings ('', ``, "") and nested constructs. It also must handle reprompting the user, if necessary, after reading a newline, and returning correct error values if it reads EOF. */ #define P_FIRSTCLOSE 0x01 #define P_ALLOWESC 0x02 static char matched_pair_error; static char * parse_matched_pair (qc, open, close, lenp, flags) int qc; /* `"' if this construct is within double quotes */ int open, close; int *lenp, flags; { int count, ch, was_dollar; int pass_next_character, nestlen, ttranslen, start_lineno; char *ret, *nestret, *ttrans; int retind, retsize; count = 1; pass_next_character = was_dollar = 0; ret = (char *)xmalloc (retsize = 64); retind = 0; start_lineno = line_number; while (count) { ch = shell_getc ((qc != '\'' || (flags & P_ALLOWESC)) && pass_next_character == 0); if (ch == EOF) { free (ret); parser_error (start_lineno, "unexpected EOF while looking for matching `%c'", close); EOF_Reached = 1; /* XXX */ return (&matched_pair_error); } /* Possible reprompting. */ if (ch == '\n' && interactive && (bash_input.type == st_stdin || bash_input.type == st_stream)) prompt_again (); if (pass_next_character) /* last char was backslash */ { pass_next_character = 0; if (qc != '\'' && ch == '\n') /* double-quoted \ disappears. */ { if (retind > 0) retind--; /* swallow previously-added backslash */ continue; } RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64); if (ch == CTLESC || ch == CTLNUL) ret[retind++] = CTLESC; ret[retind++] = ch; continue; } else if (ch == CTLESC || ch == CTLNUL) /* special shell escapes */ { RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64); ret[retind++] = CTLESC; ret[retind++] = ch; continue; } else if (ch == close) /* ending delimiter */ count--; #if 1 /* handle nested ${...} specially. */ else if (open != close && was_dollar && open == '{' && ch == open) /* } */ count++; #endif else if (((flags & P_FIRSTCLOSE) == 0) && ch == open) /* nested begin */ count++; /* Add this character. */ RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64); ret[retind++] = ch; if (open == '\'') /* '' inside grouping construct */ { if ((flags & P_ALLOWESC) && ch == '\\') pass_next_character++; continue; } if (ch == '\\') /* backslashes */ pass_next_character++; if (open != close) /* a grouping construct */ { if (shellquote (ch)) { /* '', ``, or "" inside $(...) or other grouping construct. */ push_delimiter (dstack, ch); if (was_dollar && ch == '\'') /* $'...' inside group */ nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC); else nestret = parse_matched_pair (ch, ch, ch, &nestlen, 0); pop_delimiter (dstack); if (nestret == &matched_pair_error) { free (ret); return &matched_pair_error; } if (was_dollar && ch == '\'') { /* Translate $'...' here. */ ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen); xfree (nestret); nestret = sh_single_quote (ttrans); free (ttrans); nestlen = strlen (nestret); retind -= 2; /* back up before the $' */ } else if (was_dollar && ch == '"') { /* Locale expand $"..." here. */ ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen); xfree (nestret); nestret = (char *)xmalloc (ttranslen + 3); nestret[0] = '"'; strcpy (nestret + 1, ttrans); nestret[ttranslen + 1] = '"'; nestret[ttranslen += 2] = '\0'; free (ttrans); nestlen = ttranslen; retind -= 2; /* back up before the $" */ } if (nestlen) { RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); strcpy (ret + retind, nestret); retind += nestlen; } FREE (nestret); } } /* Parse an old-style command substitution within double quotes as a single word. */ /* XXX - sh and ksh93 don't do this - XXX */ else if (open == '"' && ch == '`') { nestret = parse_matched_pair (0, '`', '`', &nestlen, 0); if (nestret == &matched_pair_error) { free (ret); return &matched_pair_error; } if (nestlen) { RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); strcpy (ret + retind, nestret); retind += nestlen; } FREE (nestret); } else if (was_dollar && (ch == '(' || ch == '{' || ch == '[')) /* ) } ] */ /* check for $(), $[], or ${} inside quoted string. */ { if (open == ch) /* undo previous increment */ count--; if (ch == '(') /* ) */ nestret = parse_matched_pair (0, '(', ')', &nestlen, 0); else if (ch == '{') /* } */ nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE); else if (ch == '[') /* ] */ nestret = parse_matched_pair (0, '[', ']', &nestlen, 0); if (nestret == &matched_pair_error) { free (ret); return &matched_pair_error; } if (nestlen) { RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64); strcpy (ret + retind, nestret); retind += nestlen; } FREE (nestret); } was_dollar = (ch == '$'); } ret[retind] = '\0'; if (lenp) *lenp = retind; return ret; } #if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND) /* We've seen a `(('. Look for the matching `))'. If we get it, return 1. If not, assume it's a nested subshell for backwards compatibility and return 0. In any case, put the characters we've consumed into a locally- allocated buffer and make *ep point to that buffer. Return -1 on an error, for example EOF. */ static int parse_arith_cmd (ep) char **ep; { int exp_lineno, rval, c; char *ttok, *tokstr; int ttoklen; exp_lineno = line_number; ttok = parse_matched_pair (0, '(', ')', &ttoklen, 0); rval = 1; if (ttok == &matched_pair_error) return -1; /* Check that the next character is the closing right paren. If not, this is a syntax error. ( */ if ((c = shell_getc (0)) != ')') rval = 0; tokstr = (char *)xmalloc (ttoklen + 4); /* (( ... )) -> "..." */ tokstr[0] = (rval == 1) ? '"' : '('; strncpy (tokstr + 1, ttok, ttoklen - 1); /* don't copy the final `)' */ if (rval == 1) { tokstr[ttoklen] = '"'; tokstr[ttoklen+1] = '\0'; } else { tokstr[ttoklen] = ')'; tokstr[ttoklen+1] = c; tokstr[ttoklen+2] = '\0'; } *ep = tokstr; FREE (ttok); return rval; } #endif /* DPAREN_ARITHMETIC || ARITH_FOR_COMMAND */ #if defined (COND_COMMAND) static COND_COM * cond_expr () { return (cond_or ()); } static COND_COM * cond_or () { COND_COM *l, *r; l = cond_and (); if (cond_token == OR_OR) { r = cond_or (); l = make_cond_node (COND_OR, (WORD_DESC *)NULL, l, r); } return l; } static COND_COM * cond_and () { COND_COM *l, *r; l = cond_term (); if (cond_token == AND_AND) { r = cond_and (); l = make_cond_node (COND_AND, (WORD_DESC *)NULL, l, r); } return l; } static int cond_skip_newlines () { while ((cond_token = read_token (READ)) == '\n') { if (interactive && (bash_input.type == st_stdin || bash_input.type == st_stream)) prompt_again (); } return (cond_token); } #define COND_RETURN_ERROR() \ do { cond_token = COND_ERROR; return ((COND_COM *)NULL); } while (0) static COND_COM * cond_term () { WORD_DESC *op; COND_COM *term, *tleft, *tright; int tok, lineno; /* Read a token. It can be a left paren, a `!', a unary operator, or a word that should be the first argument of a binary operator. Start by skipping newlines, since this is a compound command. */ tok = cond_skip_newlines (); lineno = line_number; if (tok == COND_END) { COND_RETURN_ERROR (); } else if (tok == '(') { term = cond_expr (); if (cond_token != ')') { if (term) dispose_cond_node (term); /* ( */ parser_error (lineno, "expected `)'"); COND_RETURN_ERROR (); } term = make_cond_node (COND_EXPR, (WORD_DESC *)NULL, term, (COND_COM *)NULL); (void)cond_skip_newlines (); } else if (tok == BANG || (tok == WORD && (yylval.word->word[0] == '!' && yylval.word->word[1] == '\0'))) { if (tok == WORD) dispose_word (yylval.word); /* not needed */ term = cond_term (); if (term) term->flags |= CMD_INVERT_RETURN; } else if (tok == WORD && test_unop (yylval.word->word)) { op = yylval.word; tok = read_token (READ); if (tok == WORD) { tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL); } else { dispose_word (op); parser_error (line_number, "unexpected argument to conditional unary operator"); COND_RETURN_ERROR (); } (void)cond_skip_newlines (); } else if (tok == WORD) /* left argument to binary operator */ { /* lhs */ tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); /* binop */ tok = read_token (READ); if (tok == WORD && test_binop (yylval.word->word)) op = yylval.word; else if (tok == '<' || tok == '>') op = make_word_from_token (tok); /* ( */ /* There should be a check before blindly accepting the `)' that we have seen the opening `('. */ else if (tok == COND_END || tok == AND_AND || tok == OR_OR || tok == ')') { /* Special case. [[ x ]] is equivalent to [[ -n x ]], just like the test command. Similarly for [[ x && expr ]] or [[ x || expr ]] or [[ (x) ]]. */ op = make_word ("-n"); term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL); cond_token = tok; return (term); } else { parser_error (line_number, "conditional binary operator expected"); dispose_cond_node (tleft); COND_RETURN_ERROR (); } /* rhs */ tok = read_token (READ); if (tok == WORD) { tright = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL); term = make_cond_node (COND_BINARY, op, tleft, tright); } else { parser_error (line_number, "unexpected argument to conditional binary operator"); dispose_cond_node (tleft); dispose_word (op); COND_RETURN_ERROR (); } (void)cond_skip_newlines (); } else { if (tok < 256) parser_error (line_number, "unexpected token `%c' in conditional command", tok); else parser_error (line_number, "unexpected token %d in conditional command", tok); COND_RETURN_ERROR (); } return (term); } /* This is kind of bogus -- we slip a mini recursive-descent parser in here to handle the conditional statement syntax. */ static COMMAND * parse_cond_command () { COND_COM *cexp; cexp = cond_expr (); return (make_cond_command (cexp)); } #endif static int read_token_word (character) int character; { /* The value for YYLVAL when a WORD is read. */ WORD_DESC *the_word; /* Index into the token that we are building. */ int token_index; /* ALL_DIGITS becomes zero when we see a non-digit. */ int all_digit_token; /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */ int dollar_present; /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */ int quoted; /* Non-zero means to ignore the value of the next character, and just to add it no matter what. */ int pass_next_character; /* The current delimiting character. */ int cd; int result, peek_char; char *ttok, *ttrans; int ttoklen, ttranslen; long lvalue; if (token_buffer_size < TOKEN_DEFAULT_INITIAL_SIZE) token = (char *)xrealloc (token, token_buffer_size = TOKEN_DEFAULT_INITIAL_SIZE); token_index = 0; all_digit_token = DIGIT (character); dollar_present = quoted = pass_next_character = 0; for (;;) { if (character == EOF) goto got_token; if (pass_next_character) { pass_next_character = 0; goto got_character; } cd = current_delimiter (dstack); /* Handle backslashes. Quote lots of things when not inside of double-quotes, quote some things inside of double-quotes. */ if (character == '\\') { peek_char = shell_getc (0); /* Backslash-newline is ignored in all cases except when quoted with single quotes. */ if (peek_char == '\n') { character = '\n'; goto next_character; } else { shell_ungetc (peek_char); /* If the next character is to be quoted, note it now. */ if (cd == 0 || cd == '`' || (cd == '"' && peek_char >= 0 && (sh_syntaxtab[peek_char] & CBSDQUOTE))) pass_next_character++; quoted = 1; goto got_character; } } /* Parse a matched pair of quote characters. */ if (shellquote (character)) { push_delimiter (dstack, character); ttok = parse_matched_pair (character, character, character, &ttoklen, 0); pop_delimiter (dstack); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; strcpy (token + token_index, ttok); token_index += ttoklen; all_digit_token = 0; quoted = 1; dollar_present |= (character == '"' && strchr (ttok, '$') != 0); FREE (ttok); goto next_character; } #ifdef EXTENDED_GLOB /* Parse a ksh-style extended pattern matching specification. */ if (extended_glob && PATTERN_CHAR (character)) { peek_char = shell_getc (1); if (peek_char == '(') /* ) */ { push_delimiter (dstack, peek_char); ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0); pop_delimiter (dstack); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; token[token_index++] = peek_char; strcpy (token + token_index, ttok); token_index += ttoklen; FREE (ttok); dollar_present = all_digit_token = 0; goto next_character; } else shell_ungetc (peek_char); } #endif /* EXTENDED_GLOB */ /* If the delimiter character is not single quote, parse some of the shell expansions that must be read as a single word. */ if (shellexp (character)) { peek_char = shell_getc (1); /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */ if (peek_char == '(' || ((peek_char == '{' || peek_char == '[') && character == '$')) /* ) ] } */ { if (peek_char == '{') /* } */ ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE); else if (peek_char == '(') /* ) */ { /* XXX - push and pop the `(' as a delimiter for use by the command-oriented-history code. This way newlines appearing in the $(...) string get added to the history literally rather than causing a possibly- incorrect `;' to be added. ) */ push_delimiter (dstack, peek_char); ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0); pop_delimiter (dstack); } else ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; token[token_index++] = peek_char; strcpy (token + token_index, ttok); token_index += ttoklen; FREE (ttok); dollar_present = 1; all_digit_token = 0; goto next_character; } /* This handles $'...' and $"..." new-style quoted strings. */ else if (character == '$' && (peek_char == '\'' || peek_char == '"')) { int first_line; first_line = line_number; push_delimiter (dstack, peek_char); ttok = parse_matched_pair (peek_char, peek_char, peek_char, &ttoklen, (peek_char == '\'') ? P_ALLOWESC : 0); pop_delimiter (dstack); if (ttok == &matched_pair_error) return -1; if (peek_char == '\'') { ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen); free (ttok); /* Insert the single quotes and correctly quote any embedded single quotes (allowed because P_ALLOWESC was passed to parse_matched_pair). */ ttok = sh_single_quote (ttrans); free (ttrans); ttrans = ttok; ttranslen = strlen (ttrans); } else { /* Try to locale-expand the converted string. */ ttrans = localeexpand (ttok, 0, ttoklen - 1, first_line, &ttranslen); free (ttok); /* Add the double quotes back */ ttok = (char *)xmalloc (ttranslen + 3); ttok[0] = '"'; strcpy (ttok + 1, ttrans); ttok[ttranslen + 1] = '"'; ttok[ttranslen += 2] = '\0'; free (ttrans); ttrans = ttok; } RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); strcpy (token + token_index, ttrans); token_index += ttranslen; FREE (ttrans); quoted = 1; all_digit_token = 0; goto next_character; } /* This could eventually be extended to recognize all of the shell's single-character parameter expansions, and set flags.*/ else if (character == '$' && peek_char == '$') { ttok = (char *)xmalloc (3); ttok[0] = ttok[1] = '$'; ttok[2] = '\0'; RESIZE_MALLOCED_BUFFER (token, token_index, 3, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); strcpy (token + token_index, ttok); token_index += 2; dollar_present = 1; all_digit_token = 0; FREE (ttok); goto next_character; } else shell_ungetc (peek_char); } #if defined (ARRAY_VARS) /* Identify possible compound array variable assignment. */ else if (character == '=' && token_index > 0) { peek_char = shell_getc (1); if (peek_char == '(') /* ) */ { ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0); if (ttok == &matched_pair_error) return -1; /* Bail immediately. */ if (ttok[0] == '(') /* ) */ { FREE (ttok); return -1; } RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); token[token_index++] = character; token[token_index++] = peek_char; strcpy (token + token_index, ttok); token_index += ttoklen; FREE (ttok); all_digit_token = 0; goto next_character; } else shell_ungetc (peek_char); } #endif /* When not parsing a multi-character word construct, shell meta- characters break words. */ if (shellbreak (character)) { shell_ungetc (character); goto got_token; } got_character: all_digit_token &= DIGIT (character); dollar_present |= character == '$'; if (character == CTLESC || character == CTLNUL) token[token_index++] = CTLESC; token[token_index++] = character; RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size, TOKEN_DEFAULT_GROW_SIZE); next_character: if (character == '\n' && interactive && (bash_input.type == st_stdin || bash_input.type == st_stream)) prompt_again (); /* We want to remove quoted newlines (that is, a \ pair) unless we are within single quotes or pass_next_character is set (the shell equivalent of literal-next). */ cd = current_delimiter (dstack); character = shell_getc (cd != '\'' && pass_next_character == 0); } /* end for (;;) */ got_token: token[token_index] = '\0'; /* Check to see what thing we should return. If the last_read_token is a `<', or a `&', or the character which ended this token is a '>' or '<', then, and ONLY then, is this input token a NUMBER. Otherwise, it is just a word, and should be returned as such. */ if (all_digit_token && (character == '<' || character == '>' || last_read_token == LESS_AND || last_read_token == GREATER_AND)) { if (legal_number (token, &lvalue) && (int)lvalue == lvalue) yylval.number = lvalue; else yylval.number = -1; return (NUMBER); } /* Check for special case tokens. */ result = special_case_tokens (token); if (result >= 0) return result; #if defined (ALIAS) /* Posix.2 does not allow reserved words to be aliased, so check for all of them, including special cases, before expanding the current token as an alias. */ if (posixly_correct) CHECK_FOR_RESERVED_WORD (token); /* Aliases are expanded iff EXPAND_ALIASES is non-zero, and quoting inhibits alias expansion. */ if (expand_aliases && quoted == 0) { result = alias_expand_token (token); if (result == RE_READ_TOKEN) return (RE_READ_TOKEN); else if (result == NO_EXPANSION) parser_state &= ~PST_ALEXPNEXT; } /* If not in Posix.2 mode, check for reserved words after alias expansion. */ if (posixly_correct == 0) #endif CHECK_FOR_RESERVED_WORD (token); the_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC)); the_word->word = (char *)xmalloc (1 + token_index); the_word->flags = 0; strcpy (the_word->word, token); if (dollar_present) the_word->flags |= W_HASDOLLAR; if (quoted) the_word->flags |= W_QUOTED; /* A word is an assignment if it appears at the beginning of a simple command, or after another assignment word. This is context-dependent, so it cannot be handled in the grammar. */ if (assignment (token)) { the_word->flags |= W_ASSIGNMENT; /* Don't perform word splitting on assignment statements. */ if (assignment_acceptable (last_read_token)) the_word->flags |= W_NOSPLIT; } yylval.word = the_word; result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT)) ? ASSIGNMENT_WORD : WORD; if (last_read_token == FUNCTION) { parser_state |= PST_ALLOWOPNBRC; function_dstart = line_number; } return (result); } /* $'...' ANSI-C expand the portion of STRING between START and END and return the result. The result cannot be longer than the input string. */ static char * ansiexpand (string, start, end, lenp) char *string; int start, end, *lenp; { char *temp, *t; int len, tlen; temp = (char *)xmalloc (end - start + 1); for (tlen = 0, len = start; len < end; ) temp[tlen++] = string[len++]; temp[tlen] = '\0'; if (*temp) { t = ansicstr (temp, tlen, 0, (int *)NULL, lenp); free (temp); return (t); } else { if (lenp) *lenp = 0; return (temp); } } /* Change a bash string into a string suitable for inclusion in a `po' file. This backslash-escapes `"' and `\' and changes newlines into \\\n"\n". */ static char * mk_msgstr (string, foundnlp) char *string; int *foundnlp; { register int c, len; char *result, *r, *s; for (len = 0, s = string; s && *s; s++) { len++; if (*s == '"' || *s == '\\') len++; else if (*s == '\n') len += 5; } r = result = (char *)xmalloc (len + 3); *r++ = '"'; for (s = string; s && (c = *s); s++) { if (c == '\n') /* -> \n"" */ { *r++ = '\\'; *r++ = 'n'; *r++ = '"'; *r++ = '\n'; *r++ = '"'; if (foundnlp) *foundnlp = 1; continue; } if (c == '"' || c == '\\') *r++ = '\\'; *r++ = c; } *r++ = '"'; *r++ = '\0'; return result; } /* $"..." -- Translate the portion of STRING between START and END according to current locale using gettext (if available) and return the result. The caller will take care of leaving the quotes intact. The string will be left without the leading `$' by the caller. If translation is performed, the translated string will be double-quoted by the caller. The length of the translated string is returned in LENP, if non-null. */ static char * localeexpand (string, start, end, lineno, lenp) char *string; int start, end, lineno, *lenp; { int len, tlen, foundnl; char *temp, *t, *t2; temp = (char *)xmalloc (end - start + 1); for (tlen = 0, len = start; len < end; ) temp[tlen++] = string[len++]; temp[tlen] = '\0'; /* If we're just dumping translatable strings, don't do anything with the string itself, but if we're dumping in `po' file format, convert it into a form more palatable to gettext(3) and friends by quoting `"' and `\' with backslashes and converting into `\n""'. If we find a newline in TEMP, we first output a `msgid ""' line and then the translated string; otherwise we output the `msgid' and translated string all on one line. */ if (dump_translatable_strings) { if (dump_po_strings) { foundnl = 0; t = mk_msgstr (temp, &foundnl); t2 = foundnl ? "\"\"\n" : ""; printf ("#: %s:%d\nmsgid %s%s\nmsgstr \"\"\n", (bash_input.name ? bash_input.name : "stdin"), lineno, t2, t); free (t); } else printf ("\"%s\"\n", temp); if (lenp) *lenp = tlen; return (temp); } else if (*temp) { t = localetrans (temp, tlen, &len); free (temp); if (lenp) *lenp = len; return (t); } else { if (lenp) *lenp = 0; return (temp); } } /* Return 1 if TOKSYM is a token that after being read would allow a reserved word to be seen, else 0. */ static int reserved_word_acceptable (toksym) int toksym; { if (toksym == '\n' || toksym == ';' || toksym == '(' || toksym == ')' || toksym == '|' || toksym == '&' || toksym == '{' || toksym == '}' || /* XXX */ toksym == AND_AND || toksym == BANG || toksym == TIME || toksym == TIMEOPT || toksym == DO || toksym == ELIF || toksym == ELSE || toksym == FI || toksym == IF || toksym == OR_OR || toksym == SEMI_SEMI || toksym == THEN || toksym == UNTIL || toksym == WHILE || toksym == DONE || /* XXX these two are experimental */ toksym == ESAC || toksym == 0) return (1); else return (0); } /* Return the index of TOKEN in the alist of reserved words, or -1 if TOKEN is not a shell reserved word. */ int find_reserved_word (tokstr) char *tokstr; { int i; for (i = 0; word_token_alist[i].word; i++) if (STREQ (tokstr, word_token_alist[i].word)) return i; return -1; } #if 0 #if defined (READLINE) /* Called after each time readline is called. This insures that whatever the new prompt string is gets propagated to readline's local prompt variable. */ static void reset_readline_prompt () { char *temp_prompt; if (prompt_string_pointer) { temp_prompt = (*prompt_string_pointer) ? decode_prompt_string (*prompt_string_pointer) : (char *)NULL; if (temp_prompt == 0) { temp_prompt = (char *)xmalloc (1); temp_prompt[0] = '\0'; } FREE (current_readline_prompt); current_readline_prompt = temp_prompt; } } #endif /* READLINE */ #endif /* 0 */ #if defined (HISTORY) /* A list of tokens which can be followed by newlines, but not by semi-colons. When concatenating multiple lines of history, the newline separator for such tokens is replaced with a space. */ static int no_semi_successors[] = { '\n', '{', '(', ')', ';', '&', '|', CASE, DO, ELSE, IF, SEMI_SEMI, THEN, UNTIL, WHILE, AND_AND, OR_OR, IN, 0 }; /* If we are not within a delimited expression, try to be smart about which separators can be semi-colons and which must be newlines. Returns the string that should be added into the history entry. */ char * history_delimiting_chars () { register int i; if (dstack.delimiter_depth != 0) return ("\n"); /* First, handle some special cases. */ /*(*/ /* If we just read `()', assume it's a function definition, and don't add a semicolon. If the token before the `)' was not `(', and we're not in the midst of parsing a case statement, assume it's a parenthesized command and add the semicolon. */ /*)(*/ if (token_before_that == ')') { if (two_tokens_ago == '(') /*)*/ /* function def */ return " "; /* This does not work for subshells inside case statement command lists. It's a suboptimal solution. */ else if (parser_state & PST_CASESTMT) /* case statement pattern */ return " "; else return "; "; /* (...) subshell */ } else if (token_before_that == WORD && two_tokens_ago == FUNCTION) return " "; /* function def using `function name' without `()' */ else if (token_before_that == WORD && two_tokens_ago == FOR) { /* Tricky. `for i\nin ...' should not have a semicolon, but `for i\ndo ...' should. We do what we can. */ for (i = shell_input_line_index; whitespace(shell_input_line[i]); i++) ; if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n') return " "; return ";"; } for (i = 0; no_semi_successors[i]; i++) { if (token_before_that == no_semi_successors[i]) return (" "); } return ("; "); } #endif /* HISTORY */ /* Issue a prompt, or prepare to issue a prompt when the next character is read. */ static void prompt_again () { char *temp_prompt; if (!interactive) /* XXX */ return; ps1_prompt = get_string_value ("PS1"); ps2_prompt = get_string_value ("PS2"); if (!prompt_string_pointer) prompt_string_pointer = &ps1_prompt; temp_prompt = *prompt_string_pointer ? decode_prompt_string (*prompt_string_pointer) : (char *)NULL; if (temp_prompt == 0) { temp_prompt = (char *)xmalloc (1); temp_prompt[0] = '\0'; } current_prompt_string = *prompt_string_pointer; prompt_string_pointer = &ps2_prompt; #if defined (READLINE) if (!no_line_editing) { FREE (current_readline_prompt); current_readline_prompt = temp_prompt; } else #endif /* READLINE */ { FREE (current_decoded_prompt); current_decoded_prompt = temp_prompt; } } int get_current_prompt_level () { return ((current_prompt_string && current_prompt_string == ps2_prompt) ? 2 : 1); } void set_current_prompt_level (x) int x; { prompt_string_pointer = (x == 2) ? &ps2_prompt : &ps1_prompt; current_prompt_string = *prompt_string_pointer; } static void print_prompt () { fprintf (stderr, "%s", current_decoded_prompt); fflush (stderr); } /* Return a string which will be printed as a prompt. The string may contain special characters which are decoded as follows: \a bell (ascii 07) \e escape (ascii 033) \d the date in Day Mon Date format \h the hostname up to the first `.' \H the hostname \j the number of active jobs \l the basename of the shell's tty device name \n CRLF \s the name of the shell \t the time in 24-hour hh:mm:ss format \T the time in 12-hour hh:mm:ss format \@ the time in 12-hour am/pm format \v the version of bash (e.g., 2.00) \V the release of bash, version + patchlevel (e.g., 2.00.0) \w the current working directory \W the last element of $PWD \u your username \# the command number of this command \! the history number of this command \$ a $ or a # if you are root \nnn character code nnn in octal \\ a backslash \[ begin a sequence of non-printing chars \] end a sequence of non-printing chars */ #define PROMPT_GROWTH 48 char * decode_prompt_string (string) char *string; { WORD_LIST *list; char *result, *t; struct dstack save_dstack; int last_exit_value; #if defined (PROMPT_STRING_DECODE) int result_size, result_index; int c, n; char *temp, octal_string[4]; time_t the_time; result = (char *)xmalloc (result_size = PROMPT_GROWTH); result[result_index = 0] = 0; temp = (char *)NULL; while (c = *string++) { if (posixly_correct && c == '!') { if (*string == '!') { temp = savestring ("!"); goto add_string; } else { #if !defined (HISTORY) temp = savestring ("1"); #else /* HISTORY */ temp = itos (history_number ()); #endif /* HISTORY */ string--; /* add_string increments string again. */ goto add_string; } } if (c == '\\') { c = *string; switch (c) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': strncpy (octal_string, string, 3); octal_string[3] = '\0'; n = read_octal (octal_string); temp = (char *)xmalloc (3); if (n == CTLESC || n == CTLNUL) { temp[0] = CTLESC; temp[1] = n; temp[2] = '\0'; } else if (n == -1) { temp[0] = '\\'; temp[1] = '\0'; } else { temp[0] = n; temp[1] = '\0'; } for (c = 0; n != -1 && c < 3 && ISOCTAL (*string); c++) string++; c = 0; goto add_string; case 't': case 'd': case 'T': case '@': case 'A': /* Make the current time/date into a string. */ the_time = time (0); temp = ctime (&the_time); temp = (c != 'd') ? savestring (temp + 11) : savestring (temp); temp[(c != 'd') ? 8 : 10] = '\0'; temp[(c != 'A') ? 10 : 5] = '\0'; /* quick and dirty conversion to 12-hour time */ if (c == 'T' || c == '@') { if (c == '@') { temp[5] = 'a'; /* am/pm format */ temp[6] = 'm'; temp[7] = '\0'; } c = temp[2]; temp[2] = '\0'; n = atoi (temp); temp[2] = c; n -= 12; if (n > 0) { temp[0] = (n / 10) + '0'; temp[1] = (n % 10) + '0'; } if (n >= 0 && temp[5] == 'a') temp[5] = 'p'; } goto add_string; case 'r': temp = (char *)xmalloc (2); temp[0] = '\r'; temp[1] = '\0'; goto add_string; case 'n': temp = (char *)xmalloc (3); temp[0] = no_line_editing ? '\n' : '\r'; temp[1] = no_line_editing ? '\0' : '\n'; temp[2] = '\0'; goto add_string; case 's': temp = base_pathname (shell_name); temp = savestring (temp); goto add_string; case 'v': case 'V': temp = (char *)xmalloc (8); if (c == 'v') strcpy (temp, dist_version); else sprintf (temp, "%s.%d", dist_version, patch_level); goto add_string; case 'w': case 'W': { /* Use the value of PWD because it is much more efficient. */ char t_string[PATH_MAX]; int tlen; temp = get_string_value ("PWD"); if (temp == 0) { if (getcwd (t_string, sizeof(t_string)) == 0) { t_string[0] = '.'; tlen = 1; } else tlen = strlen (t_string); } else { tlen = sizeof (t_string) - 1; strncpy (t_string, temp, tlen); } t_string[tlen] = '\0'; #define ROOT_PATH(x) ((x)[0] == '/' && (x)[1] == 0) #define DOUBLE_SLASH_ROOT(x) ((x)[0] == '/' && (x)[1] == '/' && (x)[2] == 0) if (c == 'W') { if (ROOT_PATH (t_string) == 0 && DOUBLE_SLASH_ROOT (t_string) == 0) { t = strrchr (t_string, '/'); if (t) strcpy (t_string, t + 1); } } #undef ROOT_PATH #undef DOUBLE_SLASH_ROOT else /* polite_directory_format is guaranteed to return a string no longer than PATH_MAX - 1 characters. */ strcpy (t_string, polite_directory_format (t_string)); /* If we're going to be expanding the prompt string later, quote the directory name. */ if (promptvars || posixly_correct) /* Make sure that expand_prompt_string is called with a second argument of Q_DOUBLE_QUOTE if we use this function here. */ temp = sh_backslash_quote_for_double_quotes (t_string); else temp = savestring (t_string); goto add_string; } case 'u': if (current_user.user_name == 0) get_current_user_info (); temp = savestring (current_user.user_name); goto add_string; case 'h': case 'H': temp = savestring (current_host_name); if (c == 'h' && (t = (char *)strchr (temp, '.'))) *t = '\0'; goto add_string; case '#': temp = itos (current_command_number); goto add_string; case '!': #if !defined (HISTORY) temp = savestring ("1"); #else /* HISTORY */ temp = itos (history_number ()); #endif /* HISTORY */ goto add_string; case '$': t = temp = (char *)xmalloc (3); if ((promptvars || posixly_correct) && (current_user.euid != 0)) *t++ = '\\'; *t++ = current_user.euid == 0 ? '#' : '$'; *t = '\0'; goto add_string; case 'j': temp = itos (count_all_jobs ()); goto add_string; case 'l': #if defined (HAVE_TTYNAME) temp = (char *)ttyname (fileno (stdin)); t = temp ? base_pathname (temp) : "tty"; temp = savestring (t); #else temp = savestring ("tty"); #endif /* !HAVE_TTYNAME */ goto add_string; #if defined (READLINE) case '[': case ']': temp = (char *)xmalloc (3); temp[0] = '\001'; temp[1] = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE; temp[2] = '\0'; goto add_string; #endif /* READLINE */ case '\\': temp = (char *)xmalloc (2); temp[0] = c; temp[1] = '\0'; goto add_string; case 'a': case 'e': temp = (char *)xmalloc (2); temp[0] = (c == 'a') ? '\07' : '\033'; temp[1] = '\0'; goto add_string; default: temp = (char *)xmalloc (3); temp[0] = '\\'; temp[1] = c; temp[2] = '\0'; add_string: if (c) string++; result = sub_append_string (temp, result, &result_index, &result_size); temp = (char *)NULL; /* Freed in sub_append_string (). */ result[result_index] = '\0'; break; } } else { RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, PROMPT_GROWTH); result[result_index++] = c; result[result_index] = '\0'; } } #else /* !PROMPT_STRING_DECODE */ result = savestring (string); #endif /* !PROMPT_STRING_DECODE */ /* Save the delimiter stack and point `dstack' to temp space so any command substitutions in the prompt string won't result in screwing up the parser's quoting state. */ save_dstack = dstack; dstack = temp_dstack; dstack.delimiter_depth = 0; /* Perform variable and parameter expansion and command substitution on the prompt string. */ if (promptvars || posixly_correct) { last_exit_value = last_command_exit_value; list = expand_prompt_string (result, Q_DOUBLE_QUOTES); free (result); result = string_list (list); dispose_words (list); last_command_exit_value = last_exit_value; } else { t = dequote_string (result); free (result); result = t; } dstack = save_dstack; return (result); } /* Report a syntax error, and restart the parser. Call here for fatal errors. */ int yyerror (msg) const char *msg; { report_syntax_error ((char *)NULL); reset_parser (); return (0); } /* Report a syntax error with line numbers, etc. Call here for recoverable errors. If you have a message to print, then place it in MESSAGE, otherwise pass NULL and this will figure out an appropriate message for you. */ static void report_syntax_error (message) char *message; { char *msg, *t; int token_end, i; char msg2[2]; if (message) { parser_error (line_number, "%s", message); if (interactive && EOF_Reached) EOF_Reached = 0; last_command_exit_value = EX_USAGE; return; } /* If the line of input we're reading is not null, try to find the objectionable token. */ if (shell_input_line && *shell_input_line) { t = shell_input_line; i = shell_input_line_index; token_end = 0; if (i && t[i] == '\0') i--; while (i && (whitespace (t[i]) || t[i] == '\n')) i--; if (i) token_end = i + 1; while (i && (member (t[i], " \n\t;|&") == 0)) i--; while (i != token_end && (whitespace (t[i]) || t[i] == '\n')) i++; /* Print the offending token. */ if (token_end || (i == 0 && token_end == 0)) { if (token_end) msg = substring (t, i, token_end); else /* one-character token */ { msg2[0] = t[i]; msg2[1] = '\0'; msg = msg2; } parser_error (line_number, "syntax error near unexpected token `%s'", msg); if (msg != msg2) free (msg); } /* If not interactive, print the line containing the error. */ if (interactive == 0) { msg = savestring (shell_input_line); token_end = strlen (msg); while (token_end && msg[token_end - 1] == '\n') msg[--token_end] = '\0'; parser_error (line_number, "`%s'", msg); free (msg); } } else { msg = EOF_Reached ? "syntax error: unexpected end of file" : "syntax error"; parser_error (line_number, "%s", msg); /* When the shell is interactive, this file uses EOF_Reached only for error reporting. Other mechanisms are used to decide whether or not to exit. */ if (interactive && EOF_Reached) EOF_Reached = 0; } last_command_exit_value = EX_USAGE; } /* ??? Needed function. ??? We have to be able to discard the constructs created during parsing. In the case of error, we want to return allocated objects to the memory pool. In the case of no error, we want to throw away the information about where the allocated objects live. (dispose_command () will actually free the command. */ static void discard_parser_constructs (error_p) int error_p; { } /* Do that silly `type "bye" to exit' stuff. You know, "ignoreeof". */ /* A flag denoting whether or not ignoreeof is set. */ int ignoreeof = 0; /* The number of times that we have encountered an EOF character without another character intervening. When this gets above the limit, the shell terminates. */ int eof_encountered = 0; /* The limit for eof_encountered. */ int eof_encountered_limit = 10; /* If we have EOF as the only input unit, this user wants to leave the shell. If the shell is not interactive, then just leave. Otherwise, if ignoreeof is set, and we haven't done this the required number of times in a row, print a message. */ static void handle_eof_input_unit () { if (interactive) { /* shell.c may use this to decide whether or not to write out the history, among other things. We use it only for error reporting in this file. */ if (EOF_Reached) EOF_Reached = 0; /* If the user wants to "ignore" eof, then let her do so, kind of. */ if (ignoreeof) { if (eof_encountered < eof_encountered_limit) { fprintf (stderr, "Use \"%s\" to leave the shell.\n", login_shell ? "logout" : "exit"); eof_encountered++; /* Reset the prompt string to be $PS1. */ prompt_string_pointer = (char **)NULL; prompt_again (); last_read_token = current_token = '\n'; return; } } /* In this case EOF should exit the shell. Do it now. */ reset_parser (); exit_builtin ((WORD_LIST *)NULL); } else { /* We don't write history files, etc., for non-interactive shells. */ EOF_Reached = 1; } } static WORD_LIST parse_string_error; /* Take a string and run it through the shell parser, returning the resultant word list. Used by compound array assignment. */ WORD_LIST * parse_string_to_word_list (s, whom) char *s; const char *whom; { WORD_LIST *wl; int tok, orig_line_number, orig_input_terminator; int orig_line_count; #if defined (HISTORY) int old_remember_on_history, old_history_expansion_inhibited; #endif #if defined (HISTORY) old_remember_on_history = remember_on_history; # if defined (BANG_HISTORY) old_history_expansion_inhibited = history_expansion_inhibited; # endif bash_history_disable (); #endif orig_line_number = line_number; orig_line_count = current_command_line_count; orig_input_terminator = shell_input_line_terminator; push_stream (1); last_read_token = '\n'; current_command_line_count = 0; with_input_from_string (s, whom); wl = (WORD_LIST *)NULL; while ((tok = read_token (READ)) != yacc_EOF) { if (tok == '\n' && *bash_input.location.string == '\0') break; if (tok == '\n') /* Allow newlines in compound assignments */ continue; if (tok != WORD && tok != ASSIGNMENT_WORD) { line_number = orig_line_number + line_number - 1; yyerror ((char *)NULL); /* does the right thing */ if (wl) dispose_words (wl); wl = &parse_string_error; break; } wl = make_word_list (yylval.word, wl); } last_read_token = '\n'; pop_stream (); #if defined (HISTORY) remember_on_history = old_remember_on_history; # if defined (BANG_HISTORY) history_expansion_inhibited = old_history_expansion_inhibited; # endif /* BANG_HISTORY */ #endif /* HISTORY */ current_command_line_count = orig_line_count; shell_input_line_terminator = orig_input_terminator; if (wl == &parse_string_error) { last_command_exit_value = EXECUTION_FAILURE; if (interactive_shell == 0 && posixly_correct) jump_to_top_level (FORCE_EOF); else jump_to_top_level (DISCARD); } return (REVERSE_LIST (wl, WORD_LIST *)); }