X-Git-Url: http://git.ipfire.org/?p=thirdparty%2Fbash.git;a=blobdiff_plain;f=shell.c;h=a2b2a55e0c5441242fcad7e505e56aa5a5efbe4d;hp=26b20c0c0c6f499f9766fa6b576a065d1a0827e5;hb=HEAD;hpb=726f63884db0132f01745f1fb4465e6621088ccf diff --git a/shell.c b/shell.c index 26b20c0c0..ebd89651e 100644 --- a/shell.c +++ b/shell.c @@ -1,114 +1,124 @@ -/* shell.c -- GNU's idea of the POSIX shell specification. +/* shell.c -- GNU's idea of the POSIX shell specification. */ - This file is part of Bash, the Bourne Again SHell. Bash is free - software; no one can prevent you from reading the source code, or - giving it to someone else. This file is copyrighted under the GNU - General Public License, which can be found in the file called - COPYING. +/* Copyright (C) 1987-2021 Free Software Foundation, Inc. - Copyright (C) 1988, 1991 Free Software Foundation, Inc. + This file is part of GNU Bash, the Bourne Again SHell. - This file is part of GNU Bash. + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - Bash is distributed in the hope that it will be useful, but WITHOUT - ANY WARRANTY. No author or distributor accepts responsibility to - anyone for the consequences of using it or for whether it serves - any particular purpose or works at all, unless he says so in - writing. Refer to the GNU Emacs General Public License for full - details. + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - Everyone is granted permission to copy, modify and redistribute - Bash, but only under the conditions described in the GNU General - Public License. A copy of this license is supposed to have been - given to you along with GNU Emacs so you can know your rights and - responsibilities. It should be in a file named COPYING. - - Among other things, the copyright notice and this notice must be - preserved on all copies. + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ +/* Birthdate: Sunday, January 10th, 1988. Initial author: Brian Fox */ #define INSTALL_DEBUG_MODE +#include "config.h" + #include "bashtypes.h" +#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) +# include +#endif +#include "posixstat.h" +#include "posixtime.h" +#include "bashansi.h" #include #include #include -#include #include "filecntl.h" -#include -#include "posixstat.h" -#include "bashansi.h" +#if defined (HAVE_PWD_H) +# include +#endif -#if defined (HAVE_VARARGS_H) -#include +#if defined (HAVE_UNISTD_H) +# include #endif +#include "bashintl.h" + +#define NEED_SH_SETLINEBUF_DECL /* used in externs.h */ + #include "shell.h" +#include "parser.h" #include "flags.h" +#include "trap.h" +#include "mailcheck.h" +#include "builtins.h" +#include "builtins/common.h" #if defined (JOB_CONTROL) #include "jobs.h" +#else +extern int running_in_background; +extern int initialize_job_control PARAMS((int)); +extern int get_tty_state PARAMS((void)); #endif /* JOB_CONTROL */ #include "input.h" #include "execute_cmd.h" +#include "findcmd.h" + +#if defined (USING_BASH_MALLOC) && defined (DEBUG) && !defined (DISABLE_MALLOC_WRAPPERS) +# include +#elif defined (MALLOC_DEBUG) && defined (USING_BASH_MALLOC) +# include +#endif #if defined (HISTORY) # include "bashhist.h" # include #endif +#if defined (READLINE) +# include +# include "bashline.h" +#endif + #include +#include + +#if defined (__OPENNT) +# include +#endif -#if defined (USG) && !defined (HAVE_GETPW_DECLS) +#if !defined (HAVE_GETPW_DECLS) extern struct passwd *getpwuid (); -#endif /* USG && !HAVE_GETPW_DECLS */ +#endif /* !HAVE_GETPW_DECLS */ -extern int yydebug; #if !defined (errno) extern int errno; #endif -extern char *dist_version; -extern int patch_level, build_version; -extern int subshell_environment; /* Found in execute_cmd.c. */ -extern int last_command_exit_value; -extern int return_catch_flag; -extern jmp_buf return_catch; -extern int need_here_doc, current_command_line_count, line_number; -extern char *ps1_prompt, **prompt_string_pointer; -extern int loop_level, continuing, breaking; -extern int parse_and_execute_level; -extern char *this_command_name; - -/* Non-zero means that this shell has already been run; i.e. you should - call shell_reinitialize () if you need to start afresh. */ -static int shell_initialized = 0; -static int sourced_env = 0; - -/* The current maintainer of the shell. You change this in the - Makefile. */ -#if !defined (MAINTAINER) -#define MAINTAINER "bash-maintainers@prep.ai.mit.edu" +#if defined (NO_MAIN_ENV_ARG) +extern char **environ; /* used if no third argument to main() */ #endif -char *the_current_maintainer = MAINTAINER; +extern int gnu_error_format; -char *primary_prompt = PPROMPT; -char *secondary_prompt = SPROMPT; +/* Non-zero means that this shell has already been run; i.e. you should + call shell_reinitialize () if you need to start afresh. */ +int shell_initialized = 0; +int bash_argv_initialized = 0; COMMAND *global_command = (COMMAND *)NULL; -/* Non-zero after SIGINT. */ -int interrupt_state = 0; - /* Information about the current user. */ struct user_info current_user = { - -1, -1, -1, -1, (char *)NULL, (char *)NULL, (char *)NULL + (uid_t)-1, (uid_t)-1, (gid_t)-1, (gid_t)-1, + (char *)NULL, (char *)NULL, (char *)NULL }; /* The current host's name. */ @@ -118,7 +128,7 @@ char *current_host_name = (char *)NULL; Specifically: 0 = not login shell. 1 = login shell from getty (or equivalent fake out) - -1 = login shell from "-login" flag. + -1 = login shell from "--login" (or -l) flag. -2 = both from getty, and from flag. */ int login_shell = 0; @@ -131,13 +141,25 @@ int interactive = 0; /* Non-zero means that the shell was started as an interactive shell. */ int interactive_shell = 0; +/* Non-zero means to send a SIGHUP to all jobs when an interactive login + shell exits. */ +int hup_on_exit = 0; + +/* Non-zero means to list status of running and stopped jobs at shell exit */ +int check_jobs_at_exit = 0; + +/* Non-zero means to change to a directory name supplied as a command name */ +int autocd = 0; + /* Tells what state the shell was in when it started: 0 = non-interactive shell script 1 = interactive 2 = -c command + 3 = wordexp evaluation This is a superset of the information provided by interactive_shell. */ int startup_state = 0; +int reading_shell_script = 0; /* Special debugging helper. */ int debugging_login_shell = 0; @@ -151,66 +173,107 @@ int executing = 0; /* The number of commands executed so far. */ int current_command_number = 1; -/* The environment at the top-level REP loop. We use this in the case of - error return. */ -jmp_buf top_level, catch; - -#if defined (JOB_CONTROL) || defined (_POSIX_VERSION) -/* The signal masks that this shell runs with. */ -sigset_t top_level_mask; -#endif /* JOB_CONTROL */ - /* Non-zero is the recursion depth for commands. */ int indirection_level = 0; -/* The number of times BASH has been executed. This is set - by initialize_variables () in variables.c. */ -int shell_level = 0; - /* The name of this shell, as taken from argv[0]. */ char *shell_name = (char *)NULL; /* time in seconds when the shell was started */ time_t shell_start_time; +struct timeval shellstart; + +/* Are we running in an emacs shell window? */ +int running_under_emacs; + +/* Do we have /dev/fd? */ +#ifdef HAVE_DEV_FD +int have_devfd = HAVE_DEV_FD; +#else +int have_devfd = 0; +#endif /* The name of the .(shell)rc file. */ -static char *bashrc_file = "~/.bashrc"; +static char *bashrc_file = DEFAULT_BASHRC; /* Non-zero means to act more like the Bourne shell on startup. */ -static int act_like_sh = 0; +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 = 0; /* Do debugging things. */ -static int no_rc = 0; /* Don't execute ~/.bashrc */ -static int no_profile = 0; /* Don't execute .profile */ -static int do_version = 0; /* Display interesting version info. */ -static int quiet = 0; /* Be quiet when starting up. */ -static int make_login_shell = 0; /* Make this shell be a `-bash' shell. */ +static int debugging; /* Do debugging things. */ +static int no_rc; /* Don't execute ~/.bashrc */ +static int no_profile; /* Don't execute .profile */ +static int do_version; /* Display interesting version info. */ +static int make_login_shell; /* Make this shell be a `-bash' shell. */ +static int want_initial_help; /* --help option */ + +int debugging_mode = 0; /* In debugging mode with --debugger */ +#if defined (READLINE) +int no_line_editing = 0; /* non-zero -> don't do fancy line editing. */ +#else +int no_line_editing = 1; /* can't have line editing without readline */ +#endif +#if defined (TRANSLATABLE_STRINGS) +int dump_translatable_strings; /* Dump strings in $"...", don't execute. */ +int dump_po_strings; /* Dump strings in $"..." in po format */ +#endif +int wordexp_only = 0; /* Do word expansion only */ +int protected_mode = 0; /* No command substitution with --wordexp */ -int no_line_editing = 0; /* Don't do fancy line editing. */ -int no_brace_expansion = 0; /* Non-zero means no foo{a,b} -> fooa foob. */ +int pretty_print_mode = 0; /* pretty-print a shell script */ +#if defined (STRICT_POSIX) +int posixly_correct = 1; /* Non-zero means posix.2 superset. */ +#else int posixly_correct = 0; /* Non-zero means posix.2 superset. */ +#endif /* Some long-winded argument names. These are obviously new. */ #define Int 1 #define Charp 2 -struct { - char *name; +static const struct { + const char *name; int type; int *int_value; char **char_value; } long_args[] = { { "debug", Int, &debugging, (char **)0x0 }, - { "norc", Int, &no_rc, (char **)0x0 }, +#if defined (DEBUGGER) + { "debugger", Int, &debugging_mode, (char **)0x0 }, +#endif +#if defined (TRANSLATABLE_STRINGS) + { "dump-po-strings", Int, &dump_po_strings, (char **)0x0 }, + { "dump-strings", Int, &dump_translatable_strings, (char **)0x0 }, +#endif + { "help", Int, &want_initial_help, (char **)0x0 }, + { "init-file", Charp, (int *)0x0, &bashrc_file }, + { "login", Int, &make_login_shell, (char **)0x0 }, + { "noediting", Int, &no_line_editing, (char **)0x0 }, { "noprofile", Int, &no_profile, (char **)0x0 }, + { "norc", Int, &no_rc, (char **)0x0 }, + { "posix", Int, &posixly_correct, (char **)0x0 }, + { "pretty-print", Int, &pretty_print_mode, (char **)0x0 }, +#if defined (WORDEXP_OPTION) + { "protected", Int, &protected_mode, (char **)0x0 }, +#endif { "rcfile", Charp, (int *)0x0, &bashrc_file }, +#if defined (RESTRICTED_SHELL) + { "restricted", Int, &restricted, (char **)0x0 }, +#endif + { "verbose", Int, &verbose_flag, (char **)0x0 }, { "version", Int, &do_version, (char **)0x0 }, - { "quiet", Int, &quiet, (char **)0x0 }, - { "login", Int, &make_login_shell, (char **)0x0 }, - { "nolineediting", Int, &no_line_editing, (char **)0x0 }, - { "nobraceexpansion", Int, &no_brace_expansion, (char **)0x0 }, - { "posix", Int, &posixly_correct, (char **)0x0 }, +#if defined (WORDEXP_OPTION) + { "wordexp", Int, &wordexp_only, (char **)0x0 }, +#endif { (char *)0x0, Int, (int *)0x0, (char **)0x0 } }; @@ -218,74 +281,151 @@ struct { longjmp back to main to execute a shell script, instead of calling main () again and resulting in indefinite, possibly fatal, stack growth. */ -jmp_buf subshell_top_level; +procenv_t subshell_top_level; int subshell_argc; char **subshell_argv; char **subshell_envp; +char *exec_argv0; + #if defined (BUFFERED_INPUT) /* The file descriptor from which the shell is reading input. */ int default_buffered_input = -1; #endif -static int want_pending_command; -static char *local_pending_command; +/* The following two variables are not static so they can show up in $-. */ +int read_from_stdin; /* -s flag supplied */ +int want_pending_command; /* -c flag supplied */ + +/* This variable is not static so it can be bound to $BASH_EXECUTION_STRING */ +char *command_execution_string; /* argument to -c option */ +char *shell_script_filename; /* shell script */ + +int malloc_trace_at_exit = 0; + +static int shell_reinitialized = 0; + +static FILE *default_input; + +static STRING_INT_ALIST *shopt_alist; +static int shopt_ind = 0, shopt_len = 0; + +static int parse_long_options PARAMS((char **, int, int)); +static int parse_shell_options PARAMS((char **, int, int)); +static int bind_args PARAMS((char **, int, int, int)); + +static void start_debugger PARAMS((void)); + +static void add_shopt_to_alist PARAMS((char *, int)); +static void run_shopt_alist PARAMS((void)); + +static void execute_env_file PARAMS((char *)); +static void run_startup_files PARAMS((void)); +static int open_shell_script PARAMS((char *)); +static void set_bash_input PARAMS((void)); +static int run_one_command PARAMS((char *)); +#if defined (WORDEXP_OPTION) +static int run_wordexp PARAMS((char *)); +#endif + +static int uidget PARAMS((void)); + +static void set_option_defaults PARAMS((void)); +static void reset_option_defaults PARAMS((void)); + +static void init_interactive PARAMS((void)); +static void init_noninteractive PARAMS((void)); +static void init_interactive_script PARAMS((void)); -static int isnetconn (); -static void run_startup_files (); +static void set_shell_name PARAMS((char *)); +static void shell_initialize PARAMS((void)); +static void shell_reinitialize PARAMS((void)); -static void shell_initialize (); -static void shell_reinitialize (); -static void initialize_signals (); -static void initialize_terminating_signals (); +static void show_shell_usage PARAMS((FILE *, int)); + +#ifdef __CYGWIN__ +static void +_cygwin32_check_tmp () +{ + struct stat sb; + + if (stat ("/tmp", &sb) < 0) + internal_warning (_("could not find /tmp, please create!")); + else + { + if (S_ISDIR (sb.st_mode) == 0) + internal_warning (_("/tmp must be a valid directory name")); + } +} +#endif /* __CYGWIN__ */ +#if defined (NO_MAIN_ENV_ARG) +/* systems without third argument to main() */ +int +main (argc, argv) + int argc; + char **argv; +#else /* !NO_MAIN_ENV_ARG */ +int main (argc, argv, env) int argc; char **argv, **env; +#endif /* !NO_MAIN_ENV_ARG */ { register int i; - int arg_index, locally_skip_execution; - int top_level_arg_index, read_from_stdin; - FILE *default_input; + 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 - /* There is a bug in the NeXT 2.1 rlogind that causes opens - of /dev/tty to fail. */ -#if defined (RLOGIN_PGRP_BUG) - { - int tty_fd; + /* Catch early SIGINTs. */ + code = setjmp_nosigs (top_level); + if (code) + exit (2); - tty_fd = open ("/dev/tty", O_RDWR); + xtrace_init (); - if (tty_fd < 0) - { - char *tty; - tty = (char *)ttyname (fileno (stdin)); - tty_fd = open (tty, O_RDWR); - } - close (tty_fd); - } -#endif /* RLOGIN_PGRP_BUG */ +#if defined (USING_BASH_MALLOC) && defined (DEBUG) && !defined (DISABLE_MALLOC_WRAPPERS) + malloc_set_register (1); /* XXX - change to 1 for malloc debugging */ +#endif + + check_dev_tty (); + +#ifdef __CYGWIN__ + _cygwin32_check_tmp (); +#endif /* __CYGWIN__ */ /* Wait forever if we are debugging a login shell. */ - while (debugging_login_shell); + while (debugging_login_shell) sleep (3); - current_user.uid = getuid (); - current_user.gid = getgid (); - current_user.euid = geteuid (); - current_user.egid = getegid (); + set_default_locale (); - /* See whether or not we are running setuid or setgid. */ - privileged_mode = (current_user.uid != current_user.euid) || - (current_user.gid != current_user.egid); + running_setuid = uidget (); - posixly_correct = (getenv ("POSIXLY_CORRECT") != (char *)NULL) || - (getenv ("POSIX_PEDANTIC") != (char *)NULL); + 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)) + if (setjmp_sigs (subshell_top_level)) { argc = subshell_argc; argv = subshell_argv; @@ -293,12 +433,14 @@ main (argc, argv, env) sourced_env = 0; } - /* Initialize local variables for all `invocations' of main (). */ + shell_reinitialized = 0; + + /* Initialize `local' variables for all `invocations' of main (). */ arg_index = 1; - local_pending_command = (char *)NULL; - want_pending_command = 0; - locally_skip_execution = 0; - read_from_stdin = 0; + if (arg_index > argc) + arg_index = argc; + command_execution_string = shell_script_filename = (char *)NULL; + want_pending_command = locally_skip_execution = read_from_stdin = 0; default_input = stdin; #if defined (BUFFERED_INPUT) default_buffered_input = -1; @@ -317,162 +459,74 @@ main (argc, argv, env) shell_name++; shell_reinitialize (); - if (setjmp (top_level)) + if (setjmp_nosigs (top_level)) exit (2); } - /* 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. */ - /* XXX - next version - make this be the same as -posix. */ - shell_name = base_pathname (argv[0]); - if (*shell_name == '-') - shell_name++; - if (shell_name[0] == 's' && shell_name[1] == 'h' && !shell_name[2]) - act_like_sh++; - - yydebug = 0; - shell_environment = env; - shell_name = argv[0]; - dollar_vars[0] = savestring (shell_name); - - if (*shell_name == '-') - { - shell_name++; - login_shell++; - } - -#if defined (JOB_CONTROL) - if (act_like_sh) - job_control = 0; /* XXX - not posix */ -#endif /* JOB_CONTROL */ + set_shell_name (argv[0]); - shell_start_time = NOW; /* NOW now defined in general.h */ - - /* 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 = "bash"; + gettimeofday (&shellstart, 0); + shell_start_time = shellstart.tv_sec; /* Parse argument flags from the input line. */ /* Find full word arguments first. */ - while ((arg_index != argc) && *(argv[arg_index]) == '-') + arg_index = parse_long_options (argv, arg_index, argc); + + if (want_initial_help) { - for (i = 0; long_args[i].name; i++) - { - if (STREQ (&(argv[arg_index][1]), long_args[i].name)) - { - if (long_args[i].type == Int) - *long_args[i].int_value = 1; - else - { - if (!argv[++arg_index]) - { - report_error ("option `%s' expected an argument", - long_args[i].name); - exit (1); - } - else - *long_args[i].char_value = argv[arg_index]; - } - goto handle_next_arg; - } - } - break; /* No such argument. Maybe flag arg. */ - handle_next_arg: - arg_index++; + show_shell_usage (stdout, 1); + exit (EXECUTION_SUCCESS); } - /* If we're in a strict Posix.2 mode, turn on interactive comments. */ - if (posixly_correct) - interactive_comments = 1; - - /* If user supplied the "-login" flag, then set and invert LOGIN_SHELL. */ - if (make_login_shell) + if (do_version) { - login_shell++; - login_shell = -login_shell; + show_shell_version (1); + exit (EXECUTION_SUCCESS); } + echo_input_at_read = verbose_flag; /* --verbose given */ + /* All done with full word options; do standard shell option parsing.*/ this_command_name = shell_name; /* for error reporting */ - while (arg_index != argc && argv[arg_index] && - (*argv[arg_index] == '-' || *argv[arg_index] == '+')) - { - /* There are flag arguments, so parse them. */ - int arg_character, on_or_off, next_arg; - char *o_option, *arg_string; - - i = 1; - next_arg = arg_index + 1; - arg_string = argv[arg_index]; - on_or_off = arg_string[0]; - - /* 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'))) - { - arg_index++; - break; - } + arg_index = parse_shell_options (argv, arg_index, argc); - while (arg_character = arg_string[i++]) - { - switch (arg_character) - { - case 'c': - want_pending_command = 1; - break; + /* If user supplied the "--login" (or -l) flag, then set and invert + LOGIN_SHELL. */ + if (make_login_shell) + { + login_shell++; + login_shell = -login_shell; + } - case 's': - read_from_stdin = 1; - break; + set_login_shell ("login_shell", login_shell != 0); - case 'o': - o_option = argv[next_arg]; - if (!o_option) - { - list_minus_o_opts (); - break; - } - if (set_minus_o_option (on_or_off, o_option) != EXECUTION_SUCCESS) - exit (1); - next_arg++; - break; +#if defined (TRANSLATABLE_STRINGS) + if (dump_po_strings) + dump_translatable_strings = 1; - default: - if (change_flag (arg_character, on_or_off) == FLAG_ERROR) - { - report_error ("%c%c: bad option", on_or_off, arg_character); - exit (1); - } + if (dump_translatable_strings) + read_but_dont_execute = 1; +#endif - } - } - /* Can't do just a simple increment anymore -- what about - "bash -abouo emacs ignoreeof -hP"? */ - arg_index = next_arg; - } + 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) + command_execution_string = argv[arg_index]; + if (command_execution_string == 0) { - report_error ("`-c' requires an argument"); - exit (1); + report_error (_("%s: option requires an argument"), "-c"); + exit (EX_BADUSAGE); } arg_index++; } - this_command_name = (char *)NULL; + 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 @@ -480,90 +534,136 @@ main (argc, argv, env) no -c command no arguments remaining or the -s flag given standard input is a terminal - standard output is a terminal + standard error 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 ... */ + (!command_execution_string && /* No -c command and ... */ + wordexp_only == 0 && /* No --wordexp and ... */ ((arg_index == argc) || /* no remaining args or... */ read_from_stdin) && /* -s flag with args, and */ isatty (fileno (stdin)) && /* Input is a terminal and */ - isatty (fileno (stdout)))) /* output is a terminal. */ - { - interactive_shell = startup_state = interactive = 1; - } + isatty (fileno (stderr)))) /* error output is a terminal. */ + init_interactive (); else - { -#if defined (HISTORY) -# if defined (BANG_HISTORY) - history_expansion = 0; -# endif - remember_on_history = 0; -#endif /* HISTORY */ - interactive_shell = startup_state = interactive = 0; - no_line_editing = 1; -#if defined (JOB_CONTROL) - job_control = 0; -#endif /* JOB_CONTROL */ - } + 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. + * means one for login, one for xterm, one for shelltool, etc. There are + * also systems that open persistent FDs to other agents or files as part + * of process startup; these need to be set to be close-on-exec. */ if (login_shell && interactive_shell) { for (i = 3; i < 20; i++) - close (i); + SET_CLOSE_ON_EXEC (i); + } + + /* If we're in a strict Posix.2 mode, turn on interactive comments, + alias expansion in non-interactive shells, and other Posix.2 things. */ + if (posixly_correct) + { + bind_variable ("POSIXLY_CORRECT", "y", 0); + sv_strict_posix ("POSIXLY_CORRECT"); } -#endif /* CLOSE_FDS_AT_LOGIN */ + + /* Now we run the shopt_alist and process the options. */ + if (shopt_alist) + run_shopt_alist (); /* From here on in, the shell must be a normal functioning shell. Variables from the environment are expected to be set, etc. */ shell_initialize (); + set_default_lang (); + set_default_locale_vars (); + + /* + * M-x term -> TERM=eterm-color INSIDE_EMACS='251,term:0.96' (eterm) + * M-x shell -> TERM='dumb' INSIDE_EMACS='25.1,comint' (no line editing) + * + * Older versions of Emacs may set EMACS to 't' or to something like + * '22.1 (term:0.96)' instead of (or in addition to) setting INSIDE_EMACS. + * They may set TERM to 'eterm' instead of 'eterm-color'. They may have + * a now-obsolete command that sets neither EMACS nor INSIDE_EMACS: + * M-x terminal -> TERM='emacs-em7955' (line editing) + */ if (interactive_shell) { - char *term = getenv ("TERM"); - no_line_editing |= term && (STREQ (term, "emacs")); + char *term, *emacs, *inside_emacs; + int emacs_term, in_emacs; + + term = get_string_value ("TERM"); + emacs = get_string_value ("EMACS"); + inside_emacs = get_string_value ("INSIDE_EMACS"); + + if (inside_emacs) + { + emacs_term = strstr (inside_emacs, ",term:") != 0; + in_emacs = 1; + } + else if (emacs) + { + /* Infer whether we are in an older Emacs. */ + emacs_term = strstr (emacs, " (term:") != 0; + in_emacs = emacs_term || STREQ (emacs, "t"); + } + else + in_emacs = emacs_term = 0; + + /* Not sure any emacs terminal emulator sets TERM=emacs any more */ + no_line_editing |= STREQ (term, "emacs"); + no_line_editing |= in_emacs && STREQ (term, "dumb"); + + /* running_under_emacs == 2 for `eterm' */ + running_under_emacs = in_emacs || STREQN (term, "emacs", 5); + running_under_emacs += emacs_term && STREQN (term, "eterm", 5); + + if (running_under_emacs) + gnu_error_format = 1; } top_level_arg_index = arg_index; - - if (!quiet && do_version) - show_shell_version (); + 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. */ - { - int code; - - code = setjmp (top_level); - - if (code) - { - if (code == EXITPROG) - goto exit_shell; - else + code = setjmp_sigs (top_level); + if (code) + { + if (code == EXITPROG || code == ERREXIT || code == EXITBLTIN) + exit_shell (last_command_exit_value); + else + { +#if defined (JOB_CONTROL) + /* Reset job control, since run_startup_files turned it off. */ + set_job_control (interactive_shell); +#endif + /* Reset value of `set -e', since it's turned off before running + the startup files. */ + exit_immediately_on_error += old_errexit_flag; locally_skip_execution++; - } - } + } + } arg_index = top_level_arg_index; /* Execute the start-up scripts. */ - if (!interactive_shell) + if (interactive_shell == 0) { - makunbound ("PS1", shell_variables); - makunbound ("PS2", shell_variables); + unbind_variable ("PS1"); + unbind_variable ("PS2"); interactive = 0; +#if 0 + /* This has already been done by init_noninteractive */ + expand_aliases = posixly_correct; +#endif } else { @@ -571,54 +671,142 @@ main (argc, argv, env) interactive = 1; } - if (!locally_skip_execution) - run_startup_files (); +#if defined (RESTRICTED_SHELL) + /* Set restricted_shell based on whether the basename of $0 indicates that + the shell should be restricted or if the `-r' option was supplied at + startup. */ + restricted_shell = shell_is_restricted (shell_name); + + /* If the `-r' option is supplied at invocation, make sure that the shell + is not in restricted mode when running the startup files. */ + saverst = restricted; + restricted = 0; +#endif + + /* Set positional parameters before running startup files. top_level_arg_index + holds the index of the current argument before setting the positional + parameters, so any changes performed in the startup files won't affect + later option processing. */ + if (wordexp_only) + ; /* nothing yet */ + else if (command_execution_string) + arg_index = bind_args (argv, arg_index, argc, 0); /* $0 ... $n */ + else if (arg_index != argc && read_from_stdin == 0) + { + shell_script_filename = argv[arg_index++]; + arg_index = bind_args (argv, arg_index, argc, 1); /* $1 ... $n */ + } + else + arg_index = bind_args (argv, arg_index, argc, 1); /* $1 ... $n */ + + /* The startup files are run with `set -e' temporarily disabled. */ + if (locally_skip_execution == 0 && running_setuid == 0) + { + char *t; + + old_errexit_flag = exit_immediately_on_error; + exit_immediately_on_error = 0; + + /* Temporarily set $0 while running startup files, then restore it so + we get better error messages when trying to open script files. */ + if (shell_script_filename) + { + t = dollar_vars[0]; + dollar_vars[0] = exec_argv0 ? savestring (exec_argv0) : savestring (shell_script_filename); + } + run_startup_files (); + if (shell_script_filename) + { + free (dollar_vars[0]); + dollar_vars[0] = t; + } + exit_immediately_on_error += old_errexit_flag; + } + + /* If we are invoked as `sh', turn on Posix mode. */ + if (act_like_sh) + { + bind_variable ("POSIXLY_CORRECT", "y", 0); + sv_strict_posix ("POSIXLY_CORRECT"); + } #if defined (RESTRICTED_SHELL) - /* I turn on the restrictions afterwards because it is explictly - stated in the POSIX spec that PATH cannot be set in a restricted - shell, except in .profile. */ + /* 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 (local_pending_command) - { - /* Bind remaining args to $0 ... $n */ - WORD_LIST *args = (WORD_LIST *)NULL; - while (arg_index != argc) - args = make_word_list (make_word (argv[arg_index++]), args); - if (args) - { - args = REVERSE_LIST (args, WORD_LIST *); - /* Posix.2 4.56.3 says that the first argument after - sh -c command becomes $0, and the rest of the arguments - are bound to $1 ... $N. */ - shell_name = savestring (args->word->word); /* XXX */ - dollar_vars[0] = savestring (args->word->word); - remember_args (args->next, 1); - dispose_words (args); - } - - startup_state = 2; +#if defined (WORDEXP_OPTION) + if (wordexp_only) + { + startup_state = 3; + last_command_exit_value = run_wordexp (argv[top_level_arg_index]); + exit_shell (last_command_exit_value); + } +#endif + + cmd_init (); /* initialize the command object caches */ + uwp_init (); + + if (command_execution_string) + { + startup_state = 2; + + if (debugging_mode) + start_debugger (); + #if defined (ONESHOT) - run_one_command (local_pending_command); - goto exit_shell; + executing = 1; + run_one_command (command_execution_string); + exit_shell (last_command_exit_value); #else /* ONESHOT */ - with_input_from_string (local_pending_command, "-c"); - goto read_and_execute; + with_input_from_string (command_execution_string, "-c"); + goto read_and_execute; #endif /* !ONESHOT */ - } + } + + /* Get possible input filename and set up default_buffered_input or + default_input as appropriate. */ + if (shell_script_filename) + open_shell_script (shell_script_filename); + else if (interactive == 0) + { + /* In this mode, bash is reading a script from stdin, which is a + pipe or redirected file. */ +#if defined (BUFFERED_INPUT) + default_buffered_input = fileno (stdin); /* == 0 */ +#else + setbuf (default_input, (char *)NULL); +#endif /* !BUFFERED_INPUT */ + read_from_stdin = 1; + } + else if (top_level_arg_index == argc) /* arg index before startup files */ + /* "If there are no operands and the -c option is not specified, the -s + option shall be assumed." */ + read_from_stdin = 1; + + set_bash_input (); + + if (debugging_mode && locally_skip_execution == 0 && running_setuid == 0 && (reading_shell_script || interactive_shell == 0)) + start_debugger (); /* Do the things that should be done only for interactive shells. */ if (interactive_shell) { /* Set up for checking for presence of mail. */ - remember_mail_dates (); reset_mail_timer (); + init_mail_dates (); #if defined (HISTORY) /* Initialize the interactive history stuff. */ - if (!shell_initialized) + bash_initialize_history (); + /* Don't load the history from the history file if we've already + saved some lines in this session (e.g., by putting `history -s xx' + into one of the startup files). */ + if (shell_initialized == 0 && history_lines_this_session == 0) load_history (); #endif /* HISTORY */ @@ -627,407 +815,640 @@ main (argc, argv, env) get_tty_state (); } - /* Get possible input filename. */ - if ((arg_index != argc) && !read_from_stdin) - { - int fd; - char *filename; +#if !defined (ONESHOT) + read_and_execute: +#endif /* !ONESHOT */ - free (dollar_vars[0]); - dollar_vars[0] = savestring (argv[arg_index]); - filename = savestring (argv[arg_index]); + shell_initialized = 1; - fd = open (filename, O_RDONLY); - if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0)) - { - char *path_filename; - /* If it's not in the current directory, try looking through PATH - for it. */ - path_filename = find_path_file (argv[arg_index]); - if (path_filename) - { - free (filename); - filename = path_filename; - fd = open (filename, O_RDONLY); - } - } + if (pretty_print_mode && interactive_shell) + { + internal_warning (_("pretty-printing mode ignored in interactive shells")); + pretty_print_mode = 0; + } + if (pretty_print_mode) + exit_shell (pretty_print_loop ()); - arg_index++; - if (fd < 0) + /* 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]) { - file_error (filename); - exit (1); + longarg = 1; + arg_string++; } - /* Only do this with file descriptors we can seek on. */ - if (lseek (fd, 0L, 1) != -1) + for (i = 0; long_args[i].name; i++) { - unsigned char sample[80]; - int sample_len; - - /* 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 && (check_binary_file (sample, sample_len))) + if (STREQ (arg_string + 1, long_args[i].name)) { - report_error ("%s: cannot execute binary file", filename); - exit (EX_BINARY_FILE); + if (long_args[i].type == Int) + *long_args[i].int_value = 1; + else if (argv[++arg_index] == 0) + { + report_error (_("%s: option requires an argument"), long_args[i].name); + exit (EX_BADUSAGE); + } + else + *long_args[i].char_value = argv[arg_index]; + + break; } - /* Now rewind the file back to the beginning. */ - lseek (fd, 0L, 0); } - -#if defined (BUFFERED_INPUT) - default_buffered_input = fd; - if (default_buffered_input == -1) + if (long_args[i].name == 0) { - file_error (filename); - exit (127); + if (longarg) + { + report_error (_("%s: invalid option"), argv[arg_index]); + show_shell_usage (stderr, 0); + exit (EX_BADUSAGE); + } + break; /* No such argument. Maybe flag arg. */ } - SET_CLOSE_ON_EXEC (default_buffered_input); -#else /* !BUFFERED_INPUT */ + arg_index++; + } - /* 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. */ - { - int script_fd, nfds; - - nfds = getdtablesize (); - if (nfds <= 0) - nfds = 20; - if (nfds > 256) - nfds = 256; - script_fd = dup2 (fd, nfds - 1); - if (script_fd) - { - close (fd); - fd = script_fd; - } - } + 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; - default_input = fdopen (fd, "r"); + /* 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); - if (!default_input) + i = 1; + on_or_off = arg_string[0]; + while (arg_character = arg_string[i++]) { - file_error (filename); - exit (127); - } + switch (arg_character) + { + case 'c': + want_pending_command = 1; + break; - SET_CLOSE_ON_EXEC (fd); - if (fileno (default_input) != fd) - SET_CLOSE_ON_EXEC (fileno (default_input)); + case 'l': + make_login_shell = 1; + break; -#endif /* !BUFFERED_INPUT */ + case 's': + read_from_stdin = 1; + break; - if (!interactive_shell || (!isatty (fd))) - { -#if defined (HISTORY) -# if defined (BANG_HISTORY) - history_expansion = 0; -# endif - remember_on_history = 0; -#endif /* HISTORY */ - interactive = interactive_shell = 0; - no_line_editing = 1; -#if defined (JOB_CONTROL) - set_job_control (0); -#endif /* JOB_CONTROL */ - } - else - { - /* I don't believe that this code is ever executed, even in - the presence of /dev/fd. */ - dup2 (fd, 0); - close (fd); - fclose (default_input); - } - } - else if (!interactive) - /* 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 */ + case 'o': + o_option = argv[next_arg]; + if (o_option == 0) + { + set_option_defaults (); + list_minus_o_opts (-1, (on_or_off == '-') ? 0 : 1); + reset_option_defaults (); + break; + } + if (set_minus_o_option (on_or_off, o_option) != EXECUTION_SUCCESS) + exit (EX_BADUSAGE); + next_arg++; + break; - /* Bind remaining args to $1 ... $n */ - { - WORD_LIST *args = (WORD_LIST *)NULL; - while (arg_index != argc) - args = make_word_list (make_word (argv[arg_index++]), args); - args = REVERSE_LIST (args, WORD_LIST *); - remember_args (args, 1); - dispose_words (args); - } + 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; -#if defined (BUFFERED_INPUT) - if (!interactive) - unset_nodelay_mode (default_buffered_input); - else - unset_nodelay_mode (fileno (stdin)); -#else - unset_nodelay_mode (fileno (stdin)); -#endif /* !BUFFERED_INPUT */ + case 'D': +#if defined (TRANSLATABLE_STRINGS) + dump_translatable_strings = 1; +#endif + break; - /* with_input_from_stdin really means `with_input_from_readline' */ - if (interactive && !no_line_editing) - with_input_from_stdin (); - else -#if defined (BUFFERED_INPUT) - { - if (!interactive) - with_input_from_buffered_stream (default_buffered_input, dollar_vars[0]); - else - with_input_from_stream (default_input, dollar_vars[0]); + default: + if (change_flag (arg_character, on_or_off) == FLAG_ERROR) + { + report_error (_("%c%c: invalid option"), on_or_off, arg_character); + show_shell_usage (stderr, 0); + exit (EX_BADUSAGE); + } + } + } + /* Can't do just a simple increment anymore -- what about + "bash -abouo emacs ignoreeof -hP"? */ + arg_index = next_arg; } -#else /* !BUFFERED_INPUT */ - with_input_from_stream (default_input, dollar_vars[0]); -#endif /* !BUFFERED_INPUT */ -#if !defined (ONESHOT) - read_and_execute: -#endif /* !ONESHOT */ + return (arg_index); +} - shell_initialized = 1; +/* Exit the shell with status S. */ +void +exit_shell (s) + int s; +{ + fflush (stdout); /* XXX */ + fflush (stderr); - /* Read commands until exit condition. */ - reader_loop (); + /* Clean up the terminal if we are in a state where it's been modified. */ +#if defined (READLINE) + if (RL_ISSTATE (RL_STATE_TERMPREPPED) && rl_deprep_term_function) + (*rl_deprep_term_function) (); +#endif + if (read_tty_modified ()) + read_tty_cleanup (); - exit_shell: - /* Do trap[0] if defined. */ + /* Do trap[0] if defined. Allow it to override the exit status + passed to us. */ if (signal_is_trapped (0)) - last_command_exit_value = run_exit_trap (); + s = run_exit_trap (); #if defined (PROCESS_SUBSTITUTION) - unlink_fifo_list (); + unlink_all_fifos (); #endif /* PROCESS_SUBSTITUTION */ #if defined (HISTORY) - if (interactive_shell) + if (remember_on_history) maybe_save_shell_history (); #endif /* HISTORY */ +#if defined (COPROCESS_SUPPORT) + coproc_flush (); +#endif + #if defined (JOB_CONTROL) - /* If this shell is interactive, terminate all stopped jobs and - restore the original terminal process group. */ - end_job_control (); + /* If the user has run `shopt -s huponexit', hangup all jobs when we exit + an interactive login shell. ksh does this unconditionally. */ + if (interactive_shell && login_shell && hup_on_exit) + hangup_all_jobs (); + + /* If this shell is interactive, or job control is active, terminate all + stopped jobs and restore the original terminal process group. Don't do + this if we're in a subshell and calling exit_shell after, for example, + a failed word expansion. We want to do this even if the shell is not + interactive because we set the terminal's process group when job control + is enabled regardless of the interactive status. */ + if (subshell_environment == 0) + end_job_control (); #endif /* JOB_CONTROL */ /* Always return the exit status of the last command to our parent. */ - exit (last_command_exit_value); + sh_exit (s); +} + +/* A wrapper for exit that (optionally) can do other things, like malloc + statistics tracing. */ +void +sh_exit (s) + int s; +{ +#if defined (MALLOC_DEBUG) && defined (USING_BASH_MALLOC) + if (malloc_trace_at_exit && (subshell_environment & (SUBSHELL_COMSUB|SUBSHELL_PROCSUB)) == 0) + trace_malloc_stats (get_name_for_error (), (char *)NULL); + /* mlocation_write_table (); */ +#endif + + exit (s); +} + +/* Exit a subshell, which includes calling the exit trap. We don't want to + do any more cleanup, since a subshell is created as an exact copy of its + parent. */ +void +subshell_exit (s) + int s; +{ + fflush (stdout); + fflush (stderr); + + /* Do trap[0] if defined. Allow it to override the exit status + passed to us. */ + last_command_exit_value = s; + if (signal_is_trapped (0)) + s = run_exit_trap (); + + sh_exit (s); } -#if !defined (SYS_PROFILE) -# define SYS_PROFILE "/etc/profile" -#endif /* !SYS_PROFILE */ +void +set_exit_status (s) + int s; +{ + set_pipestatus_from_exit (last_command_exit_value = s); +} /* Source the bash startup files. If POSIXLY_CORRECT is non-zero, we obey the Posix.2 startup file rules: $ENV is expanded, and if the file it names exists, that file is sourced. The Posix.2 rules are in effect - for both interactive and non-interactive shells (section 4.56.5.3) */ + 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 (!posixly_correct) +#if defined (JOB_CONTROL) + int old_job_control; +#endif + int sourced_login, run_by_ssh; + +#if 1 /* TAG:bash-5.3 andrew.gregory.8@gmail.com 2/21/2022 */ + /* get the rshd/sshd case out of the way first. */ + if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 && + act_like_sh == 0 && command_execution_string) { - if (login_shell) +#ifdef SSH_SOURCE_BASHRC + run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) || + (find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0); +#else + run_by_ssh = 0; +#endif +#endif + + /* If we were run by sshd or we think we were run by rshd, execute + ~/.bashrc if we are a top-level shell. */ +#if 1 /* TAG:bash-5.3 */ + if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2) +#else + if (isnetconn (fileno (stdin) && shell_level < 2) +#endif { - /* We don't execute .bashrc for login shells. */ - no_rc++; - if (no_profile == 0) - maybe_execute_file (SYS_PROFILE, 1); - } +#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 (login_shell && !no_profile) - { - if (act_like_sh) - maybe_execute_file ("~/.profile", 1); - else - { - if (maybe_execute_file ("~/.bash_profile", 1) == 0) - if (maybe_execute_file ("~/.bash_login", 1) == 0) - maybe_execute_file ("~/.profile", 1); - } - } +#if defined (JOB_CONTROL) + /* Startup files should be run without job control enabled. */ + old_job_control = interactive_shell ? set_job_control (0) : 0; +#endif - /* 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 - */ - if (!act_like_sh && !no_rc && - (interactive_shell || (isnetconn (fileno (stdin)) && - local_pending_command))) - maybe_execute_file (bashrc_file, 1); - } + sourced_login = 0; - /* Try a TMB suggestion. If running a script, then execute the - file mentioned in the ENV variable. */ - if (!privileged_mode && sourced_env++ == 0 && act_like_sh == 0 && - (posixly_correct || !interactive_shell)) + /* A shell begun with the --login (or -l) flag that is not in posix mode + runs the login shell startup files, no matter whether or not it is + interactive. If NON_INTERACTIVE_LOGIN_SHELLS is defined, run the + startup files if argv[0][0] == '-' as well. */ +#if defined (NON_INTERACTIVE_LOGIN_SHELLS) + if (login_shell && posixly_correct == 0) +#else + if (login_shell < 0 && posixly_correct == 0) +#endif { - char *env_file = (char *)NULL; + /* 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); + } - if (!posixly_correct) - env_file = getenv ("BASH_ENV"); - if (!env_file) - env_file = getenv ("ENV"); + 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; + } - if (env_file && *env_file) + /* Interactive shell or `-su' shell. */ + if (posixly_correct == 0) /* bash, sh */ + { + if (login_shell && sourced_login++ == 0) { - WORD_LIST *list; - char *expanded_file_name; + /* We don't execute .bashrc for login shells. */ + no_rc++; - list = expand_string_unsplit (env_file, 1); - if (list) + /* Execute /etc/profile and one of the personal login shell + initialization files. */ + if (no_profile == 0) { - expanded_file_name = string_list (list); - dispose_words (list); - - if (expanded_file_name && *expanded_file_name) - maybe_execute_file (expanded_file_name, 1); + maybe_execute_file (SYS_PROFILE, 1); - if (expanded_file_name) - free (expanded_file_name); + 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) -/* Perhaps make this shell a `restricted' one, based on NAME. - If the basename of NAME is "rbash", then this shell is restricted. - In a restricted shell, PATH and SHELL are read-only and non-unsettable. +/* Return 1 if the shell should be a restricted one based on NAME or the + value of `restricted'. Don't actually do anything, just return a + boolean value. */ +int +shell_is_restricted (name) + char *name; +{ + char *temp; + + if (restricted) + return 1; + temp = base_pathname (name); + if (*temp == '-') + temp++; + return (STREQ (temp, RESTRICTED_SHELL_NAME)); +} + +/* Perhaps make this shell a `restricted' one, based on NAME. If the + basename of NAME is "rbash", then this shell is restricted. The + name of the restricted shell is a configurable option, see config.h. + In a restricted shell, PATH, SHELL, ENV, and BASH_ENV are read-only + and non-unsettable. Do this also if `restricted' is already set to 1; maybe the shell was started with -r. */ +int maybe_make_restricted (name) char *name; { char *temp; - temp = base_pathname (shell_name); - if (restricted || (STREQ (temp, "rbash"))) + temp = base_pathname (name); + if (*temp == '-') + temp++; + if (restricted || (STREQ (temp, RESTRICTED_SHELL_NAME))) { +#if defined (RBASH_STATIC_PATH_VALUE) + bind_variable ("PATH", RBASH_STATIC_PATH_VALUE, 0); + stupidly_hack_special_variables ("PATH"); /* clear hash table */ +#endif set_var_read_only ("PATH"); - non_unsettable ("PATH"); set_var_read_only ("SHELL"); - non_unsettable ("SHELL"); - restricted++; + set_var_read_only ("ENV"); + set_var_read_only ("BASH_ENV"); + set_var_read_only ("HISTFILE"); + restricted = 1; } + return (restricted); } #endif /* RESTRICTED_SHELL */ -/* Try to execute the contents of FNAME. If FNAME doesn't exist, - that is not an error, but other kinds of errors are. A non-zero - FORCE_NONINTERACTIVE means to set the value of `interactive' to - 0 so things like job control are disabled; the value is unchanged - otherwise. Returns -1 in the case of an error, 0 in the case that - the file was not found, and 1 if the file was found and executed. */ -maybe_execute_file (fname, force_noninteractive) - char *fname; - int force_noninteractive; +/* Fetch the current set of uids and gids and return 1 if we're running + setuid or setgid. */ +static int +uidget () { - jmp_buf old_return_catch; - int return_val, fd, tresult, old_interactive; - char *filename, *string; - struct stat file_info; - - filename = tilde_expand (fname); - fd = open (filename, O_RDONLY); + uid_t u; - if (fd < 0) + u = getuid (); + if (current_user.uid != u) { -file_error_and_exit: - if (errno != ENOENT) - file_error (filename); - free (filename); - return ((errno == ENOENT) ? 0 : -1); + 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 (); - if (fstat (fd, &file_info) == -1) - goto file_error_and_exit; + /* See whether or not we are running setuid or setgid. */ + return (current_user.uid != current_user.euid) || + (current_user.gid != current_user.egid); +} + +void +disable_priv_mode () +{ + int e; - if (S_ISDIR (file_info.st_mode)) +#if HAVE_SETRESUID + if (setresuid (current_user.uid, current_user.uid, current_user.uid) < 0) +#else + if (setuid (current_user.uid) < 0) +#endif { - internal_error ("%s: cannot execute directories", filename); - free (filename); - return -1; + e = errno; + sys_error (_("cannot set uid to %d: effective uid %d"), current_user.uid, current_user.euid); +#if defined (EXIT_ON_SETUID_FAILURE) + if (e == EAGAIN) + exit (e); +#endif } +#if HAVE_SETRESGID + if (setresgid (current_user.gid, current_user.gid, current_user.gid) < 0) +#else + if (setgid (current_user.gid) < 0) +#endif + sys_error (_("cannot set gid to %d: effective gid %d"), current_user.gid, current_user.egid); + + current_user.euid = current_user.uid; + current_user.egid = current_user.gid; +} - string = (char *)xmalloc (1 + (int)file_info.st_size); - tresult = read (fd, string, file_info.st_size); +#if defined (WORDEXP_OPTION) +static int +run_wordexp (words) + char *words; +{ + int code, nw, nb; + WORD_LIST *wl, *tl, *result; - { - int tt = errno; - close (fd); - errno = tt; - } + code = setjmp_nosigs (top_level); - if (tresult != file_info.st_size) + if (code != NOT_JUMPED) { - free (string); - goto file_error_and_exit; + switch (code) + { + /* Some kind of throw to top_level has occurred. */ + case FORCE_EOF: + return last_command_exit_value = 127; + case ERREXIT: + case EXITPROG: + case EXITBLTIN: + return last_command_exit_value; + case DISCARD: + return last_command_exit_value = 1; + default: + command_error ("run_wordexp", CMDERR_BADJUMP, code, 0); + } } - string[file_info.st_size] = '\0'; - - return_catch_flag++; - xbcopy ((char *)return_catch, (char *)old_return_catch, sizeof (jmp_buf)); - if (force_noninteractive) + /* Run it through the parser to get a list of words and expand them */ + if (words && *words) { - old_interactive = interactive; - interactive = 0; + with_input_from_string (words, "--wordexp"); + if (parse_command () != 0) + return (126); + if (global_command == 0) + { + printf ("0\n0\n"); + return (0); + } + if (global_command->type != cm_simple) + return (126); + wl = global_command->value.Simple->words; + if (protected_mode) + for (tl = wl; tl; tl = tl->next) + tl->word->flags |= W_NOCOMSUB|W_NOPROCSUB; + result = wl ? expand_words_no_vars (wl) : (WORD_LIST *)0; } - - return_val = setjmp (return_catch); - - /* If `return' was seen outside of a function, but in the script, then - force parse_and_execute () to clean up. */ - if (return_val) - parse_and_execute_cleanup (); else - tresult = parse_and_execute (string, filename, -1); + result = (WORD_LIST *)0; - if (force_noninteractive) - interactive = old_interactive; + last_command_exit_value = 0; - return_catch_flag--; - xbcopy ((char *)old_return_catch, (char *)return_catch, sizeof (jmp_buf)); + if (result == 0) + { + printf ("0\n0\n"); + return (0); + } - free (filename); + /* 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 (1); + return (0); } +#endif #if defined (ONESHOT) /* Run one command, given as the argument to the -c option. Tell parse_and_execute not to fork for a simple command. */ +static int run_one_command (command) char *command; { int code; - code = setjmp (top_level); + code = setjmp_nosigs (top_level); if (code != NOT_JUMPED) { @@ -1036,240 +1457,469 @@ run_one_command (command) #endif /* PROCESS_SUBSTITUTION */ switch (code) { - /* Some kind of throw to top_level has occured. */ + /* Some kind of throw to top_level has occurred. */ case FORCE_EOF: return last_command_exit_value = 127; + case ERREXIT: case EXITPROG: + case EXITBLTIN: return last_command_exit_value; case DISCARD: return last_command_exit_value = 1; default: - programming_error ("Bad jump %d", code); + command_error ("run_one_command", CMDERR_BADJUMP, code, 0); } } - return (parse_and_execute (savestring (command), "-c", -1)); + return (parse_and_execute (savestring (command), "-c", SEVAL_NOHIST|SEVAL_RESETLINE)); } #endif /* ONESHOT */ -reader_loop () +static int +bind_args (argv, arg_start, arg_end, start_index) + char **argv; + int arg_start, arg_end, start_index; { - int our_indirection_level; - COMMAND *current_command = (COMMAND *)NULL; - - our_indirection_level = ++indirection_level; + register int i; + WORD_LIST *args, *tl; - while (!EOF_Reached) + for (i = arg_start, args = tl = (WORD_LIST *)NULL; i < arg_end; i++) { - int code; - - code = setjmp (top_level); + if (args == 0) + args = tl = make_word_list (make_word (argv[i]), args); + else + { + tl->next = make_word_list (make_word (argv[i]), (WORD_LIST *)NULL); + tl = tl->next; + } + } -#if 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) + if (args) + { + if (start_index == 0) /* bind to $0...$n for sh -c command */ { - indirection_level = our_indirection_level; - - switch (code) + /* Posix.2 4.56.3 says that the first argument after sh -c command + becomes $0, and the rest of the arguments become $1...$n */ + shell_name = savestring (args->word->word); + FREE (dollar_vars[0]); + dollar_vars[0] = savestring (args->word->word); + remember_args (args->next, 1); + if (debugging_mode) { - /* Some kind of throw to top_level has occured. */ - case FORCE_EOF: - case EXITPROG: - current_command = (COMMAND *)NULL; - EOF_Reached = EOF; - goto exec_done; - - case DISCARD: - /* Obstack free command elements, etc. */ - if (current_command) - { - dispose_command (current_command); - current_command = (COMMAND *)NULL; - } - last_command_exit_value = 1; - break; - - default: - programming_error ("Bad jump %d", code); + push_args (args->next); /* BASH_ARGV and BASH_ARGC */ + bash_argv_initialized = 1; } } + else /* bind to $1...$n for shell script */ + { + remember_args (args, 1); + /* We do this unconditionally so something like -O extdebug doesn't + do it first. We're setting the definitive positional params + here. */ + if (debugging_mode) + { + push_args (args); /* BASH_ARGV and BASH_ARGC */ + bash_argv_initialized = 1; + } + } + + dispose_words (args); + } + + return (i); +} + +void +unbind_args () +{ + remember_args ((WORD_LIST *)NULL, 1); + pop_args (); /* Reset BASH_ARGV and BASH_ARGC */ +} - executing = 0; - dispose_used_env_vars (); +static void +start_debugger () +{ +#if defined (DEBUGGER) && defined (DEBUGGER_START_FILE) + int old_errexit; + int r; + + old_errexit = exit_immediately_on_error; + exit_immediately_on_error = 0; + + r = force_execute_file (DEBUGGER_START_FILE, 1); + if (r < 0) + { + internal_warning (_("cannot start debugger; debugging mode disabled")); + debugging_mode = 0; + } + error_trace_mode = function_trace_mode = debugging_mode; + + set_shellopts (); + set_bashopts (); -#if (defined (Ultrix) && defined (mips)) || !defined (HAVE_ALLOCA) - /* Attempt to reclaim memory allocated with alloca (). */ - (void) alloca (0); + exit_immediately_on_error += old_errexit; #endif +} - if (read_command () == 0) +static int +open_shell_script (script_name) + char *script_name; +{ + int fd, e, fd_is_tty; + char *filename, *path_filename, *t; + char sample[80]; + int sample_len; + struct stat sb; +#if defined (ARRAY_VARS) + SHELL_VAR *funcname_v, *bash_source_v, *bash_lineno_v; + ARRAY *funcname_a, *bash_source_a, *bash_lineno_a; +#endif + + filename = savestring (script_name); + + fd = open (filename, O_RDONLY); + if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0)) + { + e = errno; + /* If it's not in the current directory, try looking through PATH + for it. */ + path_filename = find_path_file (script_name); + if (path_filename) { - if (global_command) - { - current_command = global_command; + free (filename); + filename = path_filename; + fd = open (filename, O_RDONLY); + } + else + errno = e; + } - current_command_number++; + if (fd < 0) + { + e = errno; + file_error (filename); +#if defined (JOB_CONTROL) + end_job_control (); /* just in case we were run as bash -i script */ +#endif + sh_exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT); + } - /* POSIX spec: "-n: The shell reads commands but does - not execute them; this can be used to check for shell - script syntax errors. The shell ignores the -n option - for interactive shells. " */ - if (interactive_shell || !read_but_dont_execute) - { - executing = 1; - execute_command (current_command); - } + free (dollar_vars[0]); + dollar_vars[0] = exec_argv0 ? savestring (exec_argv0) : savestring (script_name); + if (exec_argv0) + { + free (exec_argv0); + exec_argv0 = (char *)NULL; + } - exec_done: - if (current_command) - { - dispose_command (current_command); - current_command = (COMMAND *)NULL; - } - QUIT; + if (file_isdir (filename)) + { +#if defined (EISDIR) + errno = EISDIR; +#else + errno = EINVAL; +#endif + file_error (filename); +#if defined (JOB_CONTROL) + end_job_control (); /* just in case we were run as bash -i script */ +#endif + sh_exit (EX_NOINPUT); + } + +#if defined (ARRAY_VARS) + GET_ARRAY_FROM_VAR ("FUNCNAME", funcname_v, funcname_a); + GET_ARRAY_FROM_VAR ("BASH_SOURCE", bash_source_v, bash_source_a); + GET_ARRAY_FROM_VAR ("BASH_LINENO", bash_lineno_v, bash_lineno_a); + + array_push (bash_source_a, filename); + if (bash_lineno_a) + { + t = itos (executing_line_number ()); + array_push (bash_lineno_a, t); + free (t); + } + array_push (funcname_a, "main"); +#endif + +#ifdef HAVE_DEV_FD + fd_is_tty = isatty (fd); +#else + fd_is_tty = 0; +#endif + + /* Only do this with non-tty file descriptors we can seek on. */ + if (fd_is_tty == 0 && (lseek (fd, 0L, 1) != -1)) + { + /* Check to see if the `file' in `bash file' is a binary file + according to the same tests done by execute_simple_command (), + and report an error and exit if it is. */ + sample_len = read (fd, sample, sizeof (sample)); + if (sample_len < 0) + { + e = errno; + if ((fstat (fd, &sb) == 0) && S_ISDIR (sb.st_mode)) + { +#if defined (EISDIR) + errno = EISDIR; + file_error (filename); +#else + internal_error (_("%s: Is a directory"), filename); +#endif } + else + { + errno = e; + file_error (filename); + } +#if defined (JOB_CONTROL) + end_job_control (); /* just in case we were run as bash -i script */ +#endif + exit (EX_NOEXEC); } - else + else if (sample_len > 0 && (check_binary_file (sample, sample_len))) { - /* Parse error, maybe discard rest of stream if not interactive. */ - if (!interactive) - EOF_Reached = EOF; + internal_error (_("%s: cannot execute binary file"), filename); +#if defined (JOB_CONTROL) + end_job_control (); /* just in case we were run as bash -i script */ +#endif + exit (EX_BINARY_FILE); } - if (just_one_command) - EOF_Reached = EOF; + /* Now rewind the file back to the beginning. */ + lseek (fd, 0L, 0); } - indirection_level--; -} -/* Return a string denoting what our indirection level is. */ -static char indirection_string[100]; + /* Open the script. But try to move the file descriptor to a randomly + large one, in the hopes that any descriptors used by the script will + not match with ours. */ + fd = move_to_high_fd (fd, 1, -1); -char * -indirection_level_string () -{ - register int i, j; - char *ps4; +#if defined (BUFFERED_INPUT) + default_buffered_input = fd; + SET_CLOSE_ON_EXEC (default_buffered_input); +#else /* !BUFFERED_INPUT */ + default_input = fdopen (fd, "r"); - indirection_string[0] = '\0'; - ps4 = get_string_value ("PS4"); + if (default_input == 0) + { + file_error (filename); + exit (EX_NOTFOUND); + } - if (ps4 == 0 || *ps4 == '\0') - return (indirection_string); - - ps4 = decode_prompt_string (ps4); + SET_CLOSE_ON_EXEC (fd); + if (fileno (default_input) != fd) + SET_CLOSE_ON_EXEC (fileno (default_input)); +#endif /* !BUFFERED_INPUT */ - for (i = 0; *ps4 && i < indirection_level && i < 99; i++) - indirection_string[i] = *ps4; + /* Just about the only way for this code to be executed is if something + like `bash -i /dev/stdin' is executed. */ + if (interactive_shell && fd_is_tty) + { + dup2 (fd, 0); + close (fd); + fd = 0; +#if defined (BUFFERED_INPUT) + default_buffered_input = 0; +#else + fclose (default_input); + default_input = stdin; +#endif + } + else if (forced_interactive && fd_is_tty == 0) + /* But if a script is called with something like `bash -i scriptname', + we need to do a non-interactive setup here, since we didn't do it + before. */ + init_interactive_script (); - for (j = 1; *ps4 && ps4[j] && i < 99; i++, j++) - indirection_string[i] = ps4[j]; + free (filename); - indirection_string[i] = '\0'; - free (ps4); - return (indirection_string); + reading_shell_script = 1; + return (fd); } -static sighandler -alrm_catcher(i) - int i; +/* Initialize the input routines for the parser. */ +static void +set_bash_input () { - printf ("%ctimed out waiting for input: auto-logout\n", '\07'); - longjmp (top_level, EXITPROG); -#if !defined (VOID_SIGHANDLER) - return (0); -#endif /* !VOID_SIGHANDLER */ + /* Make sure the fd from which we are reading input is not in + no-delay mode. */ +#if defined (BUFFERED_INPUT) + if (interactive == 0) + sh_unset_nodelay_mode (default_buffered_input); + else +#endif /* !BUFFERED_INPUT */ + sh_unset_nodelay_mode (fileno (stdin)); + + /* with_input_from_stdin really means `with_input_from_readline' */ + if (interactive && no_line_editing == 0) + with_input_from_stdin (); +#if defined (BUFFERED_INPUT) + else if (interactive == 0) + with_input_from_buffered_stream (default_buffered_input, dollar_vars[0]); +#endif /* BUFFERED_INPUT */ + else + with_input_from_stream (default_input, dollar_vars[0]); } -parse_command () +/* 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; { - int r; +#if defined (BUFFERED_INPUT) + if ((check_zero && default_buffered_input >= 0) || + (check_zero == 0 && default_buffered_input > 0)) + { + close_buffered_fd (default_buffered_input); + default_buffered_input = bash_input.location.buffered_fd = -1; + bash_input.type = st_none; /* XXX */ + } +#else /* !BUFFERED_INPUT */ + if (default_input) + { + fclose (default_input); + default_input = (FILE *)NULL; + } +#endif /* !BUFFERED_INPUT */ +} + + +#if !defined (PROGRAM) +# define PROGRAM "bash" +#endif - need_here_doc = 0; - run_pending_traps (); +static void +set_shell_name (argv0) + char *argv0; +{ + /* Here's a hack. If the name of this shell is "sh", then don't do + any startup files; just try to be more like /bin/sh. */ + shell_name = argv0 ? base_pathname (argv0) : PROGRAM; - /* 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) + if (argv0 && *argv0 == '-') { - char *command_to_execute; - - command_to_execute = get_string_value ("PROMPT_COMMAND"); - if (command_to_execute) - execute_prompt_command (command_to_execute); + if (*shell_name == '-') + shell_name++; + login_shell = 1; } - current_command_line_count = 0; - r = yyparse (); + 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++; - if (need_here_doc) - gather_here_documents (); + shell_name = argv0 ? argv0 : PROGRAM; + FREE (dollar_vars[0]); + dollar_vars[0] = savestring (shell_name); - return (r); + /* 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; } -read_command () +/* Some options are initialized to -1 so we have a way to determine whether + they were set on the command line. This is an issue when listing the option + values at invocation (`bash -o'), so we set the defaults here and reset + them after the call to list_minus_o_options (). */ +/* XXX - could also do this for histexp_flag, jobs_m_flag */ +static void +set_option_defaults () { - SHELL_VAR *tmout_var = (SHELL_VAR *)NULL; - int tmout_len = 0, result; - SigHandler *old_alrm = (SigHandler *)NULL; - - prompt_string_pointer = &ps1_prompt; - global_command = (COMMAND *)NULL; - - /* Only do timeouts if interactive. */ - 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); - } - } - } +#if defined (HISTORY) + enable_history_list = 0; +#endif +} - QUIT; +static void +reset_option_defaults () +{ +#if defined (HISTORY) + enable_history_list = -1; +#endif +} - current_command_line_count = 0; - result = parse_command (); +static void +init_interactive () +{ + expand_aliases = expaliases_flag = 1; + interactive_shell = startup_state = interactive = 1; +#if defined (HISTORY) + if (enable_history_list == -1) + enable_history_list = 1; /* set default */ + remember_on_history = enable_history_list; +# if defined (BANG_HISTORY) + histexp_flag = history_expansion; /* XXX */ +# endif +#endif +} - if (interactive && tmout_var && (tmout_len > 0)) - { - alarm(0); - set_signal_handler (SIGALRM, old_alrm); - } - return (result); +static void +init_noninteractive () +{ +#if defined (HISTORY) + if (enable_history_list == -1) /* set default */ + enable_history_list = 0; + bash_history_reinit (0); +#endif /* HISTORY */ + interactive_shell = startup_state = interactive = 0; + expand_aliases = expaliases_flag = posixly_correct; /* XXX - was 0 not posixly_correct */ + no_line_editing = 1; +#if defined (JOB_CONTROL) + /* Even if the shell is not interactive, enable job control if the -i or + -m option is supplied at startup. */ + set_job_control (forced_interactive||jobs_m_flag); +#endif /* JOB_CONTROL */ } -/* Cause STREAM to buffer lines as opposed to characters or blocks. */ static void -line_buffer_stream (stream) - FILE *stream; +init_interactive_script () { - /* If your machine doesn't have either of setlinebuf or setvbuf, - you can just comment out the buffering commands, and the shell - will still work. It will take more cycles, though. */ -#if defined (HAVE_SETLINEBUF) - setlinebuf (stream); +#if defined (HISTORY) + if (enable_history_list == -1) + enable_history_list = 1; +#endif + init_noninteractive (); + expand_aliases = expaliases_flag = interactive_shell = startup_state = 1; +#if defined (HISTORY) + remember_on_history = enable_history_list; /* XXX */ +#endif +} + +void +get_current_user_info () +{ + struct passwd *entry; + + /* Don't fetch this more than once. */ + if (current_user.user_name == 0) + { +#if defined (__TANDEM) + entry = getpwnam (getlogin ()); #else -# if defined (_IOLBF) -# if defined (REVERSED_SETVBUF_ARGS) - setvbuf (stream, _IOLBF, (char *)NULL, BUFSIZ); -# else /* !REVERSED_SETVBUF_ARGS */ - setvbuf (stream, (char *)NULL, _IOLBF, BUFSIZ); -# endif /* !REVERSED_SETVBUF_ARGS */ -# endif /* _IOLBF */ -#endif /* !HAVE_SETLINEBUF */ + entry = getpwuid (current_user.uid); +#endif + if (entry) + { + current_user.user_name = savestring (entry->pw_name); + current_user.shell = (entry->pw_shell && entry->pw_shell[0]) + ? savestring (entry->pw_shell) + : savestring ("/bin/sh"); + current_user.home_dir = savestring (entry->pw_dir); + } + else + { + current_user.user_name = _("I have no name!"); + current_user.user_name = savestring (current_user.user_name); + current_user.shell = savestring ("/bin/sh"); + current_user.home_dir = savestring ("/"); + } +#if defined (HAVE_GETPWENT) + endpwent (); +#endif + } } /* Do whatever is necessary to initialize the shell. @@ -1277,9 +1927,15 @@ line_buffer_stream (stream) static void shell_initialize () { + char hostname[256]; + int should_be_restricted; + /* Line buffer output for stderr and stdout. */ - line_buffer_stream (stderr); - line_buffer_stream (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. */ @@ -1290,51 +1946,59 @@ shell_initialize () for restoring the original default signal handlers. That function is called when we make a new child. */ initialize_traps (); - initialize_signals (); - - /* Initialize current_user.name and current_host_name. */ - { - struct passwd *entry = getpwuid (current_user.uid); - char hostname[256]; - - if (gethostname (hostname, 255) < 0) - current_host_name = "??host??"; - else - current_host_name = savestring (hostname); + initialize_signals (0); - if (entry) - { - current_user.user_name = savestring (entry->pw_name); - if (entry->pw_shell && entry->pw_shell[0]) - current_user.shell = savestring (entry->pw_shell); - else - current_user.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 ("/"); - } + /* 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); + } - endpwent (); - } + /* 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. */ - initialize_shell_variables (shell_environment); +#if defined (RESTRICTED_SHELL) + should_be_restricted = shell_is_restricted (shell_name); +#endif - /* Initialize filename hash tables. */ - initialize_filename_hashing (); + /* Initialize internal and environment variables. Don't import shell + functions from the environment if we are running in privileged or + restricted mode or if the shell is running setuid. */ +#if defined (RESTRICTED_SHELL) + initialize_shell_variables (shell_environment, privileged_mode||restricted||should_be_restricted||running_setuid); +#else + initialize_shell_variables (shell_environment, privileged_mode||running_setuid); +#endif /* Initialize the data structures for storing and running jobs. */ - initialize_jobs (); + initialize_job_control (jobs_m_flag); /* Initialize input streams to null. */ initialize_bash_input (); + + initialize_flags (); + + /* Initialize the shell options. Don't import the shell options + from the environment variables $SHELLOPTS or $BASHOPTS if we are + running in privileged or restricted mode or if the shell is running + setuid. */ +#if defined (RESTRICTED_SHELL) + initialize_shell_options (privileged_mode||restricted||should_be_restricted||running_setuid); + initialize_bashopts (privileged_mode||restricted||should_be_restricted||running_setuid); +#else + initialize_shell_options (privileged_mode||running_setuid); + initialize_bashopts (privileged_mode||running_setuid); +#endif } /* Function called by main () when it appears that the shell has already @@ -1359,13 +2023,15 @@ shell_reinitialize () /* 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; + forced_interactive = interactive_shell = 0; + subshell_environment = running_in_background = 0; + expand_aliases = expaliases_flag = 0; + bash_argv_initialized = 0; + + /* XXX - should we set jobs_m_flag to 0 here? */ #if defined (HISTORY) -# if defined (BANG_HISTORY) - history_expansion = 0; -# endif - remember_on_history = 0; + bash_history_reinit (enable_history_list = 0); #endif /* HISTORY */ #if defined (RESTRICTED_SHELL) @@ -1374,419 +2040,97 @@ shell_reinitialize () /* Ensure that the default startup file is used. (Except that we don't execute this file for reinitialized shells). */ - bashrc_file = "~/.bashrc"; + bashrc_file = DEFAULT_BASHRC; /* Delete all variables and functions. They will be reinitialized when the environment is parsed. */ - - delete_all_variables (shell_variables); + delete_all_contexts (shell_variables); delete_all_variables (shell_functions); - /* Pretend the PATH variable has changed. */ - sv_path ("PATH"); -} - -static void -initialize_signals () -{ - initialize_terminating_signals (); - initialize_job_signals (); -#if defined (INITIALIZE_SIGLIST) - initialize_siglist (); -#endif -} + reinit_special_variables (); -void -reinitialize_signals () -{ - initialize_terminating_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 - -#ifdef SIGPROF - SIGPROF, NULL_HANDLER, -#endif - -#ifdef SIGLOST - SIGLOST, NULL_HANDLER, -#endif - -#ifdef SIGUSR1 - SIGUSR1, NULL_HANDLER, -#endif - -#ifdef SIGUSR2 - SIGUSR2, NULL_HANDLER, +#if defined (READLINE) + bashline_reinitialize (); #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) - -/* This function belongs here? */ -sighandler -termination_unwind_protect (sig) - int sig; -{ - if (sig == SIGINT && signal_is_trapped (SIGINT)) - run_interrupt_trap (); - -#if defined (HISTORY) - if (interactive_shell) - 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); - -#if !defined (VOID_SIGHANDLER) - return (0); -#endif /* VOID_SIGHANDLER */ + shell_reinitialized = 1; } -/* Initialize signals that will terminate the shell to do some - unwind protection. */ static void -initialize_terminating_signals () -{ - register int i; - - /* 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 (_POSIX_VERSION) - struct sigaction act, oact; - - 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); - terminating_signals[i].orig_handler = oact.sa_handler; - /* Don't do anything with signals that are ignored at shell entry - if the shell is not interactive. */ - if (!interactive_shell && oact.sa_handler == SIG_IGN) - { - sigaction (XSIG (i), &oact, &act); - set_signal_ignored (XSIG (i)); - } - } - -#else /* !_POSIX_VERSION */ - - for (i = 0; i < TERMSIGS_LENGTH; i++) - { - terminating_signals[i].orig_handler = - set_signal_handler (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 && terminating_signals[i].orig_handler == SIG_IGN) - { - set_signal_handler (XSIG (i), SIG_IGN); - set_signal_ignored (XSIG (i)); - } - } - -#endif /* !_POSIX_VERSION */ - -#if defined (JOB_CONTROL) || defined (_POSIX_VERSION) - /* 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); - sigdelset (&top_level_mask, SIGCHLD); -#endif /* JOB_CONTROL || _POSIX_VERSION */ - - /* 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 () +show_shell_usage (fp, extra) + FILE *fp; + int extra; { - register int i; - -#if defined (_POSIX_VERSION) - struct sigaction act; - - 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 - for (i = 0; i < TERMSIGS_LENGTH; i++) - { - if (signal_is_trapped (XSIG (i)) || signal_is_special (XSIG (i))) - continue; - - set_signal_handler (XSIG (i), XHANDLER (i)); - } -#endif -} -#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; + int i; + char *set_opts, *s, *t; + + if (extra) + fprintf (fp, _("GNU bash, version %s-(%s)\n"), shell_version_string (), MACHTYPE); + fprintf (fp, _("Usage:\t%s [GNU long option] [option] ...\n\t%s [GNU long option] [option] script-file ...\n"), + shell_name, shell_name); + fputs (_("GNU long options:\n"), fp); + for (i = 0; long_args[i].name; i++) + fprintf (fp, "\t--%s\n", long_args[i].name); + + fputs (_("Shell options:\n"), fp); + fputs (_("\t-ilrsD or -c command or -O shopt_option\t\t(invocation only)\n"), fp); + + for (i = 0, set_opts = 0; shell_builtins[i].name; i++) + if (STREQ (shell_builtins[i].name, "set")) + { + set_opts = savestring (shell_builtins[i].short_doc); + break; + } - if (interrupt_state) + if (set_opts) { - print_newline = 1; - interrupt_state--; + 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 (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); -#endif /* JOB_CONTROL */ - -#if defined (JOB_CONTROL) || defined (_POSIX_VERSION) - 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) + if (extra) { - fflush (stdout); - fprintf (stderr, "\n"); - fflush (stderr); + fprintf (fp, _("Type `%s -c \"help set\"' for more information about shell options.\n"), shell_name); + fprintf (fp, _("Type `%s -c help' for more information about shell builtin commands.\n"), shell_name); + fprintf (fp, _("Use the `bashbug' command to report bugs.\n")); + fprintf (fp, "\n"); + fprintf (fp, _("bash home page: \n")); + fprintf (fp, _("General help using GNU software: \n")); } - - /* An interrupted `wait' command in a script does not exit the script. */ - if (interactive || (interactive_shell && !shell_initialized) || - (print_newline && signal_is_trapped (SIGINT))) - longjmp (top_level, DISCARD); - else - longjmp (top_level, EXITPROG); } -/* When non-zero, we throw_to_top_level (). */ -int interrupt_immediately = 0; - -/* What we really do when SIGINT occurs. */ -sighandler -sigint_sighandler (sig) - int sig; +static void +add_shopt_to_alist (opt, on_or_off) + char *opt; + int on_or_off; { -#if defined (USG) && !defined (_POSIX_VERSION) - set_signal_handler (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) - interrupt_state++; - - if (interrupt_immediately) + if (shopt_ind >= shopt_len) { - interrupt_immediately = 0; - throw_to_top_level (); + shopt_len += 8; + shopt_alist = (STRING_INT_ALIST *)xrealloc (shopt_alist, shopt_len * sizeof (shopt_alist[0])); } -#if !defined (VOID_SIGHANDLER) - return (0); -#endif /* VOID_SIGHANDLER */ -} - -/* Give version information about this shell. */ -char * -shell_version_string () -{ - static char tt[16] = { '\0' }; - - if (!tt[0]) - sprintf (tt, "%s.%d(%d)", dist_version, patch_level, build_version); - return tt; + shopt_alist[shopt_ind].word = opt; + shopt_alist[shopt_ind].token = on_or_off; + shopt_ind++; } -void -show_shell_version () -{ - printf ("GNU %s, version %s\n", base_pathname (shell_name), - shell_version_string ()); -} - -#if !defined (USG) && defined (ENOTSOCK) -# if !defined (HAVE_SOCKETS) -# define HAVE_SOCKETS -# endif -#endif - -#if defined (HAVE_SOCKETS) -#include -#endif - -/* Is FD a socket or network connection? */ -static int -isnetconn (fd) - int fd; +static void +run_shopt_alist () { -#if defined (USGr4) || defined (USGr4_2) - /* Sockets on SVR4 and SVR4.2 are character special (streams) devices. */ - struct stat sb; - - if (fstat (fd, &sb) < 0) - return (0); - return (S_ISCHR (sb.st_mode)); -#else /* !USGr4 && !USGr4_2 */ -# if defined (HAVE_SOCKETS) - int rv, l; - struct sockaddr sa; - - l = sizeof(sa); - rv = getpeername(0, &sa, &l); - return ((rv < 0 && errno == ENOTSOCK) ? 0 : 1); -# else /* !HAVE_SOCKETS */ -# if defined (S_ISSOCK) - struct stat sb; + register int i; - if (fstat (fd, &sb) < 0) - return (0); - return (S_ISSOCK (sb.st_mode)); -# else /* !S_ISSOCK */ - return (0); -# endif /* !S_ISSOCK */ -# endif /* !HAVE_SOCKETS */ -#endif /* !USGr4 && !USGr4_2 */ + for (i = 0; i < shopt_ind; i++) + if (shopt_setopt (shopt_alist[i].word, (shopt_alist[i].token == '-')) != EXECUTION_SUCCESS) + exit (EX_BADUSAGE); + free (shopt_alist); + shopt_alist = 0; + shopt_ind = shopt_len = 0; }