diff options
| author | Jari Aalto <jari.aalto@cante.net> | 1996-08-26 18:22:31 +0000 |
|---|---|---|
| committer | Jari Aalto <jari.aalto@cante.net> | 2009-09-12 16:46:49 +0000 |
| commit | 726f63884db0132f01745f1fb4465e6621088ccf (patch) | |
| tree | 6c2f7765a890a97e0e513cb539df43283a8f7c4d /shell.c | |
| download | android_external_bash-726f63884db0132f01745f1fb4465e6621088ccf.tar.gz android_external_bash-726f63884db0132f01745f1fb4465e6621088ccf.tar.bz2 android_external_bash-726f63884db0132f01745f1fb4465e6621088ccf.zip | |
Imported from ../bash-1.14.7.tar.gz.
Diffstat (limited to 'shell.c')
| -rw-r--r-- | shell.c | 1792 |
1 files changed, 1792 insertions, 0 deletions
@@ -0,0 +1,1792 @@ +/* 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) 1988, 1991 Free Software Foundation, Inc. + + This file is part of GNU Bash. + + 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. + + 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. + + Birthdate: + Sunday, January 10th, 1988. + Initial author: Brian Fox +*/ +#define INSTALL_DEBUG_MODE + +#include "bashtypes.h" +#include <stdio.h> +#include <signal.h> +#include <errno.h> +#include <sys/file.h> +#include "filecntl.h" +#include <pwd.h> +#include "posixstat.h" +#include "bashansi.h" + +#if defined (HAVE_VARARGS_H) +#include <varargs.h> +#endif + +#include "shell.h" +#include "flags.h" + +#if defined (JOB_CONTROL) +#include "jobs.h" +#endif /* JOB_CONTROL */ + +#include "input.h" +#include "execute_cmd.h" + +#if defined (HISTORY) +# include "bashhist.h" +# include <readline/history.h> +#endif + +#include <tilde/tilde.h> + +#if defined (USG) && !defined (HAVE_GETPW_DECLS) +extern struct passwd *getpwuid (); +#endif /* USG && !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" +#endif + +char *the_current_maintainer = MAINTAINER; + +char *primary_prompt = PPROMPT; +char *secondary_prompt = SPROMPT; + +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 +}; + +/* 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; + +/* 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; + +/* 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; + +/* 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 = 0; + +/* 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. */ + +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 posixly_correct = 0; /* Non-zero means posix.2 superset. */ + +/* 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 }, + { "norc", Int, &no_rc, (char **)0x0 }, + { "noprofile", Int, &no_profile, (char **)0x0 }, + { "rcfile", Charp, (int *)0x0, &bashrc_file }, + { "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 }, + { (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. */ +jmp_buf 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 + +static int want_pending_command; +static char *local_pending_command; + +static int isnetconn (); +static void run_startup_files (); + +static void shell_initialize (); +static void shell_reinitialize (); +static void initialize_signals (); +static void initialize_terminating_signals (); + +main (argc, argv, env) + int argc; + char **argv, **env; +{ + register int i; + int arg_index, locally_skip_execution; + int top_level_arg_index, read_from_stdin; + FILE *default_input; + + /* 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; + + tty_fd = open ("/dev/tty", O_RDWR); + + if (tty_fd < 0) + { + char *tty; + tty = (char *)ttyname (fileno (stdin)); + tty_fd = open (tty, O_RDWR); + } + close (tty_fd); + } +#endif /* RLOGIN_PGRP_BUG */ + + /* Wait forever if we are debugging a login shell. */ + while (debugging_login_shell); + + current_user.uid = getuid (); + current_user.gid = getgid (); + current_user.euid = geteuid (); + current_user.egid = getegid (); + + /* See whether or not we are running setuid or setgid. */ + privileged_mode = (current_user.uid != current_user.euid) || + (current_user.gid != current_user.egid); + + posixly_correct = (getenv ("POSIXLY_CORRECT") != (char *)NULL) || + (getenv ("POSIX_PEDANTIC") != (char *)NULL); + +#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; + } + + /* 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; + 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); + } + + /* 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 */ + + 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"; + + /* Parse argument flags from the input line. */ + + /* Find full word arguments first. */ + while ((arg_index != argc) && *(argv[arg_index]) == '-') + { + 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++; + } + + /* 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) + { + login_shell++; + login_shell = -login_shell; + } + + /* 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; + } + + 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) + { + list_minus_o_opts (); + break; + } + if (set_minus_o_option (on_or_off, o_option) != EXECUTION_SUCCESS) + exit (1); + next_arg++; + break; + + default: + if (change_flag (arg_character, on_or_off) == FLAG_ERROR) + { + report_error ("%c%c: bad option", on_or_off, arg_character); + exit (1); + } + + } + } + /* Can't do just a simple increment anymore -- what about + "bash -abouo emacs ignoreeof -hP"? */ + arg_index = next_arg; + } + + /* 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) + { + report_error ("`-c' requires an argument"); + exit (1); + } + 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 ... */ + ((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; + } + 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 */ + } + +#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 */ + + /* From here on in, the shell must be a normal functioning shell. + Variables from the environment are expected to be set, etc. */ + shell_initialize (); + + if (interactive_shell) + { + char *term = getenv ("TERM"); + no_line_editing |= term && (STREQ (term, "emacs")); + } + + top_level_arg_index = arg_index; + + if (!quiet && do_version) + show_shell_version (); + + /* 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 + locally_skip_execution++; + } + } + + arg_index = top_level_arg_index; + + /* Execute the start-up scripts. */ + + if (!interactive_shell) + { + makunbound ("PS1", shell_variables); + makunbound ("PS2", shell_variables); + interactive = 0; + } + else + { + change_flag ('i', FLAG_ON); + interactive = 1; + } + + if (!locally_skip_execution) + run_startup_files (); + +#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. */ + 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 (ONESHOT) + run_one_command (local_pending_command); + goto exit_shell; +#else /* ONESHOT */ + with_input_from_string (local_pending_command, "-c"); + goto read_and_execute; +#endif /* !ONESHOT */ + } + + /* 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. */ + if (!shell_initialized) + load_history (); +#endif /* HISTORY */ + + /* Initialize terminal state for interactive shells after the + .bash_profile and .bashrc are interpreted. */ + get_tty_state (); + } + + /* Get possible input filename. */ + if ((arg_index != argc) && !read_from_stdin) + { + int fd; + char *filename; + + free (dollar_vars[0]); + dollar_vars[0] = savestring (argv[arg_index]); + filename = savestring (argv[arg_index]); + + 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); + } + } + + arg_index++; + if (fd < 0) + { + file_error (filename); + exit (1); + } + + /* Only do this with file descriptors we can seek on. */ + if (lseek (fd, 0L, 1) != -1) + { + 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))) + { + report_error ("%s: cannot execute binary file", filename); + exit (EX_BINARY_FILE); + } + /* 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) + { + file_error (filename); + exit (127); + } + SET_CLOSE_ON_EXEC (default_buffered_input); + +#else /* !BUFFERED_INPUT */ + + /* 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; + } + } + + default_input = fdopen (fd, "r"); + + if (!default_input) + { + file_error (filename); + exit (127); + } + + SET_CLOSE_ON_EXEC (fd); + if (fileno (default_input) != fd) + SET_CLOSE_ON_EXEC (fileno (default_input)); + +#endif /* !BUFFERED_INPUT */ + + 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 */ + + /* 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); + } + +#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 */ + + /* 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]); + } +#else /* !BUFFERED_INPUT */ + with_input_from_stream (default_input, dollar_vars[0]); +#endif /* !BUFFERED_INPUT */ + +#if !defined (ONESHOT) + read_and_execute: +#endif /* !ONESHOT */ + + shell_initialized = 1; + + /* Read commands until exit condition. */ + reader_loop (); + + exit_shell: + /* Do trap[0] if defined. */ + if (signal_is_trapped (0)) + last_command_exit_value = 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 this shell is interactive, terminate all stopped jobs and + restore the original terminal process group. */ + end_job_control (); +#endif /* JOB_CONTROL */ + + /* Always return the exit status of the last command to our parent. */ + exit (last_command_exit_value); +} + +#if !defined (SYS_PROFILE) +# define SYS_PROFILE "/etc/profile" +#endif /* !SYS_PROFILE */ + +/* 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) */ +static void +run_startup_files () +{ + if (!posixly_correct) + { + if (login_shell) + { + /* We don't execute .bashrc for login shells. */ + no_rc++; + if (no_profile == 0) + maybe_execute_file (SYS_PROFILE, 1); + } + + 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); + } + } + + /* 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); + } + + /* 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)) + { + char *env_file = (char *)NULL; + + if (!posixly_correct) + env_file = getenv ("BASH_ENV"); + if (!env_file) + env_file = getenv ("ENV"); + + if (env_file && *env_file) + { + WORD_LIST *list; + char *expanded_file_name; + + list = expand_string_unsplit (env_file, 1); + if (list) + { + expanded_file_name = string_list (list); + dispose_words (list); + + if (expanded_file_name && *expanded_file_name) + maybe_execute_file (expanded_file_name, 1); + + if (expanded_file_name) + free (expanded_file_name); + } + } + } +} + +#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. + Do this also if `restricted' is already set to 1; maybe the shell was + started with -r. */ +maybe_make_restricted (name) + char *name; +{ + char *temp; + + temp = base_pathname (shell_name); + if (restricted || (STREQ (temp, "rbash"))) + { + set_var_read_only ("PATH"); + non_unsettable ("PATH"); + set_var_read_only ("SHELL"); + non_unsettable ("SHELL"); + 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; +{ + 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); + + if (fd < 0) + { +file_error_and_exit: + if (errno != ENOENT) + file_error (filename); + free (filename); + return ((errno == ENOENT) ? 0 : -1); + } + + if (fstat (fd, &file_info) == -1) + goto file_error_and_exit; + + if (S_ISDIR (file_info.st_mode)) + { + internal_error ("%s: cannot execute directories", filename); + free (filename); + return -1; + } + + string = (char *)xmalloc (1 + (int)file_info.st_size); + tresult = read (fd, string, file_info.st_size); + + { + int tt = errno; + close (fd); + errno = tt; + } + + if (tresult != file_info.st_size) + { + free (string); + goto file_error_and_exit; + } + string[file_info.st_size] = '\0'; + + return_catch_flag++; + xbcopy ((char *)return_catch, (char *)old_return_catch, sizeof (jmp_buf)); + + if (force_noninteractive) + { + old_interactive = interactive; + interactive = 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); + + if (force_noninteractive) + interactive = old_interactive; + + return_catch_flag--; + xbcopy ((char *)old_return_catch, (char *)return_catch, sizeof (jmp_buf)); + + free (filename); + + return (1); +} + +#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. */ +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: + programming_error ("Bad jump %d", code); + } + } + return (parse_and_execute (savestring (command), "-c", -1)); +} +#endif /* ONESHOT */ + +reader_loop () +{ + int our_indirection_level; + COMMAND *current_command = (COMMAND *)NULL; + + our_indirection_level = ++indirection_level; + + while (!EOF_Reached) + { + 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; + 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); + } + } + + executing = 0; + dispose_used_env_vars (); + +#if (defined (Ultrix) && defined (mips)) || !defined (HAVE_ALLOCA) + /* Attempt to reclaim memory allocated with alloca (). */ + (void) alloca (0); +#endif + + if (read_command () == 0) + { + if (global_command) + { + current_command = global_command; + + current_command_number++; + + /* 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); + } + + 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) + EOF_Reached = EOF; + } + if (just_one_command) + EOF_Reached = EOF; + } + indirection_level--; +} + +/* 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); +} + +static sighandler +alrm_catcher(i) + int i; +{ + printf ("%ctimed out waiting for input: auto-logout\n", '\07'); + longjmp (top_level, EXITPROG); +#if !defined (VOID_SIGHANDLER) + return (0); +#endif /* !VOID_SIGHANDLER */ +} + +parse_command () +{ + int r; + + 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) + { + char *command_to_execute; + + command_to_execute = get_string_value ("PROMPT_COMMAND"); + if (command_to_execute) + execute_prompt_command (command_to_execute); + } + + current_command_line_count = 0; + r = yyparse (); + + if (need_here_doc) + gather_here_documents (); + + return (r); +} + +read_command () +{ + 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); + } + } + } + + 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); +} + +/* Cause STREAM to buffer lines as opposed to characters or blocks. */ +static void +line_buffer_stream (stream) + FILE *stream; +{ + /* 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); +#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 */ +} + +/* Do whatever is necessary to initialize the shell. + Put new initializations in here. */ +static void +shell_initialize () +{ + /* Line buffer output for stderr and stdout. */ + line_buffer_stream (stderr); + line_buffer_stream (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 (); + + /* 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); + + 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 ("/"); + } + + endpwent (); + } + + /* Initialize our interface to the tilde expander. */ + tilde_initialize (); + + /* Initialize internal and environment variables. */ + initialize_shell_variables (shell_environment); + + /* Initialize filename hash tables. */ + initialize_filename_hashing (); + + /* Initialize the data structures for storing and running jobs. */ + initialize_jobs (); + + /* Initialize input streams to null. */ + initialize_bash_input (); +} + +/* 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; + +#if defined (HISTORY) +# if defined (BANG_HISTORY) + history_expansion = 0; +# endif + remember_on_history = 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); + + /* 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 +} + +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, +#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 */ +} + +/* 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 () +{ + 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; + + if (interrupt_state) + { + print_newline = 1; + interrupt_state--; + } + + 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) + { + 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))) + 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; +{ +#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) + { + interrupt_immediately = 0; + throw_to_top_level (); + } +#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; +} + +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 <sys/socket.h> +#endif + +/* Is FD a socket or network connection? */ +static int +isnetconn (fd) + int fd; +{ +#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; + + 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 */ +} |
