/* shell.c -- GNU's idea of the POSIX shell specification. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. Birthdate: Sunday, January 10th, 1988. Initial author: Brian Fox */ #define INSTALL_DEBUG_MODE #include "config.h" #include "bashtypes.h" #ifndef _MINIX # include #endif #include "posixstat.h" #include "posixtime.h" #include "bashansi.h" #include #include #include #include "filecntl.h" #include #if defined (HAVE_UNISTD_H) # include #endif #define NEED_SH_SETLINEBUF_DECL /* used in externs.h */ #include "shell.h" #include "flags.h" #include "trap.h" #include "mailcheck.h" #include "builtins.h" #include "builtins/common.h" #if defined (JOB_CONTROL) #include "jobs.h" #endif /* JOB_CONTROL */ #include "input.h" #include "execute_cmd.h" #include "findcmd.h" #if defined (HISTORY) # include "bashhist.h" # include #endif #include #include #if defined (__OPENNT) # include #endif #if !defined (HAVE_GETPW_DECLS) extern struct passwd *getpwuid (); #endif /* !HAVE_GETPW_DECLS */ #if !defined (errno) extern int errno; #endif #if defined (NO_MAIN_ENV_ARG) extern char **environ; /* used if no third argument to main() */ #endif extern char *dist_version, *release_status; extern int patch_level, build_version; extern int shell_level; extern int subshell_environment; extern int last_command_exit_value; extern int line_number; extern char *primary_prompt, *secondary_prompt; extern int expand_aliases; extern char *this_command_name; extern int array_needs_making; /* Non-zero means that this shell has already been run; i.e. you should call shell_reinitialize () if you need to start afresh. */ int shell_initialized = 0; COMMAND *global_command = (COMMAND *)NULL; /* Information about the current user. */ struct user_info current_user = { (uid_t)-1, (uid_t)-1, (gid_t)-1, (gid_t)-1, (char *)NULL, (char *)NULL, (char *)NULL }; /* The current host's name. */ char *current_host_name = (char *)NULL; /* Non-zero means that this shell is a login shell. Specifically: 0 = not login shell. 1 = login shell from getty (or equivalent fake out) -1 = login shell from "--login" flag. -2 = both from getty, and from flag. */ int login_shell = 0; /* Non-zero means that at this moment, the shell is interactive. In general, this means that the shell is at this moment reading input from the keyboard. */ int interactive = 0; /* Non-zero means that the shell was started as an interactive shell. */ int interactive_shell = 0; /* Non-zero means to send a SIGHUP to all jobs when an interactive login shell exits. */ int hup_on_exit = 0; /* Tells what state the shell was in when it started: 0 = non-interactive shell script 1 = interactive 2 = -c command This is a superset of the information provided by interactive_shell. */ int startup_state = 0; /* Special debugging helper. */ int debugging_login_shell = 0; /* The environment that the shell passes to other commands. */ char **shell_environment; /* Non-zero when we are executing a top-level command. */ int executing = 0; /* The number of commands executed so far. */ int current_command_number = 1; /* Non-zero is the recursion depth for commands. */ int indirection_level = 0; /* The name of this shell, as taken from argv[0]. */ char *shell_name = (char *)NULL; /* time in seconds when the shell was started */ time_t shell_start_time; /* Are we running in an emacs shell window? */ int running_under_emacs; /* The name of the .(shell)rc file. */ static char *bashrc_file = "~/.bashrc"; /* Non-zero means to act more like the Bourne shell on startup. */ static int act_like_sh; /* Non-zero if this shell is being run by `su'. */ static int su_shell; /* Non-zero if we have already expanded and sourced $ENV. */ static int sourced_env; /* Is this shell running setuid? */ static int running_setuid; /* Values for the long-winded argument names. */ static int debugging; /* Do debugging things. */ static int no_rc; /* Don't execute ~/.bashrc */ static int no_profile; /* Don't execute .profile */ static int do_version; /* Display interesting version info. */ static int make_login_shell; /* Make this shell be a `-bash' shell. */ static int want_initial_help; /* --help option */ int no_line_editing = 0; /* Don't do fancy line editing. */ int posixly_correct = 0; /* Non-zero means posix.2 superset. */ int dump_translatable_strings; /* Dump strings in $"...", don't execute. */ int dump_po_strings; /* Dump strings in $"..." in po format */ int wordexp_only = 0; /* Do word expansion only */ /* Some long-winded argument names. These are obviously new. */ #define Int 1 #define Charp 2 struct { char *name; int type; int *int_value; char **char_value; } long_args[] = { { "debug", Int, &debugging, (char **)0x0 }, { "dump-po-strings", Int, &dump_po_strings, (char **)0x0 }, { "dump-strings", Int, &dump_translatable_strings, (char **)0x0 }, { "help", Int, &want_initial_help, (char **)0x0 }, { "init-file", Charp, (int *)0x0, &bashrc_file }, { "login", Int, &make_login_shell, (char **)0x0 }, { "noediting", Int, &no_line_editing, (char **)0x0 }, { "noprofile", Int, &no_profile, (char **)0x0 }, { "norc", Int, &no_rc, (char **)0x0 }, { "posix", Int, &posixly_correct, (char **)0x0 }, { "rcfile", Charp, (int *)0x0, &bashrc_file }, #if defined (RESTRICTED_SHELL) { "restricted", Int, &restricted, (char **)0x0 }, #endif { "verbose", Int, &echo_input_at_read, (char **)0x0 }, { "version", Int, &do_version, (char **)0x0 }, { "wordexp", Int, &wordexp_only, (char **)0x0 }, { (char *)0x0, Int, (int *)0x0, (char **)0x0 } }; /* These are extern so execute_simple_command can set them, and then longjmp back to main to execute a shell script, instead of calling main () again and resulting in indefinite, possibly fatal, stack growth. */ procenv_t subshell_top_level; int subshell_argc; char **subshell_argv; char **subshell_envp; #if defined (BUFFERED_INPUT) /* The file descriptor from which the shell is reading input. */ int default_buffered_input = -1; #endif /* The following two variables are not static so they can show up in $-. */ int read_from_stdin; /* -s flag supplied */ int want_pending_command; /* -c flag supplied */ static int shell_reinitialized = 0; static char *local_pending_command; static FILE *default_input; static STRING_INT_ALIST *shopt_alist; static int shopt_ind = 0, shopt_len = 0; static int parse_long_options __P((char **, int, int)); static int parse_shell_options __P((char **, int, int)); static int bind_args __P((char **, int, int, int)); static void add_shopt_to_alist __P((char *, int)); static void run_shopt_alist __P((void)); static void execute_env_file __P((char *)); static void run_startup_files __P((void)); static int open_shell_script __P((char *)); static void set_bash_input __P((void)); static int run_one_command __P((char *)); static int run_wordexp __P((char *)); static int uidget __P((void)); static int isnetconn __P((int)); static void init_interactive __P((void)); static void init_noninteractive __P((void)); static void set_shell_name __P((char *)); static void shell_initialize __P((void)); static void shell_reinitialize __P((void)); static void show_shell_usage __P((FILE *, int)); #ifdef __CYGWIN__ static void _cygwin32_check_tmp () { struct stat sb; if (stat ("/tmp", &sb) < 0) internal_warning ("could not find /tmp, please create!"); else { if (S_ISDIR (sb.st_mode) == 0) internal_warning ("/tmp must be a valid directory name"); } } #endif /* __CYGWIN__ */ #if defined (NO_MAIN_ENV_ARG) /* systems without third argument to main() */ int main (argc, argv) int argc; char **argv; #else /* !NO_MAIN_ENV_ARG */ int main (argc, argv, env) int argc; char **argv, **env; #endif /* !NO_MAIN_ENV_ARG */ { register int i; int code, old_errexit_flag; #if defined (RESTRICTED_SHELL) int saverst; #endif volatile int locally_skip_execution; volatile int arg_index, top_level_arg_index; #ifdef __OPENNT char **env; env = environ; #endif /* __OPENNT */ USE_VAR(argc); USE_VAR(argv); USE_VAR(env); USE_VAR(code); USE_VAR(old_errexit_flag); #if defined (RESTRICTED_SHELL) USE_VAR(saverst); #endif /* Catch early SIGINTs. */ code = setjmp (top_level); if (code) exit (2); #if defined (USING_BASH_MALLOC) && defined (DEBUG) # if 0 /* memory tracing */ malloc_set_trace(1); # endif # if 0 malloc_set_register (1); # endif #endif check_dev_tty (); #ifdef __CYGWIN__ _cygwin32_check_tmp (); #endif /* __CYGWIN__ */ /* Wait forever if we are debugging a login shell. */ while (debugging_login_shell); set_default_locale (); running_setuid = uidget (); if (getenv ("POSIXLY_CORRECT") || getenv ("POSIX_PEDANTIC")) posixly_correct = 1; #if defined (USE_GNU_MALLOC_LIBRARY) mcheck (programming_error, (void (*) ())0); #endif /* USE_GNU_MALLOC_LIBRARY */ if (setjmp (subshell_top_level)) { argc = subshell_argc; argv = subshell_argv; env = subshell_envp; sourced_env = 0; } shell_reinitialized = 0; /* Initialize `local' variables for all `invocations' of main (). */ arg_index = 1; local_pending_command = (char *)NULL; want_pending_command = locally_skip_execution = read_from_stdin = 0; default_input = stdin; #if defined (BUFFERED_INPUT) default_buffered_input = -1; #endif /* Fix for the `infinite process creation' bug when running shell scripts from startup files on System V. */ login_shell = make_login_shell = 0; /* If this shell has already been run, then reinitialize it to a vanilla state. */ if (shell_initialized || shell_name) { /* Make sure that we do not infinitely recurse as a login shell. */ if (*shell_name == '-') shell_name++; shell_reinitialize (); if (setjmp (top_level)) exit (2); } shell_environment = env; set_shell_name (argv[0]); shell_start_time = NOW; /* NOW now defined in general.h */ /* Parse argument flags from the input line. */ /* Find full word arguments first. */ arg_index = parse_long_options (argv, arg_index, argc); if (want_initial_help) { show_shell_usage (stdout, 1); exit (EXECUTION_SUCCESS); } if (do_version) { show_shell_version (1); exit (EXECUTION_SUCCESS); } /* If user supplied the "--login" flag, then set and invert LOGIN_SHELL. */ if (make_login_shell) { login_shell++; login_shell = -login_shell; } set_login_shell (login_shell != 0); /* All done with full word options; do standard shell option parsing.*/ this_command_name = shell_name; /* for error reporting */ arg_index = parse_shell_options (argv, arg_index, argc); if (dump_po_strings) dump_translatable_strings = 1; if (dump_translatable_strings) read_but_dont_execute = 1; if (running_setuid && privileged_mode == 0) disable_priv_mode (); /* Need to get the argument to a -c option processed in the above loop. The next arg is a command to execute, and the following args are $0...$n respectively. */ if (want_pending_command) { local_pending_command = argv[arg_index]; if (local_pending_command == 0) { report_error ("option `-c' requires an argument"); exit (EX_USAGE); } arg_index++; } this_command_name = (char *)NULL; /* First, let the outside world know about our interactive status. A shell is interactive if the `-i' flag was given, or if all of the following conditions are met: no -c command no arguments remaining or the -s flag given standard input is a terminal standard output is a terminal Refer to Posix.2, the description of the `sh' utility. */ if (forced_interactive || /* -i flag */ (!local_pending_command && /* No -c command and ... */ wordexp_only == 0 && /* No --wordexp and ... */ ((arg_index == argc) || /* no remaining args or... */ read_from_stdin) && /* -s flag with args, and */ isatty (fileno (stdin)) && /* Input is a terminal and */ isatty (fileno (stdout)))) /* output is a terminal. */ init_interactive (); else init_noninteractive (); #define CLOSE_FDS_AT_LOGIN #if defined (CLOSE_FDS_AT_LOGIN) /* * Some systems have the bad habit of starting login shells with lots of open * file descriptors. For instance, most systems that have picked up the * pre-4.0 Sun YP code leave a file descriptor open each time you call one * of the getpw* functions, and it's set to be open across execs. That * means one for login, one for xterm, one for shelltool, etc. */ if (login_shell && interactive_shell) { for (i = 3; i < 20; i++) close (i); } #endif /* CLOSE_FDS_AT_LOGIN */ /* If we're in a strict Posix.2 mode, turn on interactive comments and other Posix.2 things. */ if (posixly_correct) { bind_variable ("POSIXLY_CORRECT", "y"); sv_strict_posix ("POSIXLY_CORRECT"); } /* Now we run the shopt_alist and process the options. */ if (shopt_alist) run_shopt_alist (); /* From here on in, the shell must be a normal functioning shell. Variables from the environment are expected to be set, etc. */ shell_initialize (); set_default_locale_vars (); if (interactive_shell) { char *term; term = getenv ("TERM"); no_line_editing |= term && (STREQ (term, "emacs")); term = getenv ("EMACS"); running_under_emacs = term ? ((strmatch ("*term*", term, 0) == 0) ? 2 : 1) : 0; } top_level_arg_index = arg_index; old_errexit_flag = exit_immediately_on_error; /* Give this shell a place to longjmp to before executing the startup files. This allows users to press C-c to abort the lengthy startup. */ code = setjmp (top_level); if (code) { if (code == EXITPROG) exit_shell (last_command_exit_value); else { #if defined (JOB_CONTROL) /* Reset job control, since run_startup_files turned it off. */ set_job_control (interactive_shell); #endif /* Reset value of `set -e', since it's turned off before running the startup files. */ exit_immediately_on_error += old_errexit_flag; locally_skip_execution++; } } arg_index = top_level_arg_index; /* Execute the start-up scripts. */ if (interactive_shell == 0) { makunbound ("PS1", shell_variables); makunbound ("PS2", shell_variables); interactive = 0; expand_aliases = posixly_correct; } else { change_flag ('i', FLAG_ON); interactive = 1; } #if defined (RESTRICTED_SHELL) /* Set restricted_shell based on whether the basename of $0 indicates that the shell should be restricted or if the `-r' option was supplied at startup. */ restricted_shell = shell_is_restricted (shell_name); /* If the `-r' option is supplied at invocation, make sure that the shell is not in restricted mode when running the startup files. */ saverst = restricted; restricted = 0; #endif /* The startup files are run with `set -e' temporarily disabled. */ if (locally_skip_execution == 0 && running_setuid == 0) { old_errexit_flag = exit_immediately_on_error; exit_immediately_on_error = 0; run_startup_files (); exit_immediately_on_error += old_errexit_flag; } /* If we are invoked as `sh', turn on Posix mode. */ if (act_like_sh) { bind_variable ("POSIXLY_CORRECT", "y"); sv_strict_posix ("POSIXLY_CORRECT"); } #if defined (RESTRICTED_SHELL) /* Turn on the restrictions after executing the startup files. This means that `bash -r' or `set -r' invoked from a startup file will turn on the restrictions after the startup files are executed. */ restricted = saverst || restricted; if (shell_reinitialized == 0) maybe_make_restricted (shell_name); #endif /* RESTRICTED_SHELL */ if (wordexp_only) { startup_state = 3; last_command_exit_value = run_wordexp (argv[arg_index]); exit_shell (last_command_exit_value); } if (local_pending_command) { arg_index = bind_args (argv, arg_index, argc, 0); startup_state = 2; #if defined (ONESHOT) executing = 1; run_one_command (local_pending_command); exit_shell (last_command_exit_value); #else /* ONESHOT */ with_input_from_string (local_pending_command, "-c"); goto read_and_execute; #endif /* !ONESHOT */ } /* Get possible input filename and set up default_buffered_input or default_input as appropriate. */ if (arg_index != argc && read_from_stdin == 0) { open_shell_script (argv[arg_index]); arg_index++; } else if (interactive == 0) /* In this mode, bash is reading a script from stdin, which is a pipe or redirected file. */ #if defined (BUFFERED_INPUT) default_buffered_input = fileno (stdin); /* == 0 */ #else setbuf (default_input, (char *)NULL); #endif /* !BUFFERED_INPUT */ set_bash_input (); /* Bind remaining args to $1 ... $n */ arg_index = bind_args (argv, arg_index, argc, 1); /* Do the things that should be done only for interactive shells. */ if (interactive_shell) { /* Set up for checking for presence of mail. */ remember_mail_dates (); reset_mail_timer (); #if defined (HISTORY) /* Initialize the interactive history stuff. */ bash_initialize_history (); if (shell_initialized == 0) load_history (); #endif /* HISTORY */ /* Initialize terminal state for interactive shells after the .bash_profile and .bashrc are interpreted. */ get_tty_state (); } #if !defined (ONESHOT) read_and_execute: #endif /* !ONESHOT */ shell_initialized = 1; /* Read commands until exit condition. */ reader_loop (); exit_shell (last_command_exit_value); } static int parse_long_options (argv, arg_start, arg_end) char **argv; int arg_start, arg_end; { int arg_index, longarg, i; char *arg_string; arg_index = arg_start; while ((arg_index != arg_end) && (arg_string = argv[arg_index]) && (*arg_string == '-')) { longarg = 0; /* Make --login equivalent to -login. */ if (arg_string[1] == '-' && arg_string[2]) { longarg = 1; arg_string++; } for (i = 0; long_args[i].name; i++) { if (STREQ (arg_string + 1, long_args[i].name)) { if (long_args[i].type == Int) *long_args[i].int_value = 1; else if (argv[++arg_index] == 0) { report_error ("option `%s' requires an argument", long_args[i].name); exit (EX_USAGE); } else *long_args[i].char_value = argv[arg_index]; break; } } if (long_args[i].name == 0) { if (longarg) { report_error ("%s: unrecognized option", argv[arg_index]); show_shell_usage (stderr, 0); exit (EX_USAGE); } break; /* No such argument. Maybe flag arg. */ } arg_index++; } return (arg_index); } static int parse_shell_options (argv, arg_start, arg_end) char **argv; int arg_start, arg_end; { int arg_index; int arg_character, on_or_off, next_arg, i; char *o_option, *arg_string; arg_index = arg_start; while (arg_index != arg_end && (arg_string = argv[arg_index]) && (*arg_string == '-' || *arg_string == '+')) { /* There are flag arguments, so parse them. */ next_arg = arg_index + 1; /* A single `-' signals the end of options. From the 4.3 BSD sh. An option `--' means the same thing; this is the standard getopt(3) meaning. */ if (arg_string[0] == '-' && (arg_string[1] == '\0' || (arg_string[1] == '-' && arg_string[2] == '\0'))) return (next_arg); i = 1; on_or_off = arg_string[0]; while (arg_character = arg_string[i++]) { switch (arg_character) { case 'c': want_pending_command = 1; break; case 's': read_from_stdin = 1; break; case 'o': o_option = argv[next_arg]; if (o_option == 0) { list_minus_o_opts (-1, (on_or_off == '-') ? 0 : 1); break; } if (set_minus_o_option (on_or_off, o_option) != EXECUTION_SUCCESS) exit (EX_USAGE); next_arg++; break; case 'O': /* Since some of these can be overridden by the normal interactive/non-interactive shell initialization or initializing posix mode, we save the options and process them after initialization. */ o_option = argv[next_arg]; if (o_option == 0) { shopt_listopt (o_option, (on_or_off == '-') ? 0 : 1); break; } add_shopt_to_alist (o_option, on_or_off); next_arg++; break; case 'D': dump_translatable_strings = 1; break; default: if (change_flag (arg_character, on_or_off) == FLAG_ERROR) { report_error ("%c%c: unrecognized option", on_or_off, arg_character); show_shell_usage (stderr, 0); exit (EX_USAGE); } } } /* Can't do just a simple increment anymore -- what about "bash -abouo emacs ignoreeof -hP"? */ arg_index = next_arg; } return (arg_index); } /* Exit the shell with status S. */ void exit_shell (s) int s; { /* Do trap[0] if defined. Allow it to override the exit status passed to us. */ if (signal_is_trapped (0)) s = run_exit_trap (); #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ #if defined (HISTORY) if (interactive_shell) maybe_save_shell_history (); #endif /* HISTORY */ #if defined (JOB_CONTROL) /* If the user has run `shopt -s huponexit', hangup all jobs when we exit an interactive login shell. ksh does this unconditionally. */ if (interactive_shell && login_shell && hup_on_exit) hangup_all_jobs (); /* If this shell is interactive, terminate all stopped jobs and restore the original terminal process group. Don't do this if we're in a subshell and calling exit_shell after, for example, a failed word expansion. */ if (subshell_environment == 0) end_job_control (); #endif /* JOB_CONTROL */ /* Always return the exit status of the last command to our parent. */ exit (s); } #ifdef INCLUDE_UNUSED /* A wrapper for exit that (optionally) can do other things, like malloc statistics tracing. */ void sh_exit (s) int s; { exit (s); } #endif /* Source the bash startup files. If POSIXLY_CORRECT is non-zero, we obey the Posix.2 startup file rules: $ENV is expanded, and if the file it names exists, that file is sourced. The Posix.2 rules are in effect for interactive shells only. (section 4.56.5.3) */ /* Execute ~/.bashrc for most shells. Never execute it if ACT_LIKE_SH is set, or if NO_RC is set. If the executable file "/usr/gnu/src/bash/foo" contains: #!/usr/gnu/bin/bash echo hello then: COMMAND EXECUTE BASHRC -------------------------------- bash -c foo NO bash foo NO foo NO rsh machine ls YES (for rsh, which calls `bash -c') rsh machine foo YES (for shell started by rsh) NO (for foo!) echo ls | bash NO login NO bash YES */ static void execute_env_file (env_file) char *env_file; { char *fn; if (env_file && *env_file) { fn = expand_string_unsplit_to_string (env_file, Q_DOUBLE_QUOTES); if (fn && *fn) maybe_execute_file (fn, 1); FREE (fn); } } static void run_startup_files () { #if defined (JOB_CONTROL) int old_job_control; #endif int sourced_login, run_by_ssh; /* get the rshd/sshd case out of the way first. */ if (interactive_shell == 0 && no_rc == 0 && login_shell == 0 && act_like_sh == 0 && local_pending_command) { #ifdef SSH_SOURCE_BASHRC run_by_ssh = (find_variable ("SSH_CLIENT") != (SHELL_VAR *)0) || (find_variable ("SSH2_CLIENT") != (SHELL_VAR *)0); #else run_by_ssh = 0; #endif /* If we were run by sshd or we think we were run by rshd, execute ~/.bashrc if we are a top-level shell. */ if ((run_by_ssh || isnetconn (fileno (stdin))) && shell_level < 2) { #ifdef SYS_BASHRC # if defined (__OPENNT) maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); # else maybe_execute_file (SYS_BASHRC, 1); # endif #endif maybe_execute_file (bashrc_file, 1); return; } } #if defined (JOB_CONTROL) /* Startup files should be run without job control enabled. */ old_job_control = interactive_shell ? set_job_control (0) : 0; #endif sourced_login = 0; /* A shell begun with the --login flag that is not in posix mode runs the login shell startup files, no matter whether or not it is interactive. If NON_INTERACTIVE_LOGIN_SHELLS is defined, run the startup files if argv[0][0] == '-' as well. */ #if defined (NON_INTERACTIVE_LOGIN_SHELLS) if (login_shell && posixly_correct == 0) #else if (login_shell < 0 && posixly_correct == 0) #endif { /* We don't execute .bashrc for login shells. */ no_rc++; /* Execute /etc/profile and one of the personal login shell initialization files. */ if (no_profile == 0) { maybe_execute_file (SYS_PROFILE, 1); if (act_like_sh) /* sh */ maybe_execute_file ("~/.profile", 1); else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ maybe_execute_file ("~/.profile", 1); } sourced_login = 1; } /* A non-interactive shell not named `sh' and not in posix mode reads and executes commands from $BASH_ENV. If `su' starts a shell with `-c cmd' and `-su' as the name of the shell, we want to read the startup files. No other non-interactive shells read any startup files. */ if (interactive_shell == 0 && !(su_shell && login_shell)) { if (posixly_correct == 0 && act_like_sh == 0 && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("BASH_ENV")); return; } /* Interactive shell or `-su' shell. */ if (posixly_correct == 0) /* bash, sh */ { if (login_shell && sourced_login++ == 0) { /* We don't execute .bashrc for login shells. */ no_rc++; /* Execute /etc/profile and one of the personal login shell initialization files. */ if (no_profile == 0) { maybe_execute_file (SYS_PROFILE, 1); if (act_like_sh) /* sh */ maybe_execute_file ("~/.profile", 1); else if ((maybe_execute_file ("~/.bash_profile", 1) == 0) && (maybe_execute_file ("~/.bash_login", 1) == 0)) /* bash */ maybe_execute_file ("~/.profile", 1); } } /* bash */ if (act_like_sh == 0 && no_rc == 0) { #ifdef SYS_BASHRC # if defined (__OPENNT) maybe_execute_file (_prefixInstallPath(SYS_BASHRC, NULL, 0), 1); # else maybe_execute_file (SYS_BASHRC, 1); # endif #endif maybe_execute_file (bashrc_file, 1); } /* sh */ else if (act_like_sh && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("ENV")); } else /* bash --posix, sh --posix */ { /* bash and sh */ if (interactive_shell && privileged_mode == 0 && sourced_env++ == 0) execute_env_file (get_string_value ("ENV")); } #if defined (JOB_CONTROL) set_job_control (old_job_control); #endif } #if defined (RESTRICTED_SHELL) /* Return 1 if the shell should be a restricted one based on NAME or the value of `restricted'. Don't actually do anything, just return a boolean value. */ int shell_is_restricted (name) char *name; { char *temp; if (restricted) return 1; temp = base_pathname (name); return (STREQ (temp, RESTRICTED_SHELL_NAME)); } /* Perhaps make this shell a `restricted' one, based on NAME. If the basename of NAME is "rbash", then this shell is restricted. The name of the restricted shell is a configurable option, see config.h. In a restricted shell, PATH, SHELL, ENV, and BASH_ENV are read-only and non-unsettable. Do this also if `restricted' is already set to 1; maybe the shell was started with -r. */ int maybe_make_restricted (name) char *name; { char *temp; temp = base_pathname (shell_name); if (restricted || (STREQ (temp, RESTRICTED_SHELL_NAME))) { set_var_read_only ("PATH"); set_var_read_only ("SHELL"); set_var_read_only ("ENV"); set_var_read_only ("BASH_ENV"); restricted = 1; } return (restricted); } #endif /* RESTRICTED_SHELL */ /* Fetch the current set of uids and gids and return 1 if we're running setuid or setgid. */ static int uidget () { uid_t u; u = getuid (); if (current_user.uid != u) { FREE (current_user.user_name); FREE (current_user.shell); FREE (current_user.home_dir); current_user.user_name = current_user.shell = current_user.home_dir = (char *)NULL; } current_user.uid = u; current_user.gid = getgid (); current_user.euid = geteuid (); current_user.egid = getegid (); /* See whether or not we are running setuid or setgid. */ return (current_user.uid != current_user.euid) || (current_user.gid != current_user.egid); } void disable_priv_mode () { setuid (current_user.uid); setgid (current_user.gid); current_user.euid = current_user.uid; current_user.egid = current_user.gid; } static int run_wordexp (words) char *words; { int code, nw, nb; WORD_LIST *wl, *result; code = setjmp (top_level); if (code != NOT_JUMPED) { switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: return last_command_exit_value = 127; case EXITPROG: return last_command_exit_value; case DISCARD: return last_command_exit_value = 1; default: command_error ("run_wordexp", CMDERR_BADJUMP, code, 0); } } /* Run it through the parser to get a list of words and expand them */ if (words && *words) { with_input_from_string (words, "--wordexp"); if (parse_command () != 0) return (126); if (global_command == 0) { printf ("0\n0\n"); return (0); } if (global_command->type != cm_simple) return (126); wl = global_command->value.Simple->words; result = wl ? expand_words_no_vars (wl) : (WORD_LIST *)0; } else result = (WORD_LIST *)0; last_command_exit_value = 0; if (result == 0) { printf ("0\n0\n"); return (0); } /* Count up the number of words and bytes, and print them. Don't count the trailing NUL byte. */ for (nw = nb = 0, wl = result; wl; wl = wl->next) { nw++; nb += strlen (wl->word->word); } printf ("%u\n%u\n", nw, nb); /* Print each word on a separate line. This will have to be changed when the interface to glibc is completed. */ for (wl = result; wl; wl = wl->next) printf ("%s\n", wl->word->word); return (0); } #if defined (ONESHOT) /* Run one command, given as the argument to the -c option. Tell parse_and_execute not to fork for a simple command. */ static int run_one_command (command) char *command; { int code; code = setjmp (top_level); if (code != NOT_JUMPED) { #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: return last_command_exit_value = 127; case EXITPROG: return last_command_exit_value; case DISCARD: return last_command_exit_value = 1; default: command_error ("run_one_command", CMDERR_BADJUMP, code, 0); } } return (parse_and_execute (savestring (command), "-c", SEVAL_NOHIST)); } #endif /* ONESHOT */ static int bind_args (argv, arg_start, arg_end, start_index) char **argv; int arg_start, arg_end, start_index; { register int i; WORD_LIST *args; for (i = arg_start, args = (WORD_LIST *)NULL; i != arg_end; i++) args = make_word_list (make_word (argv[i]), args); if (args) { args = REVERSE_LIST (args, WORD_LIST *); if (start_index == 0) /* bind to $0...$n for sh -c command */ { /* Posix.2 4.56.3 says that the first argument after sh -c command becomes $0, and the rest of the arguments become $1...$n */ shell_name = savestring (args->word->word); FREE (dollar_vars[0]); dollar_vars[0] = savestring (args->word->word); remember_args (args->next, 1); } else /* bind to $1...$n for shell script */ remember_args (args, 1); dispose_words (args); } return (i); } void unbind_args () { remember_args ((WORD_LIST *)NULL, 1); } static int open_shell_script (script_name) char *script_name; { int fd, e, fd_is_tty; char *filename, *path_filename; char sample[80]; int sample_len; struct stat sb; free (dollar_vars[0]); dollar_vars[0] = savestring (script_name); filename = savestring (script_name); fd = open (filename, O_RDONLY); if ((fd < 0) && (errno == ENOENT) && (absolute_program (filename) == 0)) { e = errno; /* If it's not in the current directory, try looking through PATH for it. */ path_filename = find_path_file (script_name); if (path_filename) { free (filename); filename = path_filename; fd = open (filename, O_RDONLY); } else errno = e; } if (fd < 0) { e = errno; file_error (filename); exit ((e == ENOENT) ? EX_NOTFOUND : EX_NOINPUT); } #ifdef HAVE_DEV_FD fd_is_tty = isatty (fd); #else fd_is_tty = 0; #endif /* Only do this with non-tty file descriptors we can seek on. */ if (fd_is_tty == 0 && (lseek (fd, 0L, 1) != -1)) { /* Check to see if the `file' in `bash file' is a binary file according to the same tests done by execute_simple_command (), and report an error and exit if it is. */ sample_len = read (fd, sample, sizeof (sample)); if (sample_len < 0) { e = errno; if ((fstat (fd, &sb) == 0) && S_ISDIR (sb.st_mode)) internal_error ("%s: is a directory", filename); else { errno = e; file_error (filename); } exit (EX_NOEXEC); } else if (sample_len > 0 && (check_binary_file (sample, sample_len))) { internal_error ("%s: cannot execute binary file", filename); exit (EX_BINARY_FILE); } /* Now rewind the file back to the beginning. */ lseek (fd, 0L, 0); } /* Open the script. But try to move the file descriptor to a randomly large one, in the hopes that any descriptors used by the script will not match with ours. */ fd = move_to_high_fd (fd, 0, -1); #if defined (__CYGWIN__) && defined (O_TEXT) setmode (fd, O_TEXT); #endif #if defined (BUFFERED_INPUT) default_buffered_input = fd; SET_CLOSE_ON_EXEC (default_buffered_input); #else /* !BUFFERED_INPUT */ default_input = fdopen (fd, "r"); if (default_input == 0) { file_error (filename); exit (EX_NOTFOUND); } SET_CLOSE_ON_EXEC (fd); if (fileno (default_input) != fd) SET_CLOSE_ON_EXEC (fileno (default_input)); #endif /* !BUFFERED_INPUT */ /* Just about the only way for this code to be executed is if something like `bash -i /dev/stdin' is executed. */ if (interactive_shell && fd_is_tty) { dup2 (fd, 0); close (fd); fd = 0; #if defined (BUFFERED_INPUT) default_buffered_input = 0; #else fclose (default_input); default_input = stdin; #endif } else if (forced_interactive && fd_is_tty == 0) /* But if a script is called with something like `bash -i scriptname', we need to do a non-interactive setup here, since we didn't do it before. */ init_noninteractive (); free (filename); return (fd); } /* Initialize the input routines for the parser. */ static void set_bash_input () { /* Make sure the fd from which we are reading input is not in no-delay mode. */ #if defined (BUFFERED_INPUT) if (interactive == 0) sh_unset_nodelay_mode (default_buffered_input); else #endif /* !BUFFERED_INPUT */ sh_unset_nodelay_mode (fileno (stdin)); /* with_input_from_stdin really means `with_input_from_readline' */ if (interactive && no_line_editing == 0) with_input_from_stdin (); else #if defined (BUFFERED_INPUT) { if (interactive == 0) with_input_from_buffered_stream (default_buffered_input, dollar_vars[0]); else with_input_from_stream (default_input, dollar_vars[0]); } #else /* !BUFFERED_INPUT */ with_input_from_stream (default_input, dollar_vars[0]); #endif /* !BUFFERED_INPUT */ } /* Close the current shell script input source and forget about it. This is extern so execute_cmd.c:initialize_subshell() can call it. If CHECK_ZERO is non-zero, we close default_buffered_input even if it's the standard input (fd 0). */ void unset_bash_input (check_zero) int check_zero; { #if defined (BUFFERED_INPUT) if ((check_zero && default_buffered_input >= 0) || (check_zero == 0 && default_buffered_input > 0)) { close_buffered_fd (default_buffered_input); default_buffered_input = bash_input.location.buffered_fd = -1; } #else /* !BUFFERED_INPUT */ if (default_input) { fclose (default_input); default_input = (FILE *)NULL; } #endif /* !BUFFERED_INPUT */ } #if !defined (PROGRAM) # define PROGRAM "bash" #endif static void set_shell_name (argv0) char *argv0; { /* Here's a hack. If the name of this shell is "sh", then don't do any startup files; just try to be more like /bin/sh. */ shell_name = base_pathname (argv0); if (*shell_name == '-') shell_name++; if (shell_name[0] == 's' && shell_name[1] == 'h' && shell_name[2] == '\0') act_like_sh++; if (shell_name[0] == 's' && shell_name[1] == 'u' && shell_name[2] == '\0') su_shell++; shell_name = argv0; FREE (dollar_vars[0]); dollar_vars[0] = savestring (shell_name); if (*shell_name == '-') { shell_name++; login_shell++; } /* A program may start an interactive shell with "execl ("/bin/bash", "-", NULL)". If so, default the name of this shell to our name. */ if (!shell_name || !*shell_name || (shell_name[0] == '-' && !shell_name[1])) shell_name = PROGRAM; } static void init_interactive () { interactive_shell = startup_state = interactive = 1; expand_aliases = 1; } static void init_noninteractive () { #if defined (HISTORY) bash_history_reinit (0); #endif /* HISTORY */ interactive_shell = startup_state = interactive = 0; expand_aliases = 0; no_line_editing = 1; #if defined (JOB_CONTROL) set_job_control (0); #endif /* JOB_CONTROL */ } void get_current_user_info () { struct passwd *entry; /* Don't fetch this more than once. */ if (current_user.user_name == 0) { entry = getpwuid (current_user.uid); if (entry) { current_user.user_name = savestring (entry->pw_name); current_user.shell = (entry->pw_shell && entry->pw_shell[0]) ? savestring (entry->pw_shell) : savestring ("/bin/sh"); current_user.home_dir = savestring (entry->pw_dir); } else { current_user.user_name = savestring ("I have no name!"); current_user.shell = savestring ("/bin/sh"); current_user.home_dir = savestring ("/"); } endpwent (); } } /* Do whatever is necessary to initialize the shell. Put new initializations in here. */ static void shell_initialize () { char hostname[256]; /* Line buffer output for stderr and stdout. */ if (shell_initialized == 0) { sh_setlinebuf (stderr); sh_setlinebuf (stdout); } /* Sort the array of shell builtins so that the binary search in find_shell_builtin () works correctly. */ initialize_shell_builtins (); /* Initialize the trap signal handlers before installing our own signal handlers. traps.c:restore_original_signals () is responsible for restoring the original default signal handlers. That function is called when we make a new child. */ initialize_traps (); initialize_signals (); /* It's highly unlikely that this will change. */ if (current_host_name == 0) { /* Initialize current_host_name. */ if (gethostname (hostname, 255) < 0) current_host_name = "??host??"; else current_host_name = savestring (hostname); } /* Initialize the stuff in current_user that comes from the password file. We don't need to do this right away if the shell is not interactive. */ if (interactive_shell) get_current_user_info (); /* Initialize our interface to the tilde expander. */ tilde_initialize (); /* Initialize internal and environment variables. Don't import shell functions from the environment if we are running in privileged or restricted mode or if the shell is running setuid. */ #if defined (RESTRICTED_SHELL) initialize_shell_variables (shell_environment, privileged_mode||restricted||running_setuid); #else initialize_shell_variables (shell_environment, privileged_mode||running_setuid); #endif #if 0 /* Initialize filename hash tables. */ initialize_filename_hashing (); #endif /* Initialize the data structures for storing and running jobs. */ initialize_job_control (0); /* Initialize input streams to null. */ initialize_bash_input (); /* Initialize the shell options. Don't import the shell options from the environment variable $SHELLOPTS if we are running in privileged or restricted mode or if the shell is running setuid. */ #if defined (RESTRICTED_SHELL) initialize_shell_options (privileged_mode||restricted||running_setuid); #else initialize_shell_options (privileged_mode||running_setuid); #endif } /* Function called by main () when it appears that the shell has already had some initialization performed. This is supposed to reset the world back to a pristine state, as if we had been exec'ed. */ static void shell_reinitialize () { /* The default shell prompts. */ primary_prompt = PPROMPT; secondary_prompt = SPROMPT; /* Things that get 1. */ current_command_number = 1; /* We have decided that the ~/.bashrc file should not be executed for the invocation of each shell script. If the variable $ENV (or $BASH_ENV) is set, its value is used as the name of a file to source. */ no_rc = no_profile = 1; /* Things that get 0. */ login_shell = make_login_shell = interactive = executing = 0; debugging = do_version = line_number = last_command_exit_value = 0; forced_interactive = interactive_shell = subshell_environment = 0; expand_aliases = 0; #if defined (HISTORY) bash_history_reinit (0); #endif /* HISTORY */ #if defined (RESTRICTED_SHELL) restricted = 0; #endif /* RESTRICTED_SHELL */ /* Ensure that the default startup file is used. (Except that we don't execute this file for reinitialized shells). */ bashrc_file = "~/.bashrc"; /* Delete all variables and functions. They will be reinitialized when the environment is parsed. */ delete_all_variables (shell_variables); delete_all_variables (shell_functions); shell_reinitialized = 1; } static void show_shell_usage (fp, extra) FILE *fp; int extra; { int i; char *set_opts, *s, *t; if (extra) fprintf (fp, "GNU bash, version %s-(%s)\n", shell_version_string (), MACHTYPE); fprintf (fp, "Usage:\t%s [GNU long option] [option] ...\n\t%s [GNU long option] [option] script-file ...\n", shell_name, shell_name); fputs ("GNU long options:\n", fp); for (i = 0; long_args[i].name; i++) fprintf (fp, "\t--%s\n", long_args[i].name); fputs ("Shell options:\n", fp); fputs ("\t-irsD or -c command or -O shopt_option\t\t(invocation only)\n", fp); for (i = 0, set_opts = 0; shell_builtins[i].name; i++) if (STREQ (shell_builtins[i].name, "set")) set_opts = savestring (shell_builtins[i].short_doc); if (set_opts) { s = strchr (set_opts, '['); if (s == 0) s = set_opts; while (*++s == '-') ; t = strchr (s, ']'); if (t) *t = '\0'; fprintf (fp, "\t-%s or -o option\n", s); free (set_opts); } if (extra) { fprintf (fp, "Type `%s -c \"help set\"' for more information about shell options.\n", shell_name); fprintf (fp, "Type `%s -c help' for more information about shell builtin commands.\n", shell_name); fprintf (fp, "Use the `bashbug' command to report bugs.\n"); } } static void add_shopt_to_alist (opt, on_or_off) char *opt; int on_or_off; { if (shopt_ind >= shopt_len) { shopt_len += 8; shopt_alist = (STRING_INT_ALIST *)xrealloc (shopt_alist, shopt_len * sizeof (shopt_alist[0])); } shopt_alist[shopt_ind].word = opt; shopt_alist[shopt_ind].token = on_or_off; shopt_ind++; } static void run_shopt_alist () { register int i; for (i = 0; i < shopt_ind; i++) if (shopt_setopt (shopt_alist[i].word, (shopt_alist[i].token == '-')) != EXECUTION_SUCCESS) exit (EX_USAGE); free (shopt_alist); shopt_alist = 0; shopt_ind = shopt_len = 0; } /* The second and subsequent conditions must match those used to decide whether or not to call getpeername() in isnetconn(). */ #if defined (HAVE_SYS_SOCKET_H) && defined (HAVE_GETPEERNAME) && !defined (SVR4_2) # include #endif /* Is FD a socket or network connection? */ static int isnetconn (fd) int fd; { #if defined (HAVE_GETPEERNAME) && !defined (SVR4_2) && !defined (__BEOS__) int rv; socklen_t l; struct sockaddr sa; l = sizeof(sa); rv = getpeername(fd, &sa, &l); /* Solaris 2.5 getpeername() returns EINVAL if the fd is not a socket. */ return ((rv < 0 && (errno == ENOTSOCK || errno == EINVAL)) ? 0 : 1); #else /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ # if defined (SVR4) || defined (SVR4_2) /* Sockets on SVR4 and SVR4.2 are character special (streams) devices. */ struct stat sb; if (isatty (fd)) return (0); if (fstat (fd, &sb) < 0) return (0); # if defined (S_ISFIFO) if (S_ISFIFO (sb.st_mode)) return (0); # endif /* S_ISFIFO */ return (S_ISCHR (sb.st_mode)); # else /* !SVR4 && !SVR4_2 */ # if defined (S_ISSOCK) && !defined (__BEOS__) struct stat sb; if (fstat (fd, &sb) < 0) return (0); return (S_ISSOCK (sb.st_mode)); # else /* !S_ISSOCK || __BEOS__ */ return (0); # endif /* !S_ISSOCK || __BEOS__ */ # endif /* !SVR4 && !SVR4_2 */ #endif /* !HAVE_GETPEERNAME || SVR4_2 || __BEOS__ */ } /* alias.h -- structure definitions. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_ALIAS_H_) #define _ALIAS_H_ #include "stdc.h" #include "hashlib.h" typedef struct alias { char *name; char *value; char flags; } alias_t; /* Values for `flags' member of struct alias. */ #define AL_EXPANDNEXT 0x1 #define AL_BEINGEXPANDED 0x2 /* The list of known aliases. */ extern HASH_TABLE *aliases; extern void initialize_aliases __P((void)); /* Scan the list of aliases looking for one with NAME. Return NULL if the alias doesn't exist, else a pointer to the alias. */ extern alias_t *find_alias __P((char *)); /* Return the value of the alias for NAME, or NULL if there is none. */ extern char *get_alias_value __P((char *)); /* Make a new alias from NAME and VALUE. If NAME can be found, then replace its value. */ extern void add_alias __P((char *, char *)); /* Remove the alias with name NAME from the alias list. Returns the index of the removed alias, or -1 if the alias didn't exist. */ extern int remove_alias __P((char *)); /* Remove all aliases. */ extern void delete_all_aliases __P((void)); /* Return an array of all defined aliases. */ extern alias_t **all_aliases __P((void)); /* Expand a single word for aliases. */ extern char *alias_expand_word __P((char *)); /* Return a new line, with any aliases expanded. */ extern char *alias_expand __P((char *)); #endif /* _ALIAS_H_ */ /* array.h -- definitions for the interface exported by array.c that allows the rest of the shell to manipulate array variables. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifndef _ARRAY_H_ #define _ARRAY_H_ #include "stdc.h" typedef long arrayind_t; enum atype {array_indexed, array_assoc}; typedef struct array { enum atype type; arrayind_t max_index, num_elements, max_size; struct array_element *head; } ARRAY; typedef struct array_element { arrayind_t ind; char *value; struct array_element *next, *prev; } ARRAY_ELEMENT; typedef int sh_ae_map_func_t __P((ARRAY_ELEMENT *)); char *array_reference __P((ARRAY *, arrayind_t)); extern int array_add_element __P((ARRAY *, arrayind_t, char *)); extern ARRAY_ELEMENT *array_delete_element __P((ARRAY *, arrayind_t)); extern ARRAY_ELEMENT *new_array_element __P((arrayind_t, char *)); extern void destroy_array_element __P((ARRAY_ELEMENT *)); extern ARRAY *new_array __P((void)); extern void empty_array __P((ARRAY *)); extern void dispose_array __P((ARRAY *)); extern ARRAY *dup_array __P((ARRAY *)); extern ARRAY *dup_array_subrange __P((ARRAY *, ARRAY_ELEMENT *, ARRAY_ELEMENT *)); extern ARRAY_ELEMENT *copy_array_element __P((ARRAY_ELEMENT *)); extern WORD_LIST *array_to_word_list __P((ARRAY *)); extern ARRAY *word_list_to_array __P((WORD_LIST *)); extern ARRAY *assign_word_list __P((ARRAY *, WORD_LIST *)); extern char **array_to_argv __P((ARRAY *)); extern char *array_to_assignment_string __P((ARRAY *)); extern char *quoted_array_assignment_string __P((ARRAY *)); extern char *array_to_string __P((ARRAY *, char *, int)); extern ARRAY *string_to_array __P((char *, char *)); extern char *array_subrange __P((ARRAY *, arrayind_t, arrayind_t, int)); extern char *array_pat_subst __P((ARRAY *, char *, char *, int)); extern ARRAY *array_quote __P((ARRAY *)); #define array_num_elements(a) ((a)->num_elements) #define array_max_index(a) ((a)->max_index) #define array_head(a) ((a)->head) #define array_empty(a) ((a)->num_elements == 0) #define element_value(ae) ((ae)->value) #define element_index(ae) ((ae)->ind) #define element_forw(ae) ((ae)->next) #define element_back(ae) ((ae)->prev) #define ALL_ELEMENT_SUB(c) ((c) == '@' || (c) == '*') #endif /* _ARRAY_H_ */ /* arrayfunc.h -- declarations for miscellaneous array functions in arrayfunc.c */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_ARRAYFUNC_H_) #define _ARRAYFUNC_H_ /* Must include variables.h before including this file. */ #if defined (ARRAY_VARS) extern SHELL_VAR *convert_var_to_array __P((SHELL_VAR *)); extern SHELL_VAR *bind_array_variable __P((char *, arrayind_t, char *)); extern SHELL_VAR *assign_array_element __P((char *, char *)); extern SHELL_VAR *find_or_make_array_variable __P((char *, int)); extern SHELL_VAR *assign_array_from_string __P((char *, char *)); extern SHELL_VAR *assign_array_var_from_word_list __P((SHELL_VAR *, WORD_LIST *)); extern SHELL_VAR *assign_array_var_from_string __P((SHELL_VAR *, char *)); extern int unbind_array_element __P((SHELL_VAR *, char *)); extern int skipsubscript __P((const char *, int)); extern void print_array_assignment __P((SHELL_VAR *, int)); extern arrayind_t array_expand_index __P((char *, int)); extern int valid_array_reference __P((char *)); extern char *array_value __P((char *, int)); extern char *get_array_value __P((char *, int)); extern char *array_variable_name __P((char *, char **, int *)); extern SHELL_VAR *array_variable_part __P((char *, char **, int *)); #endif #endif /* !_ARRAYFUNC_H_ */ /* bashansi.h -- Typically included information required by picky compilers. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_BASHANSI_H_) #define _BASHANSI_H_ #if defined (HAVE_STRING_H) # if ! defined (STDC_HEADERS) && defined (HAVE_MEMORY_H) # include # endif # include #endif /* !HAVE_STRING_H */ #if defined (HAVE_STRINGS_H) # include #endif /* !HAVE_STRINGS_H */ #if defined (HAVE_STDLIB_H) # include #else # include "ansi_stdlib.h" #endif /* !HAVE_STDLIB_H */ #endif /* !_BASHANSI_H_ */ /* bashhist.h -- interface to the bash history functions in bashhist.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_BASHHIST_H_) #define _BASHHIST_H_ #include "stdc.h" extern int remember_on_history; extern int history_lines_this_session; extern int history_lines_in_file; extern int history_expansion; extern int history_control; extern int command_oriented_history; extern int hist_last_line_added; # if defined (BANG_HISTORY) extern int history_expansion_inhibited; # endif /* BANG_HISTORY */ extern void bash_initialize_history __P((void)); extern void bash_history_reinit __P((int)); extern void bash_history_disable __P((void)); extern void bash_history_enable __P((void)); extern void load_history __P((void)); extern void save_history __P((void)); extern int maybe_append_history __P((char *)); extern int maybe_save_shell_history __P((void)); extern char *pre_process_line __P((char *, int, int)); extern void maybe_add_history __P((char *)); extern void bash_add_history __P((char *)); extern int history_number __P((void)); extern void setup_history_ignore __P((char *)); extern char *last_history_line __P((void)); #endif /* _BASHHIST_H_ */ /* bashintl.h -- Internationalization stuff Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_BASHINTL_H_) #define _BASHINTL_H_ /* Include this *after* config.h */ #if defined (HAVE_LIBINTL_H) # include #endif #if defined (HAVE_LOCALE_H) # include #endif #if defined (HAVE_SETLOCALE) && !defined (LC_ALL) # undef HAVE_SETLOCALE #endif #if !defined (HAVE_SETLOCALE) # define setlocale(cat, loc) #endif #if !defined (HAVE_TEXTDOMAIN) # define textdomain(dom) #endif #if !defined (HAVE_BINDTEXTDOMAIN) # define bindtextdomain(dom, dir) #endif #endif /* !_BASHINTL_H_ */ /* bashjmp.h -- wrapper for setjmp.h with necessary bash definitions. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifndef _BASHJMP_H_ #define _BASHJMP_H_ #include "posixjmp.h" extern procenv_t top_level; extern procenv_t subshell_top_level; extern procenv_t return_catch; /* used by `return' builtin */ #define SHFUNC_RETURN() longjmp (return_catch, 1) #define COPY_PROCENV(old, save) \ xbcopy ((char *)old, (char *)save, sizeof (procenv_t)); /* Values for the second argument to longjmp/siglongjmp. */ #define NOT_JUMPED 0 /* Not returning from a longjmp. */ #define FORCE_EOF 1 /* We want to stop parsing. */ #define DISCARD 2 /* Discard current command. */ #define EXITPROG 3 /* Unconditionally exit the program now. */ #endif /* _BASHJMP_H_ */ /* bashline.h -- interface to the bash readline functions in bashline.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_BASHLINE_H_) #define _BASHLINE_H_ #include "stdc.h" extern int bash_readline_initialized; extern void posix_readline_initialize __P((int)); extern int enable_hostname_completion __P((int)); extern void initialize_readline __P((void)); extern void bashline_reinitialize __P((void)); extern int bash_re_edit __P((char *)); extern int bind_keyseq_to_unix_command __P((char *)); /* Used by programmable completion code. */ extern char *command_word_completion_function __P((const char *, int)); extern char *bash_groupname_completion_function __P((const char *, int)); extern char **get_hostname_list __P((void)); extern void clear_hostname_list __P((void)); extern char **bash_directory_completion_matches __P((const char *)); #endif /* _BASHLINE_H_ */ /* bashtypes.h -- with special handling for crays. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_BASHTYPES_H_) # define _BASHTYPES_H_ #if defined (CRAY) # define word __word #endif #include #if defined (CRAY) # undef word #endif #endif /* _BASHTYPES_H_ */ /* builtins.h -- What a builtin looks like, and where to find them. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "command.h" #include "general.h" #if defined (ALIAS) #include "alias.h" #endif /* Flags describing various things about a builtin. */ #define BUILTIN_ENABLED 0x1 /* This builtin is enabled. */ #define BUILTIN_DELETED 0x2 /* This has been deleted with enable -d. */ #define STATIC_BUILTIN 0x4 /* This builtin is not dynamically loaded. */ #define SPECIAL_BUILTIN 0x8 /* This is a Posix `special' builtin. */ #define ASSIGNMENT_BUILTIN 0x10 /* This builtin takes assignment statements. */ /* The thing that we build the array of builtins out of. */ struct builtin { char *name; /* The name that the user types. */ sh_builtin_func_t *function; /* The address of the invoked function. */ int flags; /* One of the #defines above. */ char **long_doc; /* NULL terminated array of strings. */ char *short_doc; /* Short version of documenation. */ char *handle; /* for future use */ }; /* Found in builtins.c, created by builtins/mkbuiltins. */ extern int num_shell_builtins; /* Number of shell builtins. */ extern struct builtin static_shell_builtins[]; extern struct builtin *shell_builtins; extern struct builtin *current_builtin; /* command.h -- The structures used internally to represent commands, and the extern declarations of the functions used to create them. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_COMMAND_H_) #define _COMMAND_H_ #include "stdc.h" /* Instructions describing what kind of thing to do for a redirection. */ enum r_instruction { r_output_direction, r_input_direction, r_inputa_direction, r_appending_to, r_reading_until, r_duplicating_input, r_duplicating_output, r_deblank_reading_until, r_close_this, r_err_and_out, r_input_output, r_output_force, r_duplicating_input_word, r_duplicating_output_word }; /* Redirection errors. */ #define AMBIGUOUS_REDIRECT -1 #define NOCLOBBER_REDIRECT -2 #define RESTRICTED_REDIRECT -3 /* can only happen in restricted shells. */ #define HEREDOC_REDIRECT -4 /* here-doc temp file can't be created */ #define CLOBBERING_REDIRECT(ri) \ (ri == r_output_direction || ri == r_err_and_out) #define OUTPUT_REDIRECT(ri) \ (ri == r_output_direction || ri == r_input_output || ri == r_err_and_out) #define INPUT_REDIRECT(ri) \ (ri == r_input_direction || ri == r_inputa_direction || ri == r_input_output) #define WRITE_REDIRECT(ri) \ (ri == r_output_direction || \ ri == r_input_output || \ ri == r_err_and_out || \ ri == r_appending_to || \ ri == r_output_force) /* Command Types: */ enum command_type { cm_for, cm_case, cm_while, cm_if, cm_simple, cm_select, cm_connection, cm_function_def, cm_until, cm_group, cm_arith, cm_cond, cm_arith_for, cm_subshell }; /* Possible values for the `flags' field of a WORD_DESC. */ #define W_HASDOLLAR 0x01 /* Dollar sign present. */ #define W_QUOTED 0x02 /* Some form of quote character is present. */ #define W_ASSIGNMENT 0x04 /* This word is a variable assignment. */ #define W_GLOBEXP 0x08 /* This word is the result of a glob expansion. */ #define W_NOSPLIT 0x10 /* Do not perform word splitting on this word. */ #define W_NOGLOB 0x20 /* Do not perform globbing on this word. */ #define W_NOSPLIT2 0x40 /* Don't split word except for $@ expansion. */ /* Possible values for subshell_environment */ #define SUBSHELL_ASYNC 0x01 /* subshell caused by `command &' */ #define SUBSHELL_PAREN 0x02 /* subshell caused by ( ... ) */ #define SUBSHELL_COMSUB 0x04 /* subshell caused by `command` or $(command) */ #define SUBSHELL_FORK 0x08 /* subshell caused by executing a disk command */ #define SUBSHELL_PIPE 0x10 /* subshell from a pipeline element */ /* A structure which represents a word. */ typedef struct word_desc { char *word; /* Zero terminated string. */ int flags; /* Flags associated with this word. */ } WORD_DESC; /* A linked list of words. */ typedef struct word_list { struct word_list *next; WORD_DESC *word; } WORD_LIST; /* **************************************************************** */ /* */ /* Shell Command Structs */ /* */ /* **************************************************************** */ /* What a redirection descriptor looks like. If the redirection instruction is ri_duplicating_input or ri_duplicating_output, use DEST, otherwise use the file in FILENAME. Out-of-range descriptors are identified by a negative DEST. */ typedef union { int dest; /* Place to redirect REDIRECTOR to, or ... */ WORD_DESC *filename; /* filename to redirect to. */ } REDIRECTEE; /* Structure describing a redirection. If REDIRECTOR is negative, the parser (or translator in redir.c) encountered an out-of-range file descriptor. */ typedef struct redirect { struct redirect *next; /* Next element, or NULL. */ int redirector; /* Descriptor to be redirected. */ int flags; /* Flag value for `open'. */ enum r_instruction instruction; /* What to do with the information. */ REDIRECTEE redirectee; /* File descriptor or filename */ char *here_doc_eof; /* The word that appeared in <flags. */ #define CMD_WANT_SUBSHELL 0x01 /* User wants a subshell: ( command ) */ #define CMD_FORCE_SUBSHELL 0x02 /* Shell needs to force a subshell. */ #define CMD_INVERT_RETURN 0x04 /* Invert the exit value. */ #define CMD_IGNORE_RETURN 0x08 /* Ignore the exit value. For set -e. */ #define CMD_NO_FUNCTIONS 0x10 /* Ignore functions during command lookup. */ #define CMD_INHIBIT_EXPANSION 0x20 /* Do not expand the command words. */ #define CMD_NO_FORK 0x40 /* Don't fork; just call execve */ #define CMD_TIME_PIPELINE 0x80 /* Time a pipeline */ #define CMD_TIME_POSIX 0x100 /* time -p; use POSIX.2 time output spec. */ #define CMD_AMPERSAND 0x200 /* command & */ #define CMD_STDIN_REDIR 0x400 /* async command needs implicit " /* System-wide .bashrc file for interactive shells. */ /* #define SYS_BASHRC "/etc/bash.bashrc" */ /* System-wide .bash_logout for login shells. */ /* #define SYS_BASH_LOGOUT "/etc/bash.bash_logout" */ /* Define this to make non-interactive shells begun with argv[0][0] == '-' run the startup files when not in posix mode. */ /* #define NON_INTERACTIVE_LOGIN_SHELLS */ /* Define this if you want bash to try to check whether it's being run by sshd and source the .bashrc if so (like the rshd behavior). */ /* #define SSH_SOURCE_BASHRC */ /* conftypes.h -- defines for build and host system. */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_CONFTYPES_H_) #define _CONFTYPES_H_ /* Placeholder for future modifications if cross-compiling or building a `fat' binary, e.g. on Apple Rhapsody. These values are used in multiple files, so they appear here. */ #if !defined (RHAPSODY) # define HOSTTYPE CONF_HOSTTYPE # define OSTYPE CONF_OSTYPE # define MACHTYPE CONF_MACHTYPE #else /* RHAPSODY */ # if defined(__powerpc__) || defined(__ppc__) # define HOSTTYPE "powerpc" # elif defined(__i386__) # define HOSTTYPE "i386" # else # define HOSTTYPE CONF_HOSTTYPE # endif # define OSTYPE CONF_OSTYPE # define VENDOR CONF_VENDOR # define MACHTYPE HOSTTYPE "-" VENDOR "-" OSTYPE #endif /* RHAPSODY */ #ifndef HOSTTYPE # define HOSTTYPE "unknown" #endif #ifndef OSTYPE # define OSTYPE "unknown" #endif #ifndef MACHTYPE # define MACHTYPE "unknown" #endif #endif /* _CONFTYPES_H_ */ /* dispose_cmd.h -- Functions appearing in dispose_cmd.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_DISPOSE_CMD_H_) #define _DISPOSE_CMD_H_ #include "stdc.h" extern void dispose_command __P((COMMAND *)); extern void dispose_word __P((WORD_DESC *)); extern void dispose_words __P((WORD_LIST *)); extern void dispose_word_array __P((char **)); extern void dispose_redirects __P((REDIRECT *)); #if defined (COND_COMMAND) extern void dispose_cond_node __P((COND_COM *)); #endif #endif /* !_DISPOSE_CMD_H_ */ /* error.h -- External declarations of functions appearing in error.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_ERROR_H_) #define _ERROR_H_ #include "stdc.h" /* Get the name of the shell or shell script for an error message. */ extern char *get_name_for_error __P((void)); /* Report an error having to do with FILENAME. */ extern void file_error __P((const char *)); /* Report a programmer's error, and abort. Pass REASON, and ARG1 ... ARG5. */ extern void programming_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* General error reporting. Pass FORMAT and ARG1 ... ARG5. */ extern void report_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Error messages for parts of the parser that don't call report_syntax_error */ extern void parser_error __P((int, const char *, ...)) __attribute__((__format__ (printf, 2, 3))); /* Report an unrecoverable error and exit. Pass FORMAT and ARG1 ... ARG5. */ extern void fatal_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Report a system error, like BSD warn(3). */ extern void sys_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Report an internal error. */ extern void internal_error __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Report an internal warning. */ extern void internal_warning __P((const char *, ...)) __attribute__((__format__ (printf, 1, 2))); /* Report an error having to do with command parsing or execution. */ extern void command_error __P((const char *, int, int, int)); extern char *command_errstr __P((int)); /* Debugging function, not enabled in released version. */ extern void itrace __P((const char *, ...)) __attribute__ ((__format__ (printf, 1, 2))); #endif /* !_ERROR_H_ */ /* execute_cmd.h - functions from execute_cmd.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_EXECUTE_CMD_H_) #define _EXECUTE_CMD_H_ #include "stdc.h" extern struct fd_bitmap *new_fd_bitmap __P((int)); extern void dispose_fd_bitmap __P((struct fd_bitmap *)); extern void close_fd_bitmap __P((struct fd_bitmap *)); extern int executing_line_number __P((void)); extern int execute_command __P((COMMAND *)); extern int execute_command_internal __P((COMMAND *, int, int, int, struct fd_bitmap *)); extern int shell_execve __P((char *, char **, char **)); extern void setup_async_signals __P((void)); extern void dispose_exec_redirects __P ((void)); extern int execute_shell_function __P((SHELL_VAR *, WORD_LIST *)); #if defined (PROCESS_SUBSTITUTION) extern void close_all_files __P((void)); #endif #endif /* _EXECUTE_CMD_H_ */ /* externs.h -- extern function declarations which do not appear in their own header file. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* Make sure that this is included *after* config.h! */ #if !defined (_EXTERNS_H_) # define _EXTERNS_H_ #include "stdc.h" /* Functions from expr.c. */ extern long evalexp __P((char *, int *)); /* Functions from print_cmd.c. */ extern char *make_command_string __P((COMMAND *)); extern void print_command __P((COMMAND *)); extern void print_simple_command __P((SIMPLE_COM *)); extern char *named_function_string __P((char *, COMMAND *, int)); extern void print_word_list __P((WORD_LIST *, char *)); extern void xtrace_print_word_list __P((WORD_LIST *)); #if defined (DPAREN_ARITHMETIC) extern void xtrace_print_arith_cmd __P((WORD_LIST *)); #endif #if defined (COND_COMMAND) extern void xtrace_print_cond_term __P((int, int, WORD_DESC *, char *, char *)); #endif /* Functions from shell.c. */ extern void exit_shell __P((int)) __attribute__((__noreturn__)); extern void disable_priv_mode __P((void)); extern void unbind_args __P((void)); #if defined (RESTRICTED_SHELL) extern int shell_is_restricted __P((char *)); extern int maybe_make_restricted __P((char *)); #endif extern void unset_bash_input __P((int)); extern void get_current_user_info __P((void)); /* Functions from eval.c. */ extern int reader_loop __P((void)); extern int parse_command __P((void)); extern int read_command __P((void)); /* Functions from braces.c. */ #if defined (BRACE_EXPANSION) extern char **brace_expand __P((char *)); #endif /* Miscellaneous functions from parse.y */ extern int yyparse __P((void)); extern int return_EOF __P((void)); extern void reset_parser __P((void)); extern WORD_LIST *parse_string_to_word_list __P((char *, const char *)); extern int get_current_prompt_level __P((void)); extern void set_current_prompt_level __P((int)); #if defined (HISTORY) extern char *history_delimiting_chars __P((void)); #endif /* Declarations for functions defined in locale.c */ extern void set_default_locale __P((void)); extern void set_default_locale_vars __P((void)); extern int set_locale_var __P((char *, char *)); extern int set_lang __P((char *, char *)); extern char *get_locale_var __P((char *)); extern char *localetrans __P((char *, int, int *)); /* Declarations for functions defined in list.c. */ extern void map_over_list __P((GENERIC_LIST *, sh_glist_func_t *)); extern void map_over_words __P((WORD_LIST *, sh_icpfunc_t *)); extern GENERIC_LIST *reverse_list (); extern int list_length (); extern GENERIC_LIST *list_append (); extern GENERIC_LIST *delete_element (); /* Declarations for functions defined in stringlib.c */ extern char **word_list_to_argv __P((WORD_LIST *, int, int, int *)); extern WORD_LIST *argv_to_word_list __P((char **, int, int)); extern int find_string_in_alist __P((char *, STRING_INT_ALIST *, int)); extern char *strsub __P((char *, char *, char *, int)); extern char *strcreplace __P((char *, int, char *, int)); extern void strip_leading __P((char *)); extern void strip_trailing __P((char *, int, int)); extern void xbcopy __P((char *, char *, int)); /* Functions from the bash library, lib/sh/libsh.a. These should really go into a separate include file. */ /* declarations for functions defined in lib/sh/clktck.c */ extern long get_clk_tck __P((void)); /* declarations for functions defined in lib/sh/clock.c */ extern void clock_t_to_secs (); extern void print_clock_t (); /* Declarations for functions defined in lib/sh/fmtulong.c */ #define FL_PREFIX 0x01 /* add 0x, 0X, or 0 prefix as appropriate */ #define FL_ADDBASE 0x02 /* add base# prefix to converted value */ #define FL_HEXUPPER 0x04 /* use uppercase when converting to hex */ #define FL_UNSIGNED 0x08 /* don't add any sign */ extern char *fmtulong __P((unsigned long int, int, char *, size_t, int)); /* Declarations for functions defined in lib/sh/fmtulong.c */ #if defined (HAVE_LONG_LONG) extern char *fmtullong __P((unsigned long long int, int, char *, size_t, int)); #endif /* Declarations for functions defined in lib/sh/getcwd.c */ #if !defined (HAVE_GETCWD) extern char *getcwd __P((char *, size_t)); #endif /* Declarations for functions defined in lib/sh/itos.c */ extern char *inttostr __P((long, char *, size_t)); extern char *itos __P((long)); extern char *uinttostr __P((unsigned long, char *, size_t)); extern char *uitos __P((unsigned long)); /* declarations for functions defined in lib/sh/makepath.c */ #define MP_DOTILDE 0x01 #define MP_DOCWD 0x02 #define MP_RMDOT 0x04 extern char *sh_makepath __P((const char *, const char *, int)); /* declarations for functions defined in lib/sh/netopen.c */ extern int netopen __P((char *)); /* Declarations for functions defined in lib/sh/oslib.c */ extern int dup2 __P((int, int)); #if !defined (HAVE_GETDTABLESIZE) extern int getdtablesize __P((void)); #endif /* !HAVE_GETDTABLESIZE */ #if !defined (HAVE_GETHOSTNAME) extern int gethostname __P((char *, int)); #endif /* !HAVE_GETHOSTNAME */ /* declarations for functions defined in lib/sh/pathcanon.c */ #define PATH_CHECKDOTDOT 0x0001 #define PATH_CHECKEXISTS 0x0002 #define PATH_HARDPATH 0x0004 #define PATH_NOALLOC 0x0008 extern char *sh_canonpath __P((char *, int)); /* declarations for functions defined in lib/sh/pathphys.c */ extern char *sh_physpath __P((char *, int)); extern char *sh_realpath __P((const char *, char *)); /* declarations for functions defined in lib/sh/setlinebuf.c */ #ifdef NEED_SH_SETLINEBUF_DECL extern int sh_setlinebuf __P((FILE *)); #endif /* declarations for functions defined in lib/sh/shquote.c */ extern char *sh_single_quote __P((char *)); extern char *sh_double_quote __P((char *)); extern char *sh_un_double_quote __P((char *)); extern char *sh_backslash_quote __P((char *)); extern char *sh_backslash_quote_for_double_quotes __P((char *)); extern int sh_contains_shell_metas __P((char *)); /* declarations for functions defined in lib/sh/spell.c */ extern int spname __P((char *, char *)); /* declarations for functions defined in lib/sh/strcasecmp.c */ #if !defined (HAVE_STRCASECMP) extern int strncasecmp __P((const char *, const char *, int)); extern int strcasecmp __P((const char *, const char *)); #endif /* HAVE_STRCASECMP */ /* declarations for functions defined in lib/sh/strerror.c */ #if !defined (strerror) extern char *strerror __P((int)); #endif /* declarations for functions defined in lib/sh/strindex.c */ extern char *strindex __P((const char *, const char *)); /* declarations for functions and structures defined in lib/sh/stringlist.c */ /* This is a general-purpose argv-style array struct. */ typedef struct _list_of_strings { char **list; int list_size; int list_len; } STRINGLIST; extern STRINGLIST *alloc_stringlist __P((int)); extern STRINGLIST *realloc_stringlist __P((STRINGLIST *, int)); extern void free_stringlist __P((STRINGLIST *)); extern STRINGLIST *copy_stringlist __P((STRINGLIST *)); extern STRINGLIST *merge_stringlists __P((STRINGLIST *, STRINGLIST *)); extern STRINGLIST *append_stringlist __P((STRINGLIST *, STRINGLIST *)); extern STRINGLIST *prefix_suffix_stringlist __P((STRINGLIST *, char *, char *)); extern void print_stringlist __P((STRINGLIST *, char *)); extern void sort_stringlist __P((STRINGLIST *)); /* declarations for functions defined in lib/sh/stringvec.c */ extern int find_name_in_array __P((char *, char **)); extern char **alloc_array __P((int)); extern int array_len __P((char **)); extern void free_array_members __P((char **)); extern void free_array __P((char **)); extern char **copy_array __P((char **)); extern int qsort_string_compare __P((char **, char **)); extern void sort_char_array __P((char **)); /* declarations for functions defined in lib/sh/strtod.c */ #if !defined (HAVE_STRTOD) extern double strtod __P((const char *, char **)); #endif /* declarations for functions defined in lib/sh/strtol.c */ #if !HAVE_DECL_STRTOL extern long strtol __P((const char *, char **, int)); #endif /* declarations for functions defined in lib/sh/strtoll.c */ #if defined (HAVE_LONG_LONG) && !HAVE_DECL_STRTOLL extern long long strtoll __P((const char *, char **, int)); #endif /* declarations for functions defined in lib/sh/strtoul.c */ #if !HAVE_DECL_STRTOUL extern unsigned long strtoul __P((const char *, char **, int)); #endif /* declarations for functions defined in lib/sh/strtoull.c */ #if defined (HAVE_LONG_LONG) && !HAVE_DECL_STRTOULL extern unsigned long long strtoull __P((const char *, char **, int)); #endif /* declarations for functions defined in lib/sh/strimax.c */ #ifdef NEED_STRTOIMAX_DECL #if !HAVE_DECL_STRTOIMAX extern intmax_t strtoimax __P((const char *, char **, int)); #endif /* declarations for functions defined in lib/sh/strumax.c */ #if !HAVE_DECL_STRTOUMAX extern uintmax_t strtoumax __P((const char *, char **, int)); #endif #endif /* NEED_STRTOIMAX_DECL */ /* declarations for functions defined in lib/sh/strtrans.c */ extern char *ansicstr __P((char *, int, int, int *, int *)); extern char *ansic_quote __P((char *, int, int *)); extern int ansic_shouldquote __P((const char *)); /* declarations for functions defined in lib/sh/timeval.c. No prototypes so we don't have to count on having a definition of struct timeval in scope when this file is included. */ extern void timeval_to_secs (); extern void print_timeval (); /* declarations for functions defined in lib/sh/tmpfile.c */ #define MT_USETMPDIR 0x0001 #define MT_READWRITE 0x0002 #define MT_USERANDOM 0x0004 extern char *sh_mktmpname __P((char *, int)); extern int sh_mktmpfd __P((char *, int, char **)); /* extern FILE *sh_mktmpfp __P((char *, int, char **)); */ /* declarations for functions defined in lib/sh/zread.c */ extern ssize_t zread __P((int, char *, size_t)); extern ssize_t zread1 __P((int, char *, size_t)); extern ssize_t zreadc __P((int, char *)); extern void zreset __P((void)); extern void zsyncfd __P((int)); /* declarations for functions defined in lib/sh/zwrite.c */ extern int zwrite __P((int, char *, size_t)); #endif /* _EXTERNS_H_ */ /* findcmd.h - functions from findcmd.c. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_FINDCMD_H_) #define _FINDCMD_H_ #include "stdc.h" extern int file_status __P((const char *)); extern int executable_file __P((const char *)); extern int is_directory __P((const char *)); extern int executable_or_directory __P((const char *)); extern char *find_user_command __P((const char *)); extern char *find_path_file __P((const char *)); extern char *search_for_command __P((const char *)); extern char *user_command_matches __P((const char *, int, int)); #endif /* _FINDCMD_H_ */ /* flags.h -- a list of all the flags that the shell knows about. You add a flag to this program by adding the name here, and in flags.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_FLAGS_H_) #define _FLAGS_H_ #include "stdc.h" /* Welcome to the world of Un*x, where everything is slightly backwards. */ #define FLAG_ON '-' #define FLAG_OFF '+' #define FLAG_ERROR -1 #define FLAG_UNKNOWN (int *)0 /* The thing that we build the array of flags out of. */ struct flags_alist { char name; int *value; }; extern struct flags_alist shell_flags[]; extern int mark_modified_vars, exit_immediately_on_error, disallow_filename_globbing, place_keywords_in_env, read_but_dont_execute, just_one_command, unbound_vars_is_error, echo_input_at_read, echo_command_at_execute, no_invisible_vars, noclobber, hashing_enabled, forced_interactive, privileged_mode, asynchronous_notification, interactive_comments, no_symbolic_links; #if 0 extern int lexical_scoping; #endif #if defined (BRACE_EXPANSION) extern int brace_expansion; #endif #if defined (BANG_HISTORY) extern int history_expansion; #endif /* BANG_HISTORY */ #if defined (RESTRICTED_SHELL) extern int restricted; extern int restricted_shell; #endif /* RESTRICTED_SHELL */ extern int *find_flag __P((int)); extern int change_flag __P((int, int)); extern char *which_set_flags __P((void)); extern void reset_shell_flags __P((void)); /* A macro for efficiency. */ #define change_flag_char(flag, on_or_off) change_flag (flag, on_or_off) #endif /* _FLAGS_H_ */ /* general.h -- defines that everybody likes to use. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_GENERAL_H_) #define _GENERAL_H_ #include "stdc.h" #include "bashtypes.h" #if defined (HAVE_SYS_RESOURCE_H) && defined (RLIMTYPE) # if defined (HAVE_SYS_TIME_H) # include # endif # include #endif #if defined (HAVE_STRING_H) # include #else # include #endif /* !HAVE_STRING_H */ #if defined (HAVE_LIMITS_H) # include #endif #include "xmalloc.h" /* NULL pointer type. */ #if !defined (NULL) # if defined (__STDC__) # define NULL ((void *) 0) # else # define NULL 0x0 # endif /* !__STDC__ */ #endif /* !NULL */ #define pointer_to_int(x) (int)((long)(x)) #if defined (alpha) && defined (__GNUC__) && !defined (strchr) && !defined (__STDC__) extern char *strchr (), *strrchr (); #endif #if !defined (strcpy) extern char *strcpy __P((char *, const char *)); #endif #if !defined (savestring) # define savestring(x) (char *)strcpy (xmalloc (1 + strlen (x)), (x)) #endif #ifndef member # define member(c, s) ((c) ? ((char *)strchr ((s), (c)) != (char *)NULL) : 0) #endif #ifndef whitespace #define whitespace(c) (((c) == ' ') || ((c) == '\t')) #endif #ifndef CHAR_MAX # ifdef __CHAR_UNSIGNED__ # define CHAR_MAX 0xff # else # define CHAR_MAX 0x7f # endif #endif #ifndef CHAR_BIT # define CHAR_BIT 8 #endif /* Nonzero if the integer type T is signed. */ #define TYPE_SIGNED(t) (! ((t) 0 < (t) -1)) /* Bound on length of the string representing an integer value of type T. Subtract one for the sign bit if T is signed; 302 / 1000 is log10 (2) rounded up; add one for integer division truncation; add one more for a minus sign if t is signed. */ #define INT_STRLEN_BOUND(t) \ ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 \ + 1 + TYPE_SIGNED (t)) /* Define exactly what a legal shell identifier consists of. */ #define legal_variable_starter(c) (ISALPHA(c) || (c == '_')) #define legal_variable_char(c) (ISALNUM(c) || c == '_') /* Definitions used in subst.c and by the `read' builtin for field splitting. */ #define spctabnl(c) ((c) == ' ' || (c) == '\t' || (c) == '\n') /* All structs which contain a `next' field should have that field as the first field in the struct. This means that functions can be written to handle the general case for linked lists. */ typedef struct g_list { struct g_list *next; } GENERIC_LIST; /* Here is a generic structure for associating character strings with integers. It is used in the parser for shell tokenization. */ typedef struct { char *word; int token; } STRING_INT_ALIST; /* A macro to avoid making an uneccessary function call. */ #define REVERSE_LIST(list, type) \ ((list && list->next) ? (type)reverse_list ((GENERIC_LIST *)list) \ : (type)(list)) #if __GNUC__ > 1 # define FASTCOPY(s, d, n) __builtin_memcpy (d, s, n) #else /* !__GNUC__ */ # if !defined (HAVE_BCOPY) # if !defined (HAVE_MEMMOVE) # define FASTCOPY(s, d, n) memcpy (d, s, n) # else # define FASTCOPY(s, d, n) memmove (d, s, n) # endif /* !HAVE_MEMMOVE */ # else /* HAVE_BCOPY */ # define FASTCOPY(s, d, n) bcopy (s, d, n) # endif /* HAVE_BCOPY */ #endif /* !__GNUC__ */ /* String comparisons that possibly save a function call each. */ #define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0) #define STREQN(a, b, n) ((n == 0) ? (1) \ : ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)) /* More convenience definitions that possibly save system or libc calls. */ #define STRLEN(s) (((s) && (s)[0]) ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0) #define FREE(s) do { if (s) free (s); } while (0) #define MEMBER(c, s) (((c) && c == (s)[0] && !(s)[1]) || (member(c, s))) /* A fairly hairy macro to check whether an allocated string has more room, and to resize it using xrealloc if it does not. STR is the string (char *) CIND is the current index into the string (int) ROOM is the amount of additional room we need in the string (int) CSIZE is the currently-allocated size of STR (int) SINCR is how much to increment CSIZE before calling xrealloc (int) */ #define RESIZE_MALLOCED_BUFFER(str, cind, room, csize, sincr) \ do { \ if ((cind) + (room) >= csize) \ { \ while ((cind) + (room) >= csize) \ csize += (sincr); \ str = xrealloc (str, csize); \ } \ } while (0) /* Function pointers can be declared as (Function *)foo. */ #if !defined (_FUNCTION_DEF) # define _FUNCTION_DEF typedef int Function (); typedef void VFunction (); typedef char *CPFunction (); /* no longer used */ typedef char **CPPFunction (); /* no longer used */ #endif /* _FUNCTION_DEF */ #ifndef SH_FUNCTION_TYPEDEF # define SH_FUNCTION_TYPEDEF /* Shell function typedefs with prototypes */ /* `Generic' function pointer typedefs */ typedef int sh_intfunc_t __P((int)); typedef int sh_ivoidfunc_t __P((void)); typedef int sh_icpfunc_t __P((char *)); typedef int sh_icppfunc_t __P((char **)); typedef int sh_iptrfunc_t __P((PTR_T)); typedef void sh_voidfunc_t __P((void)); typedef void sh_vintfunc_t __P((int)); typedef void sh_vcpfunc_t __P((char *)); typedef void sh_vcppfunc_t __P((char **)); typedef void sh_vptrfunc_t __P((PTR_T)); typedef int sh_wdesc_func_t __P((WORD_DESC *)); typedef int sh_wlist_func_t __P((WORD_LIST *)); typedef int sh_glist_func_t __P((GENERIC_LIST *)); typedef char *sh_string_func_t __P((char *)); /* like savestring, et al. */ typedef int sh_msg_func_t __P((const char *, ...)); /* printf(3)-like */ typedef void sh_vmsg_func_t __P((const char *, ...)); /* printf(3)-like */ /* Specific function pointer typedefs. Most of these could be done with #defines. */ typedef void sh_sv_func_t __P((char *)); /* sh_vcpfunc_t */ typedef void sh_free_func_t __P((PTR_T)); /* sh_vptrfunc_t */ typedef void sh_resetsig_func_t __P((int)); /* sh_vintfunc_t */ typedef int sh_ignore_func_t __P((const char *)); /* sh_icpfunc_t */ typedef int sh_assign_func_t __P((const char *)); /* sh_icpfunc_t */ typedef int sh_builtin_func_t __P((WORD_LIST *)); /* sh_wlist_func_t */ #endif /* SH_FUNCTION_TYPEDEF */ #define NOW ((time_t) time ((time_t *) 0)) /* Some defines for calling file status functions. */ #define FS_EXISTS 0x1 #define FS_EXECABLE 0x2 #define FS_EXEC_PREFERRED 0x4 #define FS_EXEC_ONLY 0x8 #define FS_DIRECTORY 0x10 #define FS_NODIRS 0x20 /* The type of function passed as the fourth argument to qsort(3). */ #ifdef __STDC__ typedef int QSFUNC (const void *, const void *); #else typedef int QSFUNC (); #endif /* Some useful definitions for Unix pathnames. Argument convention: x == string, c == character */ #if !defined (__CYGWIN__) # define ABSPATH(x) ((x)[0] == '/') # define RELPATH(x) ((x)[0] != '/') #else /* __CYGWIN__ */ # define ABSPATH(x) (((x)[0] && ISALPHA((unsigned char)(x)[0]) && (x)[1] == ':' && (x)[2] == '/') || (x)[0] == '/') # define RELPATH(x) (!(x)[0] || ((x)[1] != ':' && (x)[0] != '/')) #endif /* __CYGWIN__ */ #define ROOTEDPATH(x) (ABSPATH(x)) #define DIRSEP '/' #define ISDIRSEP(c) ((c) == '/') #define PATHSEP(c) (ISDIRSEP(c) || (c) == 0) #if 0 /* Declarations for functions defined in xmalloc.c */ extern PTR_T xmalloc __P((size_t)); extern PTR_T xrealloc __P((void *, size_t)); extern void xfree __P((void *)); #endif /* Declarations for functions defined in general.c */ extern void posix_initialize __P((int)); #if defined (RLIMTYPE) extern RLIMTYPE string_to_rlimtype __P((char *)); extern void print_rlimtype __P((RLIMTYPE, int)); #endif extern int all_digits __P((char *)); extern int legal_number __P((char *, long *)); extern int legal_identifier __P((char *)); extern int check_identifier __P((WORD_DESC *, int)); extern int sh_unset_nodelay_mode __P((int)); extern void check_dev_tty __P((void)); extern int move_to_high_fd __P((int, int, int)); extern int check_binary_file __P((char *, int)); #ifdef _POSIXSTAT_H_ extern int same_file __P((char *, char *, struct stat *, struct stat *)); #endif extern char *make_absolute __P((char *, char *)); extern int absolute_pathname __P((const char *)); extern int absolute_program __P((const char *)); extern char *base_pathname __P((char *)); extern char *full_pathname __P((char *)); extern char *polite_directory_format __P((char *)); extern char *extract_colon_unit __P((char *, int *)); extern void tilde_initialize __P((void)); extern char *bash_tilde_expand __P((const char *)); extern int group_member __P((gid_t)); extern char **get_group_list __P((int *)); extern int *get_group_array __P((int *)); #endif /* _GENERAL_H_ */ /* hashcmd.h - Common defines for hashing filenames. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "stdc.h" #include "hashlib.h" #define FILENAME_HASH_BUCKETS 107 extern HASH_TABLE *hashed_filenames; typedef struct { char *path; /* The full pathname of the file. */ int flags; } PATH_DATA; #define HASH_RELPATH 0x01 /* this filename is a relative pathname. */ #define HASH_CHKDOT 0x02 /* check `.' since it was earlier in $PATH */ #define pathdata(x) ((PATH_DATA *)(x)->data) extern void initialize_filename_hashing __P((void)); extern void flush_hashed_filenames __P((void)); extern void remove_hashed_filename __P((const char *)); extern void remember_filename __P((char *, char *, int, int)); extern char *find_hashed_filename __P((const char *)); /* hashlib.h -- the data structures used in hashing in Bash. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_HASHLIB_H_) #define _HASHLIB_H_ #include "stdc.h" typedef struct bucket_contents { struct bucket_contents *next; /* Link to next hashed key in this bucket. */ char *key; /* What we look up. */ char *data; /* What we really want. */ int times_found; /* Number of times this item has been found. */ } BUCKET_CONTENTS; typedef struct hash_table { BUCKET_CONTENTS **bucket_array; /* Where the data is kept. */ int nbuckets; /* How many buckets does this table have. */ int nentries; /* How many entries does this table have. */ } HASH_TABLE; extern int hash_string __P((const char *, HASH_TABLE *)); extern int hash_table_nentries __P((HASH_TABLE *)); extern HASH_TABLE *make_hash_table __P((int)); extern HASH_TABLE *copy_hash_table __P((HASH_TABLE *, sh_string_func_t *)); extern BUCKET_CONTENTS *find_hash_item __P((const char *, HASH_TABLE *)); extern BUCKET_CONTENTS *remove_hash_item __P((const char *, HASH_TABLE *)); extern BUCKET_CONTENTS *add_hash_item __P((char *, HASH_TABLE *)); extern void flush_hash_table __P((HASH_TABLE *, sh_free_func_t *)); extern void dispose_hash_table __P((HASH_TABLE *)); /* Redefine the function as a macro for speed. */ #define get_hash_bucket(bucket, table) \ ((table && (bucket < table->nbuckets)) ? \ table->bucket_array[bucket] : \ (BUCKET_CONTENTS *)NULL) /* Default number of buckets in the hash table. */ #define DEFAULT_HASH_BUCKETS 53 /* was 107 */ #define HASH_ENTRIES(ht) ((ht) ? (ht)->nentries : 0) #if !defined (NULL) # if defined (__STDC__) # define NULL ((void *) 0) # else # define NULL 0x0 # endif /* !__STDC__ */ #endif /* !NULL */ #endif /* _HASHLIB_H */ /* input.h -- Structures and unions used for reading input. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_INPUT_H_) #define _INPUT_H_ #include "stdc.h" /* Function pointers can be declared as (Function *)foo. */ #if !defined (_FUNCTION_DEF) # define _FUNCTION_DEF typedef int Function (); typedef void VFunction (); typedef char *CPFunction (); /* no longer used */ typedef char **CPPFunction (); /* no longer used */ #endif /* _FUNCTION_DEF */ typedef int sh_cget_func_t __P((void)); /* sh_ivoidfunc_t */ typedef int sh_cunget_func_t __P((int)); /* sh_intfunc_t */ enum stream_type {st_none, st_stdin, st_stream, st_string, st_bstream}; #if defined (BUFFERED_INPUT) /* Possible values for b_flag. */ #undef B_EOF #undef B_ERROR /* There are some systems with this define */ #undef B_UNBUFF #define B_EOF 0x01 #define B_ERROR 0x02 #define B_UNBUFF 0x04 #define B_WASBASHINPUT 0x08 /* A buffered stream. Like a FILE *, but with our own buffering and synchronization. Look in input.c for the implementation. */ typedef struct BSTREAM { int b_fd; char *b_buffer; /* The buffer that holds characters read. */ size_t b_size; /* How big the buffer is. */ size_t b_used; /* How much of the buffer we're using, */ int b_flag; /* Flag values. */ size_t b_inputp; /* The input pointer, index into b_buffer. */ } BUFFERED_STREAM; #if 0 extern BUFFERED_STREAM **buffers; #endif extern int default_buffered_input; #endif /* BUFFERED_INPUT */ typedef union { FILE *file; char *string; #if defined (BUFFERED_INPUT) int buffered_fd; #endif } INPUT_STREAM; typedef struct { enum stream_type type; char *name; INPUT_STREAM location; sh_cget_func_t *getter; sh_cunget_func_t *ungetter; } BASH_INPUT; extern BASH_INPUT bash_input; /* Functions from parse.y. */ extern void initialize_bash_input __P((void)); extern void init_yy_io __P((sh_cget_func_t *, sh_cunget_func_t *, enum stream_type, const char *, INPUT_STREAM)); extern void with_input_from_stdin __P((void)); extern void with_input_from_string __P((char *, const char *)); extern void with_input_from_stream __P((FILE *, const char *)); extern void push_stream __P((int)); extern void pop_stream __P((void)); extern int stream_on_stack __P((enum stream_type)); extern char *read_secondary_line __P((int)); extern int find_reserved_word __P((char *)); extern char *decode_prompt_string __P((char *)); extern void gather_here_documents __P((void)); extern void execute_prompt_command __P((char *)); extern int *save_token_state __P((void)); extern void restore_token_state __P((int *)); /* Functions from input.c */ extern int getc_with_restart __P((FILE *)); extern int ungetc_with_restart __P((int, FILE *)); #if defined (BUFFERED_INPUT) /* Functions from input.c. */ extern int fd_is_bash_input __P((int)); extern int set_bash_input_fd __P((int)); extern int save_bash_input __P((int, int)); extern int check_bash_input __P((int)); extern int duplicate_buffered_stream __P((int, int)); extern BUFFERED_STREAM *fd_to_buffered_stream __P((int)); extern BUFFERED_STREAM *set_buffered_stream __P((int, BUFFERED_STREAM *)); extern BUFFERED_STREAM *open_buffered_stream __P((char *)); extern void free_buffered_stream __P((BUFFERED_STREAM *)); extern int close_buffered_stream __P((BUFFERED_STREAM *)); extern int close_buffered_fd __P((int)); extern int sync_buffered_stream __P((int)); extern int buffered_getchar __P((void)); extern int buffered_ungetchar __P((int)); extern void with_input_from_buffered_stream __P((int, char *)); #endif /* BUFFERED_INPUT */ #endif /* _INPUT_H_ */ /* jobs.h -- structures and stuff used by the jobs.c file. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_JOBS_H_) # define _JOBS_H_ #include "quit.h" #include "siglist.h" #include "stdc.h" #include "posixwait.h" /* Defines controlling the fashion in which jobs are listed. */ #define JLIST_STANDARD 0 #define JLIST_LONG 1 #define JLIST_PID_ONLY 2 #define JLIST_CHANGED_ONLY 3 #define JLIST_NONINTERACTIVE 4 /* I looked it up. For pretty_print_job (). The real answer is 24. */ #define LONGEST_SIGNAL_DESC 24 /* We keep an array of jobs. Each entry in the array is a linked list of processes that are piped together. The first process encountered is the group leader. */ /* Values for the `running' field of a struct process. */ #define PS_DONE 0 #define PS_RUNNING 1 #define PS_STOPPED 2 /* Each child of the shell is remembered in a STRUCT PROCESS. A chain of such structures is a pipeline. The chain is circular. */ typedef struct process { struct process *next; /* Next process in the pipeline. A circular chain. */ pid_t pid; /* Process ID. */ WAIT status; /* The status of this command as returned by wait. */ int running; /* Non-zero if this process is running. */ char *command; /* The particular program that is running. */ } PROCESS; /* A description of a pipeline's state. */ typedef enum { JRUNNING, JSTOPPED, JDEAD, JMIXED } JOB_STATE; #define JOBSTATE(job) (jobs[(job)]->state) #define STOPPED(j) (jobs[(j)]->state == JSTOPPED) #define RUNNING(j) (jobs[(j)]->state == JRUNNING) #define DEADJOB(j) (jobs[(j)]->state == JDEAD) /* Values for the FLAGS field in the JOB struct below. */ #define J_FOREGROUND 0x01 /* Non-zero if this is running in the foreground. */ #define J_NOTIFIED 0x02 /* Non-zero if already notified about job state. */ #define J_JOBCONTROL 0x04 /* Non-zero if this job started under job control. */ #define J_NOHUP 0x08 /* Don't send SIGHUP to job if shell gets SIGHUP. */ #define IS_FOREGROUND(j) ((jobs[j]->flags & J_FOREGROUND) != 0) #define IS_NOTIFIED(j) ((jobs[j]->flags & J_NOTIFIED) != 0) #define IS_JOBCONTROL(j) ((jobs[j]->flags & J_JOBCONTROL) != 0) typedef struct job { char *wd; /* The working directory at time of invocation. */ PROCESS *pipe; /* The pipeline of processes that make up this job. */ pid_t pgrp; /* The process ID of the process group (necessary). */ JOB_STATE state; /* The state that this job is in. */ int flags; /* Flags word: J_NOTIFIED, J_FOREGROUND, or J_JOBCONTROL. */ #if defined (JOB_CONTROL) COMMAND *deferred; /* Commands that will execute when this job is done. */ sh_vptrfunc_t *j_cleanup; /* Cleanup function to call when job marked JDEAD */ PTR_T cleanarg; /* Argument passed to (*j_cleanup)() */ #endif /* JOB_CONTROL */ } JOB; #define NO_JOB -1 /* An impossible job array index. */ #define DUP_JOB -2 /* A possible return value for get_job_spec (). */ /* A value which cannot be a process ID. */ #define NO_PID (pid_t)-1 /* System calls. */ #if !defined (HAVE_UNISTD_H) extern pid_t fork (), getpid (), getpgrp (); #endif /* !HAVE_UNISTD_H */ /* Stuff from the jobs.c file. */ extern pid_t original_pgrp, shell_pgrp, pipeline_pgrp; extern pid_t last_made_pid, last_asynchronous_pid; extern int current_job, previous_job; extern int asynchronous_notification; extern JOB **jobs; extern int job_slots; extern void making_children __P((void)); extern void stop_making_children __P((void)); extern void cleanup_the_pipeline __P((void)); extern void save_pipeline __P((int)); extern void restore_pipeline __P((int)); extern void start_pipeline __P((void)); extern int stop_pipeline __P((int, COMMAND *)); extern void delete_job __P((int, int)); extern void nohup_job __P((int)); extern void delete_all_jobs __P((int)); extern void nohup_all_jobs __P((int)); extern int count_all_jobs __P((void)); extern void terminate_current_pipeline __P((void)); extern void terminate_stopped_jobs __P((void)); extern void hangup_all_jobs __P((void)); extern void kill_current_pipeline __P((void)); #if defined (__STDC__) && defined (pid_t) extern int get_job_by_pid __P((int, int)); extern void describe_pid __P((int)); #else extern int get_job_by_pid __P((pid_t, int)); extern void describe_pid __P((pid_t)); #endif extern void list_one_job __P((JOB *, int, int, int)); extern void list_all_jobs __P((int)); extern void list_stopped_jobs __P((int)); extern void list_running_jobs __P((int)); extern pid_t make_child __P((char *, int)); extern int get_tty_state __P((void)); extern int set_tty_state __P((void)); extern int wait_for_single_pid __P((pid_t)); extern void wait_for_background_pids __P((void)); extern int wait_for __P((pid_t)); extern int wait_for_job __P((int)); extern void notify_and_cleanup __P((void)); extern void reap_dead_jobs __P((void)); extern int start_job __P((int, int)); extern int kill_pid __P((pid_t, int, int)); extern int initialize_job_control __P((int)); extern void initialize_job_signals __P((void)); extern int give_terminal_to __P((pid_t, int)); extern void set_sigwinch_handler __P((void)); extern void unset_sigwinch_handler __P((void)); extern void unfreeze_jobs_list __P((void)); extern int set_job_control __P((int)); extern void without_job_control __P((void)); extern void end_job_control __P((void)); extern void restart_job_control __P((void)); extern void set_sigchld_handler __P((void)); extern void ignore_tty_job_signals __P((void)); extern void default_tty_job_signals __P((void)); #if defined (JOB_CONTROL) extern int job_control; #endif #endif /* _JOBS_H_ */ /* mailcheck.h -- variables and function declarations for mail checking. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_MAILCHECK_H_) #define _MAILCHECK_H_ /* Functions from mailcheck.c */ extern int time_to_check_mail __P((void)); extern void reset_mail_timer __P((void)); extern void reset_mail_files __P((void)); extern void free_mail_files __P((void)); extern char *make_default_mailpath __P((void)); extern void remember_mail_dates __P((void)); extern void check_mail __P((void)); #endif /* _MAILCHECK_H */ /* make_cmd.h -- Declarations of functions found in make_cmd.c */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_MAKE_CMD_H_) #define _MAKE_CMD_H_ #include "stdc.h" extern WORD_LIST *make_word_list __P((WORD_DESC *, WORD_LIST *)); extern WORD_LIST *add_string_to_list __P((char *, WORD_LIST *)); extern WORD_DESC *make_bare_word __P((const char *)); extern WORD_DESC *make_word_flags __P((WORD_DESC *, const char *)); extern WORD_DESC *make_word __P((const char *)); extern WORD_DESC *make_word_from_token __P((int)); extern COMMAND *make_command __P((enum command_type, SIMPLE_COM *)); extern COMMAND *command_connect __P((COMMAND *, COMMAND *, int)); extern COMMAND *make_for_command __P((WORD_DESC *, WORD_LIST *, COMMAND *)); extern COMMAND *make_group_command __P((COMMAND *)); extern COMMAND *make_case_command __P((WORD_DESC *, PATTERN_LIST *)); extern PATTERN_LIST *make_pattern_list __P((WORD_LIST *, COMMAND *)); extern COMMAND *make_if_command __P((COMMAND *, COMMAND *, COMMAND *)); extern COMMAND *make_while_command __P((COMMAND *, COMMAND *)); extern COMMAND *make_until_command __P((COMMAND *, COMMAND *)); extern COMMAND *make_bare_simple_command __P((void)); extern COMMAND *make_simple_command __P((ELEMENT, COMMAND *)); extern void make_here_document __P((REDIRECT *)); extern REDIRECT *make_redirection __P((int, enum r_instruction, REDIRECTEE)); extern COMMAND *make_function_def __P((WORD_DESC *, COMMAND *, int, int)); extern COMMAND *clean_simple_command __P((COMMAND *)); extern COMMAND *make_arith_command __P((WORD_LIST *)); extern COMMAND *make_select_command __P((WORD_DESC *, WORD_LIST *, COMMAND *)); #if defined (COND_COMMAND) extern COND_COM *make_cond_node __P((int, WORD_DESC *, COND_COM *, COND_COM *)); extern COMMAND *make_cond_command __P((COND_COM *)); #endif extern COMMAND *make_arith_for_command __P((WORD_LIST *, COMMAND *, int)); extern COMMAND *make_subshell_command __P((COMMAND *)); extern COMMAND *connect_async_list __P((COMMAND *, COMMAND *, int)); #endif /* !_MAKE_CMD_H */ /* parser.h -- Everything you wanted to know about the parser, but were afraid to ask. */ /* Copyright (C) 1995 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_PARSER_H_) # define _PARSER_H_ # include "command.h" # include "input.h" /* Definition of the delimiter stack. Needed by parse.y and bashhist.c. */ struct dstack { /* DELIMITERS is a stack of the nested delimiters that we have encountered so far. */ char *delimiters; /* Offset into the stack of delimiters. */ int delimiter_depth; /* How many slots are allocated to DELIMITERS. */ int delimiter_space; }; #endif /* _PARSER_H_ */ /* patchlevel.h -- current bash patch level */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_PATCHLEVEL_H_) #define _PATCHLEVEL_H_ /* It's important that there be no other strings in this file that match the regexp `^#define[ ]*PATCHLEVEL', since that's what support/mkversion.sh looks for to find the patch level (for the sccs version string). */ #define PATCHLEVEL 0 #endif /* _PATCHLEVEL_H_ */ /* pathexp.h -- The shell interface to the globbing library. */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_PATHEXP_H_) #define _PATHEXP_H_ #if defined (USE_POSIX_GLOB_LIBRARY) # define GLOB_FAILED(glist) !(glist) #else /* !USE_POSIX_GLOB_LIBRARY */ # define GLOB_FAILED(glist) (glist) == (char **)&glob_error_return extern int noglob_dot_filenames; extern char *glob_error_return; #endif /* !USE_POSIX_GLOB_LIBRARY */ /* Flag values for quote_string_for_globbing */ #define QGLOB_CVTNULL 0x01 /* convert QUOTED_NULL strings to '\0' */ #define QGLOB_FILENAME 0x02 /* do correct quoting for matching filenames */ #if defined (EXTENDED_GLOB) /* Flags to OR with other flag args to strmatch() to enabled the extended pattern matching. */ # define FNMATCH_EXTFLAG (extended_glob ? FNM_EXTMATCH : 0) #else # define FNMATCH_EXTFLAG 0 #endif /* !EXTENDED_GLOB */ extern int glob_dot_filenames; extern int extended_glob; extern int unquoted_glob_pattern_p __P((char *)); /* PATHNAME can contain characters prefixed by CTLESC; this indicates that the character is to be quoted. We quote it here in the style that the glob library recognizes. If flags includes QGLOB_CVTNULL, we change quoted null strings (pathname[0] == CTLNUL) into empty strings (pathname[0] == 0). If this is called after quote removal is performed, (flags & QGLOB_CVTNULL) should be 0; if called when quote removal has not been done (for example, before attempting to match a pattern while executing a case statement), flags should include QGLOB_CVTNULL. If flags includes QGLOB_FILENAME, appropriate quoting to match a filename should be performed. */ extern char *quote_string_for_globbing __P((const char *, int)); extern char *quote_globbing_chars __P((char *)); /* Call the glob library to do globbing on PATHNAME. */ extern char **shell_glob_filename __P((const char *)); /* Filename completion ignore. Used to implement the "fignore" facility of tcsh and GLOBIGNORE (like ksh-93 FIGNORE). It is passed a NULL-terminated array of (char *)'s that must be free()'d if they are deleted. The first element (names[0]) is the least-common-denominator string of the matching patterns (i.e. u produces names[0] = "und", names[1] = "under.c", names[2] = "undun.c", name[3] = NULL). */ struct ign { char *val; int len, flags; }; typedef int sh_iv_item_func_t __P((struct ign *)); struct ignorevar { char *varname; /* FIGNORE or GLOBIGNORE */ struct ign *ignores; /* Store the ignore strings here */ int num_ignores; /* How many are there? */ char *last_ignoreval; /* Last value of variable - cached for speed */ sh_iv_item_func_t *item_func; /* Called when each item is parsed from $`varname' */ }; extern void setup_ignore_patterns __P((struct ignorevar *)); extern void setup_glob_ignore __P((char *)); extern int should_ignore_glob_matches __P((void)); extern void ignore_glob_matches __P((char **)); #endif /* pathnames.h -- absolute filenames that bash wants for various defaults. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_PATHNAMES_H_) #define _PATHNAMES_H_ /* The default file for hostname completion. */ #define DEFAULT_HOSTS_FILE "/etc/hosts" /* The default login shell startup file. */ #define SYS_PROFILE "/etc/profile" #endif /* _PATHNAMES_H */ /* pcomplete.h - structure definitions and other stuff for programmable completion. */ /* Copyright (C) 1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_PCOMPLETE_H_) # define _PCOMPLETE_H_ #include "stdc.h" #include "hashlib.h" typedef struct compspec { int refcount; unsigned long actions; unsigned long options; char *globpat; char *words; char *prefix; char *suffix; char *funcname; char *command; char *filterpat; } COMPSPEC; /* Values for COMPSPEC actions. These are things the shell knows how to build internally. */ #define CA_ALIAS (1<<0) #define CA_ARRAYVAR (1<<1) #define CA_BINDING (1<<2) #define CA_BUILTIN (1<<3) #define CA_COMMAND (1<<4) #define CA_DIRECTORY (1<<5) #define CA_DISABLED (1<<6) #define CA_ENABLED (1<<7) #define CA_EXPORT (1<<8) #define CA_FILE (1<<9) #define CA_FUNCTION (1<<10) #define CA_GROUP (1<<11) #define CA_HELPTOPIC (1<<12) #define CA_HOSTNAME (1<<13) #define CA_JOB (1<<14) #define CA_KEYWORD (1<<15) #define CA_RUNNING (1<<16) #define CA_SETOPT (1<<17) #define CA_SHOPT (1<<18) #define CA_SIGNAL (1<<19) #define CA_STOPPED (1<<20) #define CA_USER (1<<21) #define CA_VARIABLE (1<<22) /* Values for COMPSPEC options field. */ #define COPT_RESERVED (1<<0) /* reserved for other use */ #define COPT_DEFAULT (1<<1) #define COPT_FILENAMES (1<<2) #define COPT_DIRNAMES (1<<3) /* List of items is used by the code that implements the programmable completions. */ typedef struct _list_of_items { int flags; int (*list_getter) __P((struct _list_of_items *)); /* function to call to get the list */ STRINGLIST *slist; /* These may or may not be used. */ STRINGLIST *genlist; /* for handing to the completion code one item at a time */ int genindex; /* index of item last handed to completion code */ } ITEMLIST; /* Values for ITEMLIST -> flags */ #define LIST_DYNAMIC 0x001 #define LIST_DIRTY 0x002 #define LIST_INITIALIZED 0x004 #define LIST_MUSTSORT 0x008 #define LIST_DONTFREE 0x010 #define LIST_DONTFREEMEMBERS 0x020 extern HASH_TABLE *prog_completes; extern int prog_completion_enabled; /* Not all of these are used yet. */ extern ITEMLIST it_aliases; extern ITEMLIST it_arrayvars; extern ITEMLIST it_bindings; extern ITEMLIST it_builtins; extern ITEMLIST it_commands; extern ITEMLIST it_directories; extern ITEMLIST it_disabled; extern ITEMLIST it_enabled; extern ITEMLIST it_exports; extern ITEMLIST it_files; extern ITEMLIST it_functions; extern ITEMLIST it_groups; extern ITEMLIST it_hostnames; extern ITEMLIST it_jobs; extern ITEMLIST it_keywords; extern ITEMLIST it_running; extern ITEMLIST it_setopts; extern ITEMLIST it_shopts; extern ITEMLIST it_signals; extern ITEMLIST it_stopped; extern ITEMLIST it_users; extern ITEMLIST it_variables; /* Functions from pcomplib.c */ typedef void sh_csprint_func_t __P((char *, COMPSPEC *)); extern COMPSPEC *alloc_compspec __P((void)); extern void free_compspec __P((COMPSPEC *)); extern COMPSPEC *copy_compspec __P((COMPSPEC *)); extern void initialize_progcomp __P((void)); extern void clear_progcomps __P((void)); extern int remove_progcomp __P((char *)); extern int add_progcomp __P((char *, COMPSPEC *)); extern int num_progcomps __P((void)); extern COMPSPEC *find_compspec __P((const char *)); extern void print_all_compspecs __P((sh_csprint_func_t *)); /* Functions from pcomplete.c */ extern void set_itemlist_dirty __P((ITEMLIST *)); extern STRINGLIST *completions_to_stringlist __P((char **)); extern STRINGLIST *gen_compspec_completions __P((COMPSPEC *, const char *, const char *, int, int)); extern char **programmable_completions __P((const char *, const char *, int, int, int *)); #endif /* _PCOMPLETE_H_ */ /* quit.h -- How to handle SIGINT gracefully. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_QUIT_H_) #define _QUIT_H_ /* Non-zero means SIGINT has already ocurred. */ extern int interrupt_state; /* Macro to call a great deal. SIGINT just sets above variable. When it is safe, put QUIT in the code, and the "interrupt" will take place. */ #define QUIT if (interrupt_state) throw_to_top_level () #define SETINTERRUPT interrupt_state = 1 #define CLRINTERRUPT interrupt_state = 0 #define ADDINTERRUPT interrupt_state++ #define DELINTERRUPT interrupt_state-- #endif /* _QUIT_H_ */ /* redir.h - functions from redir.c. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_REDIR_H_) #define _REDIR_H_ #include "stdc.h" extern void redirection_error __P((REDIRECT *, int)); extern int do_redirections __P((REDIRECT *, int, int, int)); extern char *redirection_expand __P((WORD_DESC *)); extern int stdin_redirects __P((REDIRECT *)); #endif /* _REDIR_H_ */ /* shell.h -- The data structures used by the shell */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "bashjmp.h" #include "command.h" #include "syntax.h" #include "general.h" #include "error.h" #include "variables.h" #include "arrayfunc.h" #include "quit.h" #include "maxpath.h" #include "unwind_prot.h" #include "dispose_cmd.h" #include "make_cmd.h" #include "subst.h" #include "sig.h" #include "pathnames.h" #include "externs.h" #include "version.h" extern int EOF_Reached; #define NO_PIPE -1 #define REDIRECT_BOTH -2 #define NO_VARIABLE -1 /* Values that can be returned by execute_command (). */ #define EXECUTION_FAILURE 1 #define EXECUTION_SUCCESS 0 /* Usage messages by builtins result in a return status of 2. */ #define EX_BADUSAGE 2 /* Special exit statuses used by the shell, internally and externally. */ #define EX_BINARY_FILE 126 #define EX_NOEXEC 126 #define EX_NOINPUT 126 #define EX_NOTFOUND 127 #define EX_SHERRBASE 256 /* all special error values are > this. */ #define EX_BADSYNTAX 257 /* shell syntax error */ #define EX_USAGE 258 /* syntax error in usage */ #define EX_REDIRFAIL 259 /* redirection failed */ #define EX_BADASSIGN 260 /* variable assignment error */ #define EX_EXPFAIL 261 /* word expansion failed */ /* Flag values that control parameter pattern substitution. */ #define MATCH_ANY 0x0 #define MATCH_BEG 0x1 #define MATCH_END 0x2 #define MATCH_TYPEMASK 0x3 #define MATCH_GLOBREP 0x10 #define MATCH_QUOTED 0x20 /* Some needed external declarations. */ extern char **shell_environment; extern WORD_LIST *rest_of_args; /* Generalized global variables. */ extern int executing, login_shell; extern int interactive, interactive_shell; /* Structure to pass around that holds a bitmap of file descriptors to close, and the size of that structure. Used in execute_cmd.c. */ struct fd_bitmap { int size; char *bitmap; }; #define FD_BITMAP_SIZE 32 #define CTLESC '\001' #define CTLNUL '\177' /* Information about the current user. */ struct user_info { uid_t uid, euid; gid_t gid, egid; char *user_name; char *shell; /* shell from the password file */ char *home_dir; }; extern struct user_info current_user; /* Force gcc to not clobber X on a longjmp(). Old versions of gcc mangle this badly. */ #if __GNUC__ == 2 && __GNUC_MINOR__ > 8 # define USE_VAR(x) ((void) &(x)) #else # define USE_VAR(x) #endif /* sig.h -- header file for signal handler definitions. */ /* Copyright (C) 1994 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* Make sure that this is included *after* config.h! */ #if !defined (_SIG_H_) # define _SIG_H_ #include "stdc.h" #if !defined (SIGABRT) && defined (SIGIOT) # define SIGABRT SIGIOT #endif #define sighandler RETSIGTYPE typedef RETSIGTYPE SigHandler (); #if defined (VOID_SIGHANDLER) # define SIGRETURN(n) return #else # define SIGRETURN(n) return(n) #endif /* !VOID_SIGHANDLER */ /* Here is a definition for set_signal_handler () which simply expands to a call to signal () for non-Posix systems. The code for set_signal_handler in the Posix case resides in general.c. */ #if !defined (HAVE_POSIX_SIGNALS) # define set_signal_handler(sig, handler) (SigHandler *)signal (sig, handler) #else extern SigHandler *set_signal_handler (); /* in sig.c */ #endif /* _POSIX_VERSION */ /* Definitions used by the job control code. */ #if defined (JOB_CONTROL) #if !defined (SIGCHLD) && defined (SIGCLD) # define SIGCHLD SIGCLD #endif #if !defined (HAVE_POSIX_SIGNALS) && !defined (sigmask) # define sigmask(x) (1 << ((x)-1)) #endif /* !HAVE_POSIX_SIGNALS && !sigmask */ #if !defined (HAVE_POSIX_SIGNALS) # if !defined (SIG_BLOCK) # define SIG_BLOCK 2 # define SIG_SETMASK 3 # endif /* SIG_BLOCK */ /* sigset_t defined in config.h */ /* Make sure there is nothing inside the signal set. */ # define sigemptyset(set) (*(set) = 0) /* Initialize the signal set to hold all signals. */ # define sigfillset(set) (*set) = sigmask (NSIG) - 1 /* Add SIG to the contents of SET. */ # define sigaddset(set, sig) *(set) |= sigmask (sig) /* Delete SIG from signal set SET. */ # define sigdelset(set, sig) *(set) &= ~sigmask (sig) /* Is SIG a member of the signal set SET? */ # define sigismember(set, sig) ((*(set) & sigmask (sig)) != 0) /* Suspend the process until the reception of one of the signals not present in SET. */ # define sigsuspend(set) sigpause (*(set)) #endif /* !HAVE_POSIX_SIGNALS */ /* These definitions are used both in POSIX and non-POSIX implementations. */ #define BLOCK_SIGNAL(sig, nvar, ovar) \ sigemptyset (&nvar); \ sigaddset (&nvar, sig); \ sigemptyset (&ovar); \ sigprocmask (SIG_BLOCK, &nvar, &ovar) #if defined (HAVE_POSIX_SIGNALS) # define BLOCK_CHILD(nvar, ovar) \ BLOCK_SIGNAL (SIGCHLD, nvar, ovar) # define UNBLOCK_CHILD(ovar) \ sigprocmask (SIG_SETMASK, &ovar, (sigset_t *) NULL) #else /* !HAVE_POSIX_SIGNALS */ # define BLOCK_CHILD(nvar, ovar) ovar = sigblock (sigmask (SIGCHLD)) # define UNBLOCK_CHILD(ovar) sigsetmask (ovar) #endif /* !HAVE_POSIX_SIGNALS */ #endif /* JOB_CONTROL */ /* Functions from sig.c. */ extern sighandler termination_unwind_protect __P((int)); extern sighandler sigint_sighandler __P((int)); extern void initialize_signals __P((void)); extern void reinitialize_signals __P((void)); extern void initialize_terminating_signals __P((void)); extern void reset_terminating_signals __P((void)); extern void throw_to_top_level __P((void)); extern void jump_to_top_level __P((int)) __attribute__((__noreturn__)); /* Functions defined in trap.c. */ extern SigHandler *set_sigint_handler __P((void)); extern SigHandler *trap_to_sighandler __P((int)); extern sighandler trap_handler __P((int)); #endif /* _SIG_H_ */ /* siglist.h -- encapsulate various definitions for sys_siglist */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_SIGLIST_H_) #define _SIGLIST_H_ #if !defined (SYS_SIGLIST_DECLARED) && !defined (HAVE_STRSIGNAL) #if defined (HAVE_UNDER_SYS_SIGLIST) && !defined (HAVE_SYS_SIGLIST) && !defined (sys_siglist) # define sys_siglist _sys_siglist #endif /* HAVE_UNDER_SYS_SIGLIST && !HAVE_SYS_SIGLIST && !sys_siglist */ #if !defined (sys_siglist) extern char *sys_siglist[]; #endif /* !sys_siglist */ #endif /* !SYS_SIGLIST_DECLARED && !HAVE_STRSIGNAL */ #if !defined (strsignal) && !defined (HAVE_STRSIGNAL) # define strsignal(sig) (char *)sys_siglist[sig] #endif /* !strsignal && !HAVE_STRSIGNAL */ #if !defined (strsignal) && !HAVE_DECL_STRSIGNAL extern char *strsignal __P((int)); #endif #endif /* _SIGLIST_H */ /* subst.h -- Names of externally visible functions in subst.c. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_SUBST_H_) #define _SUBST_H_ #include "stdc.h" /* Constants which specify how to handle backslashes and quoting in expand_word_internal (). Q_DOUBLE_QUOTES means to use the function slashify_in_quotes () to decide whether the backslash should be retained. Q_HERE_DOCUMENT means slashify_in_here_document () to decide whether to retain the backslash. Q_KEEP_BACKSLASH means to unconditionally retain the backslash. */ #define Q_DOUBLE_QUOTES 0x1 #define Q_HERE_DOCUMENT 0x2 #define Q_KEEP_BACKSLASH 0x4 #define Q_NOQUOTE 0x8 #define Q_QUOTED 0x10 #define Q_ADDEDQUOTES 0x20 #define Q_QUOTEDNULL 0x40 /* Cons a new string from STRING starting at START and ending at END, not including END. */ extern char *substring __P((char *, int, int)); /* Remove backslashes which are quoting backquotes from STRING. Modifies STRING, and returns a pointer to it. */ extern char * de_backslash __P((char *)); /* Replace instances of \! in a string with !. */ extern void unquote_bang __P((char *)); /* Extract the $( construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "$(". Make (SINDEX) get the position just after the matching ")". */ extern char *extract_command_subst __P((char *, int *)); /* Extract the $[ construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "$[". Make (SINDEX) get the position just after the matching "]". */ extern char *extract_arithmetic_subst __P((char *, int *)); #if defined (PROCESS_SUBSTITUTION) /* Extract the <( or >( construct in STRING, and return a new string. Start extracting at (SINDEX) as if we had just seen "<(". Make (SINDEX) get the position just after the matching ")". */ extern char *extract_process_subst __P((char *, char *, int *)); #endif /* PROCESS_SUBSTITUTION */ /* Extract the name of the variable to bind to from the assignment string. */ extern char *assignment_name __P((char *)); /* Return a single string of all the words present in LIST, separating each word with a space. */ extern char *string_list __P((WORD_LIST *)); /* Turn $* into a single string, obeying POSIX rules. */ extern char *string_list_dollar_star __P((WORD_LIST *)); /* Expand $@ into a single string, obeying POSIX rules. */ extern char *string_list_dollar_at __P((WORD_LIST *, int)); /* Perform quoted null character removal on each element of LIST. This modifies LIST. */ extern void word_list_remove_quoted_nulls __P((WORD_LIST *)); /* This performs word splitting and quoted null character removal on STRING. */ extern WORD_LIST *list_string __P((char *, char *, int)); extern char *get_word_from_string __P((char **, char *, char **)); extern char *strip_trailing_ifs_whitespace __P((char *, char *, int)); /* Given STRING, an assignment string, get the value of the right side of the `=', and bind it to the left side. If EXPAND is true, then perform parameter expansion, command substitution, and arithmetic expansion on the right-hand side. Perform tilde expansion in any case. Do not perform word splitting on the result of expansion. */ extern int do_assignment __P((const char *)); extern int do_assignment_no_expand __P((const char *)); /* Append SOURCE to TARGET at INDEX. SIZE is the current amount of space allocated to TARGET. SOURCE can be NULL, in which case nothing happens. Gets rid of SOURCE by free ()ing it. Returns TARGET in case the location has changed. */ extern char *sub_append_string __P((char *, char *, int *, int *)); /* Append the textual representation of NUMBER to TARGET. INDEX and SIZE are as in SUB_APPEND_STRING. */ extern char *sub_append_number __P((long, char *, int *, int *)); /* Return the word list that corresponds to `$*'. */ extern WORD_LIST *list_rest_of_args __P((void)); /* Make a single large string out of the dollar digit variables, and the rest_of_args. If DOLLAR_STAR is 1, then obey the special case of "$*" with respect to IFS. */ extern char *string_rest_of_args __P((int)); extern int number_of_args __P((void)); /* Expand STRING by performing parameter expansion, command substitution, and arithmetic expansion. Dequote the resulting WORD_LIST before returning it, but do not perform word splitting. The call to remove_quoted_nulls () is made here because word splitting normally takes care of quote removal. */ extern WORD_LIST *expand_string_unsplit __P((char *, int)); /* Expand a prompt string. */ extern WORD_LIST *expand_prompt_string __P((char *, int)); /* Expand STRING just as if you were expanding a word. This also returns a list of words. Note that filename globbing is *NOT* done for word or string expansion, just when the shell is expanding a command. This does parameter expansion, command substitution, arithmetic expansion, and word splitting. Dequote the resultant WORD_LIST before returning. */ extern WORD_LIST *expand_string __P((char *, int)); /* Convenience functions that expand strings to strings, taking care of converting the WORD_LIST * returned by the expand_string* functions to a string and deallocating the WORD_LIST *. */ extern char *expand_string_to_string __P((char *, int)); extern char *expand_string_unsplit_to_string __P((char *, int)); /* De-quoted quoted characters in STRING. */ extern char *dequote_string __P((char *)); /* Expand WORD, performing word splitting on the result. This does parameter expansion, command substitution, arithmetic expansion, word splitting, and quote removal. */ extern WORD_LIST *expand_word __P((WORD_DESC *, int)); /* Expand WORD, but do not perform word splitting on the result. This does parameter expansion, command substitution, arithmetic expansion, and quote removal. */ extern WORD_LIST *expand_word_unsplit __P((WORD_DESC *, int)); extern WORD_LIST *expand_word_leave_quoted __P((WORD_DESC *, int)); /* Return the value of a positional parameter. This handles values > 10. */ extern char *get_dollar_var_value __P((long)); /* Quote a string to protect it from word splitting. */ extern char *quote_string __P((char *)); /* Quote escape characters (characters special to interals of expansion) in a string. */ extern char *quote_escapes __P((char *)); /* Perform quote removal on STRING. If QUOTED > 0, assume we are obeying the backslash quoting rules for within double quotes. */ extern char *string_quote_removal __P((char *, int)); /* Perform quote removal on word WORD. This allocates and returns a new WORD_DESC *. */ extern WORD_DESC *word_quote_removal __P((WORD_DESC *, int)); /* Perform quote removal on all words in LIST. If QUOTED is non-zero, the members of the list are treated as if they are surrounded by double quotes. Return a new list, or NULL if LIST is NULL. */ extern WORD_LIST *word_list_quote_removal __P((WORD_LIST *, int)); /* This splits a single word into a WORD LIST on $IFS, but only if the word is not quoted. list_string () performs quote removal for us, even if we don't do any splitting. */ extern WORD_LIST *word_split __P((WORD_DESC *)); /* Take the list of words in LIST and do the various substitutions. Return a new list of words which is the expanded list, and without things like variable assignments. */ extern WORD_LIST *expand_words __P((WORD_LIST *)); /* Same as expand_words (), but doesn't hack variable or environment variables. */ extern WORD_LIST *expand_words_no_vars __P((WORD_LIST *)); /* Perform the `normal shell expansions' on a WORD_LIST. These are brace expansion, tilde expansion, parameter and variable substitution, command substitution, arithmetic expansion, and word splitting. */ extern WORD_LIST *expand_words_shellexp __P((WORD_LIST *)); extern char *command_substitute __P((char *, int)); extern char *pat_subst __P((char *, char *, char *, int)); extern void unlink_fifo_list __P((void)); extern WORD_LIST *list_string_with_quotes __P((char *)); #if defined (ARRAY_VARS) extern char *extract_array_assignment_list __P((char *, int *)); #endif #if defined (COND_COMMAND) extern char *remove_backslashes __P((char *)); extern char *cond_expand_word __P((WORD_DESC *, int)); #endif #if defined (READLINE) extern int char_is_quoted __P((char *, int)); extern int unclosed_pair __P((char *, int, char *)); extern int skip_to_delim __P((char *, int, char *)); extern WORD_LIST *split_at_delims __P((char *, int, char *, int, int *, int *)); #endif /* How to determine the quoted state of the character C. */ #define QUOTED_CHAR(c) ((c) == CTLESC) /* Is the first character of STRING a quoted NULL character? */ #define QUOTED_NULL(string) ((string)[0] == CTLNUL && (string)[1] == '\0') #endif /* !_SUBST_H_ */ /* syntax.h -- Syntax definitions for the shell */ /* Copyright (C) 2000 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #ifndef _SYNTAX_H_ #define _SYNTAX_H_ /* Defines for use by mksyntax.c */ #define slashify_in_quotes "\\`$\"\n" #define slashify_in_here_document "\\`$" #define shell_meta_chars "()<>;&|" #define shell_break_chars "()<>;&| \t\n" #define shell_quote_chars "\"`'" #if defined (PROCESS_SUBSTITUTION) # define shell_exp_chars "$<>" #else # define shell_exp_chars "$" #endif #if defined (EXTENDED_GLOB) # define ext_glob_chars "@*+?!" #else # define ext_glob_chars "" #endif #define shell_glob_chars "*?[]^" /* Defines shared by mksyntax.c and the rest of the shell code. */ /* Values for character flags in syntax tables */ #define CWORD 0x0000 /* nothing special; an ordinary character */ #define CSHMETA 0x0001 /* shell meta character */ #define CSHBRK 0x0002 /* shell break character */ #define CBACKQ 0x0004 /* back quote */ #define CQUOTE 0x0008 /* shell quote character */ #define CSPECL 0x0010 /* special character that needs quoting */ #define CEXP 0x0020 /* shell expansion character */ #define CBSDQUOTE 0x0040 /* characters escaped by backslash in double quotes */ #define CBSHDOC 0x0080 /* characters escaped by backslash in here doc */ #define CGLOB 0x0100 /* globbing characters */ #define CXGLOB 0x0200 /* extended globbing characters */ #define CXQUOTE 0x0400 /* cquote + backslash */ #define CSPECVAR 0x0800 /* single-character shell variable name */ /* Defines for use by the rest of the shell. */ extern const int sh_syntaxtab[]; #define shellmeta(c) (sh_syntaxtab[(c)] & CSHMETA) #define shellbreak(c) (sh_syntaxtab[(c)] & CSHBRK) #define shellquote(c) (sh_syntaxtab[(c)] & CQUOTE) #if defined (PROCESS_SUBSTITUTION) # define shellexp(c) ((c) == '$' || (c) == '<' || (c) == '>') #else # define shellexp(c) ((c) == '$') #endif #if defined (EXTENDED_GLOB) # define PATTERN_CHAR(c) \ ((c) == '@' || (c) == '*' || (c) == '+' || (c) == '?' || (c) == '!') #else # define PATTERN_CHAR(c) 0 #endif #define GLOB_CHAR(c) \ ((c) == '*' || (c) == '?' || (c) == '[' || (c) == ']' || (c) == '^') #define CTLESC '\001' #define CTLNUL '\177' #endif /* _SYNTAX_H_ */ /* test.h -- external interface to the conditional command code. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #ifndef _TEST_H_ #define _TEST_H_ #include "stdc.h" /* Values for the flags argument to binary_test */ #define TEST_PATMATCH 0x01 #define TEST_ARITHEXP 0x02 extern int test_eaccess __P((char *, int)); extern int test_unop __P((char *)); extern int test_binop __P((char *)); extern int unary_test __P((char *, char *)); extern int binary_test __P((char *, char *, char *, int)); extern int test_command __P((int, char **)); #endif /* _TEST_H_ */ /* trap.h -- data structures used in the trap mechanism. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_TRAP_H_) #define _TRAP_H_ #include "stdc.h" #if !defined (SIG_DFL) #include "bashtypes.h" #include #endif /* SIG_DFL */ #if !defined (NSIG) #define NSIG 64 #endif /* !NSIG */ #define NO_SIG -1 #define DEFAULT_SIG SIG_DFL #define IGNORE_SIG SIG_IGN /* Special shell trap names. */ #define DEBUG_TRAP NSIG #define ERROR_TRAP NSIG+1 #define EXIT_TRAP 0 /* system signals plus special bash traps */ #define BASH_NSIG NSIG+2 #define signal_object_p(x) (decode_signal (x) != NO_SIG) #define TRAP_STRING(s) \ (signal_is_trapped (s) && signal_is_ignored (s) == 0) ? trap_list[s] \ : (char *)NULL extern char *trap_list[]; /* Externally-visible functions declared in trap.c. */ extern void initialize_traps __P((void)); extern void run_pending_traps __P((void)); extern void maybe_set_sigchld_trap __P((char *)); extern void set_sigchld_trap __P((char *)); extern void set_debug_trap __P((char *)); extern void set_error_trap __P((char *)); extern void set_sigint_trap __P((char *)); extern void set_signal __P((int, char *)); extern void restore_default_signal __P((int)); extern void ignore_signal __P((int)); extern int run_exit_trap __P((void)); extern void run_trap_cleanup __P((int)); extern void run_debug_trap __P((void)); extern void run_error_trap __P((void)); extern void free_trap_strings __P((void)); extern void reset_signal_handlers __P((void)); extern void restore_original_signals __P((void)); extern char *signal_name __P((int)); extern int decode_signal __P((char *)); extern void run_interrupt_trap __P((void)); extern int maybe_call_trap_handler __P((int)); extern int signal_is_trapped __P((int)); extern int signal_is_ignored __P((int)); extern int signal_is_special __P((int)); extern void set_signal_ignored __P((int)); #endif /* _TRAP_H_ */ /* unwind_prot.h - Macros and functions for hacking unwind protection. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_UNWIND_PROT_H) #define _UNWIND_PROT_H /* Run a function without interrupts. */ extern void begin_unwind_frame __P((char *)); extern void discard_unwind_frame __P((char *)); extern void run_unwind_frame __P((char *)); extern void add_unwind_protect (); /* Not portable to arbitrary C99 hosts. */ extern void remove_unwind_protect __P((void)); extern void run_unwind_protects __P((void)); extern void clear_unwind_protect_list __P((int)); /* Define for people who like their code to look a certain way. */ #define end_unwind_frame() /* How to protect a variable. */ #define unwind_protect_var(X) unwind_protect_mem ((char *)&(X), sizeof (X)) extern void unwind_protect_mem __P((char *, int)); /* Backwards compatibility */ #define unwind_protect_int unwind_protect_var #define unwind_protect_short unwind_protect_var #define unwind_protect_string unwind_protect_var #define unwind_protect_pointer unwind_protect_var #define unwind_protect_jmp_buf unwind_protect_var #endif /* _UNWIND_PROT_H */ /* variables.h -- data structures for shell variables. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_VARIABLES_H_) #define _VARIABLES_H_ #include "stdc.h" #include "array.h" /* Shell variables and functions are stored in hash tables. */ #include "hashlib.h" #include "conftypes.h" /* What a shell variable looks like. */ typedef struct variable *DYNAMIC_FUNC (); typedef struct variable { char *name; /* Symbol that the user types. */ char *value; /* Value that is returned. */ char *exportstr; /* String for the environment. */ DYNAMIC_FUNC *dynamic_value; /* Function called to return a `dynamic' value for a variable, like $SECONDS or $RANDOM. */ DYNAMIC_FUNC *assign_func; /* Function called when this `special variable' is assigned a value in bind_variable. */ int attributes; /* export, readonly, array, invisible... */ int context; /* Which context this variable belongs to. */ struct variable *prev_context; /* Value from previous context or NULL. */ } SHELL_VAR; /* The various attributes that a given variable can have. */ #define att_exported 0x001 /* export to environment */ #define att_readonly 0x002 /* cannot change */ #define att_invisible 0x004 /* cannot see */ #define att_array 0x008 /* value is an array */ #define att_nounset 0x010 /* cannot unset */ #define att_function 0x020 /* value is a function */ #define att_integer 0x040 /* internal representation is int */ #define att_imported 0x080 /* came from environment */ #define att_local 0x100 /* variable is local to a function */ #define att_tempvar 0x200 /* variable came from the temp environment */ #define att_importstr 0x400 /* exportstr points into initial environment */ #define att_noassign 0x800 /* assignment not allowed */ #define exported_p(var) ((((var)->attributes) & (att_exported))) #define readonly_p(var) ((((var)->attributes) & (att_readonly))) #define invisible_p(var) ((((var)->attributes) & (att_invisible))) #define array_p(var) ((((var)->attributes) & (att_array))) #define non_unsettable_p(var) ((((var)->attributes) & (att_nounset))) #define function_p(var) ((((var)->attributes) & (att_function))) #define integer_p(var) ((((var)->attributes) & (att_integer))) #define imported_p(var) ((((var)->attributes) & (att_imported))) #define local_p(var) ((((var)->attributes) & (att_local))) #define tempvar_p(var) ((((var)->attributes) & (att_tempvar))) #define noassign_p(var) ((((var)->attributes) & (att_noassign))) #define value_cell(var) ((var)->value) #define function_cell(var) (COMMAND *)((var)->value) #define array_cell(var) ((ARRAY *)(var)->value) #define SETVARATTR(var, attr, undo) \ ((undo == 0) ? ((var)->attributes |= (attr)) \ : ((var)->attributes &= ~(attr))) #define VSETATTR(var, attr) ((var)->attributes |= (attr)) #define VUNSETATTR(var, attr) ((var)->attributes &= ~(attr)) #define VGETFLAGS(var) ((var)->attributes) #define VSETFLAGS(var, flags) ((var)->attributes = (flags)) #define VCLRFLAGS(var) ((var)->attributes = 0) /* Macros to perform various operations on `exportstr' member of a SHELL_VAR. */ #define CLEAR_EXPORTSTR(var) (var)->exportstr = (char *)NULL #define COPY_EXPORTSTR(var) ((var)->exportstr) ? savestring ((var)->exportstr) : (char *)NULL #define SET_EXPORTSTR(var, value) (var)->exportstr = (value) #define SAVE_EXPORTSTR(var, value) (var)->exportstr = (value) ? savestring (value) : (char *)NULL #define FREE_EXPORTSTR(var) \ do { \ if ((var)->exportstr) \ { \ if (((var)->attributes & att_importstr) == 0) \ free ((var)->exportstr); \ } \ } while (0) #if 0 #define CACHE_IMPORTSTR(var, value) \ do { \ (var)->exportstr = value; \ (var)->attributes |= att_importstr; \ } while (0) #else #define CACHE_IMPORTSTR(var, value) \ do { \ (var)->exportstr = savestring (value); \ } while (0) #endif #define INVALIDATE_EXPORTSTR(var) \ do { \ if ((var)->exportstr) \ { \ if (((var)->attributes & att_importstr) == 0) \ free ((var)->exportstr); \ (var)->exportstr = (char *)NULL; \ (var)->attributes &= ~att_importstr; \ } \ } while (0) /* Stuff for hacking variables. */ typedef int sh_var_map_func_t __P((SHELL_VAR *)); extern int variable_context; extern HASH_TABLE *shell_variables, *shell_functions; extern char *dollar_vars[]; extern char **export_env; extern char **non_unsettable_vars; extern void initialize_shell_variables __P((char **, int)); extern SHELL_VAR *set_if_not __P((char *, char *)); extern void sh_set_lines_and_columns __P((int, int)); extern void set_pwd __P((void)); extern void set_ppid __P((void)); extern void make_funcname_visible __P((int)); extern SHELL_VAR *var_lookup __P((const char *, HASH_TABLE *)); extern SHELL_VAR *find_function __P((const char *)); extern SHELL_VAR *find_variable __P((const char *)); extern SHELL_VAR *find_variable_internal __P((const char *, int)); extern SHELL_VAR *find_tempenv_variable __P((const char *)); extern SHELL_VAR *copy_variable __P((SHELL_VAR *)); extern SHELL_VAR *make_local_variable __P((const char *)); extern SHELL_VAR *bind_variable __P((const char *, char *)); extern SHELL_VAR *bind_function __P((const char *, COMMAND *)); extern SHELL_VAR **map_over __P((sh_var_map_func_t *, HASH_TABLE *)); extern SHELL_VAR **all_shell_variables __P((void)); extern SHELL_VAR **all_shell_functions __P((void)); extern SHELL_VAR **all_visible_variables __P((void)); extern SHELL_VAR **all_visible_functions __P((void)); extern SHELL_VAR **all_exported_variables __P((void)); #if defined (ARRAY_VARS) extern SHELL_VAR **all_array_variables __P((void)); #endif extern char **all_variables_matching_prefix __P((const char *)); extern char **make_var_array __P((HASH_TABLE *)); extern char **add_or_supercede_exported_var __P((char *, int)); extern char *get_string_value __P((const char *)); extern char *make_variable_value __P((SHELL_VAR *, char *)); extern SHELL_VAR *bind_variable_value __P((SHELL_VAR *, char *)); extern SHELL_VAR *bind_int_variable __P((char *, char *)); extern SHELL_VAR *bind_var_to_int __P((char *, long)); extern int assignment __P((const char *)); extern int variable_in_context __P((SHELL_VAR *)); extern int assign_in_env __P((const char *)); extern int unbind_variable __P((const char *)); extern int makunbound __P((const char *, HASH_TABLE *)); extern int kill_local_variable __P((const char *)); extern void delete_all_variables __P((HASH_TABLE *)); extern void adjust_shell_level __P((int)); extern void non_unsettable __P((char *)); extern void dispose_variable __P((SHELL_VAR *)); extern void dispose_used_env_vars __P((void)); extern void dispose_function_env __P((void)); extern void dispose_builtin_env __P((void)); extern void merge_temporary_env __P((void)); extern void merge_builtin_env __P((void)); extern void merge_function_env __P((void)); extern void kill_all_local_variables __P((void)); extern void set_var_read_only __P((char *)); extern void set_func_read_only __P((const char *)); extern void set_var_auto_export __P((char *)); extern void set_func_auto_export __P((const char *)); extern void sort_variables __P((SHELL_VAR **)); extern void maybe_make_export_env __P((void)); extern void update_export_env_inplace __P((char *, int, char *)); extern void put_command_name_into_env __P((char *)); extern void put_gnu_argv_flags_into_env __P((long, char *)); extern void print_var_list __P((SHELL_VAR **)); extern void print_func_list __P((SHELL_VAR **)); extern void print_assignment __P((SHELL_VAR *)); extern void print_var_value __P((SHELL_VAR *, int)); extern void print_var_function __P((SHELL_VAR *)); extern char *indirection_level_string __P((void)); #if defined (ARRAY_VARS) extern SHELL_VAR *make_new_array_variable __P((char *)); extern SHELL_VAR *make_local_array_variable __P((char *)); extern void set_pipestatus_array __P((int *)); #endif extern void set_pipestatus_from_exit __P((int)); /* The variable in NAME has just had its state changed. Check to see if it is one of the special ones where something special happens. */ extern void stupidly_hack_special_variables __P((char *)); extern int get_random_number __P((void)); /* The `special variable' functions that get called when a particular variable is set. */ extern void sv_path __P((char *)); extern void sv_mail __P((char *)); extern void sv_globignore __P((char *)); extern void sv_ignoreeof __P((char *)); extern void sv_strict_posix __P((char *)); extern void sv_optind __P((char *)); extern void sv_opterr __P((char *)); extern void sv_locale __P((char *)); #if defined (READLINE) extern void sv_terminal __P((char *)); extern void sv_hostfile __P((char *)); #endif #if defined (HAVE_TZSET) && defined (PROMPT_STRING_DECODE) extern void sv_tz __P((char *)); #endif #if defined (HISTORY) extern void sv_histsize __P((char *)); extern void sv_histignore __P((char *)); extern void sv_history_control __P((char *)); # if defined (BANG_HISTORY) extern void sv_histchars __P((char *)); # endif #endif /* HISTORY */ #endif /* !_VARIABLES_H_ */ /* xmalloc.h -- defines for the `x' memory allocation functions */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #if !defined (_XMALLOC_H_) #define _XMALLOC_H_ #include "stdc.h" #include "bashansi.h" /* Generic pointer type. */ #ifndef PTR_T #if defined (__STDC__) # define PTR_T void * #else # define PTR_T char * #endif #endif /* PTR_T */ /* Allocation functions in xmalloc.c */ extern PTR_T xmalloc __P((size_t)); extern PTR_T xrealloc __P((void *, size_t)); extern void xfree __P((void *)); #ifdef USING_BASH_MALLOC extern PTR_T sh_xmalloc __P((size_t, const char *, int)); extern PTR_T sh_xrealloc __P((void *, size_t, const char *, int)); extern void sh_xfree __P((void *, const char *, int)); #define xmalloc(x) sh_xmalloc((x), __FILE__, __LINE__) #define xrealloc(x, n) sh_xrealloc((x), (n), __FILE__, __LINE__) #define xfree(x) sh_xfree((x), __FILE__, __LINE__) #ifdef free #undef free #endif #define free(x) sh_xfree((x), __FILE__, __LINE__) #endif /* USING_BASH_MALLOC */ #endif /* _XMALLOC_H_ */ typedef union { WORD_DESC *word; /* the word that we read. */ int number; /* the number that we read. */ WORD_LIST *word_list; COMMAND *command; REDIRECT *redirect; ELEMENT element; PATTERN_LIST *pattern; } YYSTYPE; #define IF 257 #define THEN 258 #define ELSE 259 #define ELIF 260 #define FI 261 #define CASE 262 #define ESAC 263 #define FOR 264 #define SELECT 265 #define WHILE 266 #define UNTIL 267 #define DO 268 #define DONE 269 #define FUNCTION 270 #define COND_START 271 #define COND_END 272 #define COND_ERROR 273 #define IN 274 #define BANG 275 #define TIME 276 #define TIMEOPT 277 #define WORD 278 #define ASSIGNMENT_WORD 279 #define NUMBER 280 #define ARITH_CMD 281 #define ARITH_FOR_EXPRS 282 #define COND_CMD 283 #define AND_AND 284 #define OR_OR 285 #define GREATER_GREATER 286 #define LESS_LESS 287 #define LESS_AND 288 #define GREATER_AND 289 #define SEMI_SEMI 290 #define LESS_LESS_MINUS 291 #define AND_GREATER 292 #define LESS_GREATER 293 #define GREATER_BAR 294 #define yacc_EOF 295 extern YYSTYPE yylval; /* alias.c -- Not a full alias, but just the kind that we use in the shell. Csh style alias is somewhere else (`over there, in a box'). */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (ALIAS) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include #include "chartypes.h" #include "bashansi.h" #include "command.h" #include "general.h" #include "externs.h" #include "alias.h" #if defined (PROGRAMMABLE_COMPLETION) # include "pcomplete.h" #endif typedef int sh_alias_map_func_t __P((alias_t *)); static void free_alias_data __P((PTR_T)); static alias_t **map_over_aliases __P((sh_alias_map_func_t *)); static void sort_aliases __P((alias_t **)); static int qsort_alias_compare __P((alias_t **, alias_t **)); #if defined (READLINE) static int skipquotes __P((char *, int)); static int skipws __P((char *, int)); static int rd_token __P((char *, int)); #endif /* Non-zero means expand all words on the line. Otherwise, expand after first expansion if the expansion ends in a space. */ int alias_expand_all = 0; /* The list of aliases that we have. */ HASH_TABLE *aliases = (HASH_TABLE *)NULL; void initialize_aliases () { if (!aliases) aliases = make_hash_table (0); } /* Scan the list of aliases looking for one with NAME. Return NULL if the alias doesn't exist, else a pointer to the alias_t. */ alias_t * find_alias (name) char *name; { BUCKET_CONTENTS *al; if (aliases == 0) return ((alias_t *)NULL); al = find_hash_item (name, aliases); return (al ? (alias_t *)al->data : (alias_t *)NULL); } /* Return the value of the alias for NAME, or NULL if there is none. */ char * get_alias_value (name) char *name; { alias_t *alias; if (aliases == 0) return ((char *)NULL); alias = find_alias (name); return (alias ? alias->value : (char *)NULL); } /* Make a new alias from NAME and VALUE. If NAME can be found, then replace its value. */ void add_alias (name, value) char *name, *value; { BUCKET_CONTENTS *elt; alias_t *temp; int n; if (!aliases) { initialize_aliases (); temp = (alias_t *)NULL; } else temp = find_alias (name); if (temp) { free (temp->value); temp->value = savestring (value); n = value[strlen (value) - 1]; if (n == ' ' || n == '\t') temp->flags |= AL_EXPANDNEXT; } else { temp = (alias_t *)xmalloc (sizeof (alias_t)); temp->name = savestring (name); temp->value = savestring (value); temp->flags = 0; n = value[strlen (value) - 1]; if (n == ' ' || n == '\t') temp->flags |= AL_EXPANDNEXT; elt = add_hash_item (savestring (name), aliases); elt->data = (char *)temp; #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_aliases); #endif } } /* Delete a single alias structure. */ static void free_alias_data (data) PTR_T data; { register alias_t *a; a = (alias_t *)data; free (a->value); free (a->name); free (data); } /* Remove the alias with name NAME from the alias table. Returns the number of aliases left in the table, or -1 if the alias didn't exist. */ int remove_alias (name) char *name; { BUCKET_CONTENTS *elt; if (aliases == 0) return (-1); elt = remove_hash_item (name, aliases); if (elt) { free_alias_data (elt->data); free (elt->key); /* alias name */ free (elt); /* XXX */ #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_aliases); #endif return (aliases->nentries); } return (-1); } /* Delete all aliases. */ void delete_all_aliases () { if (aliases == 0) return; flush_hash_table (aliases, free_alias_data); dispose_hash_table (aliases); aliases = (HASH_TABLE *)NULL; #if defined (PROGRAMMABLE_COMPLETION) set_itemlist_dirty (&it_aliases); #endif } /* Return an array of aliases that satisfy the conditions tested by FUNCTION. If FUNCTION is NULL, return all aliases. */ static alias_t ** map_over_aliases (function) sh_alias_map_func_t *function; { register int i; register BUCKET_CONTENTS *tlist; alias_t *alias, **list; int list_index, list_size; list = (alias_t **)NULL; for (i = list_index = list_size = 0; i < aliases->nbuckets; i++) { tlist = get_hash_bucket (i, aliases); while (tlist) { alias = (alias_t *)tlist->data; if (!function || (*function) (alias)) { if (list_index + 1 >= list_size) { list_size += 20; list = (alias_t **)xrealloc (list, list_size * sizeof (alias_t *)); } list[list_index++] = alias; list[list_index] = (alias_t *)NULL; } tlist = tlist->next; } } return (list); } static void sort_aliases (array) alias_t **array; { qsort (array, array_len ((char **)array), sizeof (alias_t *), (QSFUNC *)qsort_alias_compare); } static int qsort_alias_compare (as1, as2) alias_t **as1, **as2; { int result; if ((result = (*as1)->name[0] - (*as2)->name[0]) == 0) result = strcmp ((*as1)->name, (*as2)->name); return (result); } /* Return a sorted list of all defined aliases */ alias_t ** all_aliases () { alias_t **list; if (!aliases) return ((alias_t **)NULL); list = map_over_aliases ((sh_alias_map_func_t *)NULL); if (list) sort_aliases (list); return (list); } char * alias_expand_word (s) char *s; { alias_t *r; r = find_alias (s); return (r ? savestring (r->value) : (char *)NULL); } /* Readline support functions -- expand all aliases in a line. */ #if defined (READLINE) /* Return non-zero if CHARACTER is a member of the class of characters that are self-delimiting in the shell (this really means that these characters delimit tokens). */ #define self_delimiting(character) (member ((character), " \t\n\r;|&()")) /* Return non-zero if CHARACTER is a member of the class of characters that delimit commands in the shell. */ #define command_separator(character) (member ((character), "\r\n;|&(")) /* If this is 1, we are checking the next token read for alias expansion because it is the first word in a command. */ static int command_word; /* This is for skipping quoted strings in alias expansions. */ #define quote_char(c) (((c) == '\'') || ((c) == '"')) /* Consume a quoted string from STRING, starting at string[START] (so string[START] is the opening quote character), and return the index of the closing quote character matching the opening quote character. This handles single matching pairs of unquoted quotes; it could afford to be a little smarter... This skips words between balanced pairs of quotes, words where the first character is quoted with a `\', and other backslash-escaped characters. */ static int skipquotes (string, start) char *string; int start; { register int i; int delimiter = string[start]; /* i starts at START + 1 because string[START] is the opening quote character. */ for (i = start + 1 ; string[i] ; i++) { if (string[i] == '\\') { i++; /* skip backslash-quoted quote characters, too */ continue; } if (string[i] == delimiter) return i; } return (i); } /* Skip the white space and any quoted characters in STRING, starting at START. Return the new index into STRING, after zero or more characters have been skipped. */ static int skipws (string, start) char *string; int start; { register int i; int pass_next, backslash_quoted_word; unsigned char peekc; /* skip quoted strings, in ' or ", and words in which a character is quoted with a `\'. */ i = backslash_quoted_word = pass_next = 0; /* Skip leading whitespace (or separator characters), and quoted words. But save it in the output. */ for (i = start; string[i]; i++) { if (pass_next) { pass_next = 0; continue; } if (whitespace (string[i])) { backslash_quoted_word = 0; /* we are no longer in a backslash-quoted word */ continue; } if (string[i] == '\\') { peekc = string[i+1]; if (ISLETTER (peekc)) backslash_quoted_word++; /* this is a backslash-quoted word */ else pass_next++; continue; } /* This only handles single pairs of non-escaped quotes. This overloads backslash_quoted_word to also mean that a word like ""f is being scanned, so that the quotes will inhibit any expansion of the word. */ if (quote_char(string[i])) { i = skipquotes (string, i); /* This could be a line that contains a single quote character, in which case skipquotes () terminates with string[i] == '\0' (the end of the string). Check for that here. */ if (string[i] == '\0') break; peekc = string[i + 1]; if (ISLETTER (peekc)) backslash_quoted_word++; continue; } /* If we're in the middle of some kind of quoted word, let it pass through. */ if (backslash_quoted_word) continue; /* If this character is a shell command separator, then set a hint for alias_expand that the next token is the first word in a command. */ if (command_separator (string[i])) { command_word++; continue; } break; } return (i); } /* Characters that may appear in a token. Basically, anything except white space and a token separator. */ #define token_char(c) (!((whitespace (string[i]) || self_delimiting (string[i])))) /* Read from START in STRING until the next separator character, and return the index of that separator. Skip backslash-quoted characters. Call skipquotes () for quoted strings in the middle or at the end of tokens, so all characters show up (e.g. foo'' and foo""bar) */ static int rd_token (string, start) char *string; int start; { register int i; /* From here to next separator character is a token. */ for (i = start; string[i] && token_char (string[i]); i++) { if (string[i] == '\\') { i++; /* skip backslash-escaped character */ continue; } /* If this character is a quote character, we want to call skipquotes to get the whole quoted portion as part of this word. That word will not generally match an alias, even if te unquoted word would have. The presence of the quotes in the token serves then to inhibit expansion. */ if (quote_char (string[i])) { i = skipquotes (string, i); /* This could be a line that contains a single quote character, in which case skipquotes () terminates with string[i] == '\0' (the end of the string). Check for that here. */ if (string[i] == '\0') break; /* Now string[i] is the matching quote character, and the quoted portion of the token has been scanned. */ continue; } } return (i); } /* Return a new line, with any aliases substituted. */ char * alias_expand (string) char *string; { register int i, j, start; char *line, *token; int line_len, tl, real_start, expand_next, expand_this_token; alias_t *alias; line_len = strlen (string) + 1; line = (char *)xmalloc (line_len); token = (char *)xmalloc (line_len); line[0] = i = 0; expand_next = 0; command_word = 1; /* initialized to expand the first word on the line */ /* Each time through the loop we find the next word in line. If it has an alias, substitute the alias value. If the value ends in ` ', then try again with the next word. Else, if there is no value, or if the value does not end in space, we are done. */ for (;;) { token[0] = 0; start = i; /* Skip white space and quoted characters */ i = skipws (string, start); if (start == i && string[i] == '\0') { free (token); return (line); } /* copy the just-skipped characters into the output string, expanding it if there is not enough room. */ j = strlen (line); tl = i - start; /* number of characters just skipped */ RESIZE_MALLOCED_BUFFER (line, j, (tl + 1), line_len, (tl + 50)); strncpy (line + j, string + start, tl); line[j + tl] = '\0'; real_start = i; command_word = command_word || (command_separator (string[i])); expand_this_token = (command_word || expand_next); expand_next = 0; /* Read the next token, and copy it into TOKEN. */ start = i; i = rd_token (string, start); tl = i - start; /* token length */ /* If tl == 0, but we're not at the end of the string, then we have a single-character token, probably a delimiter */ if (tl == 0 && string[i] != '\0') { tl = 1; i++; /* move past it */ } strncpy (token, string + start, tl); token [tl] = '\0'; /* If there is a backslash-escaped character quoted in TOKEN, then we don't do alias expansion. This should check for all other quoting characters, too. */ if (strchr (token, '\\')) expand_this_token = 0; /* If we should be expanding here, if we are expanding all words, or if we are in a location in the string where an expansion is supposed to take place, see if this word has a substitution. If it does, then do the expansion. Note that we defer the alias value lookup until we are sure we are expanding this token. */ if ((token[0]) && (expand_this_token || alias_expand_all) && (alias = find_alias (token))) { char *v; int vlen, llen; v = alias->value; vlen = strlen (v); llen = strlen (line); /* +3 because we possibly add one more character below. */ RESIZE_MALLOCED_BUFFER (line, llen, (vlen + 3), line_len, (vlen + 50)); strcpy (line + llen, v); if ((expand_this_token && vlen && whitespace (v[vlen - 1])) || alias_expand_all) expand_next = 1; } else { int llen, tlen; llen = strlen (line); tlen = i - real_start; /* tlen == strlen(token) */ RESIZE_MALLOCED_BUFFER (line, llen, (tlen + 1), line_len, (llen + tlen + 50)); strncpy (line + llen, string + real_start, tlen); line[llen + tlen] = '\0'; } command_word = 0; } } #endif /* READLINE */ #endif /* ALIAS */ /* * array.c - functions to create, destroy, access, and manipulate arrays * of strings. * * Arrays are sparse doubly-linked lists. An element's index is stored * with it. * * Chet Ramey * chet@ins.cwru.edu */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (ARRAY_VARS) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include #include "bashansi.h" #include "shell.h" #include "array.h" #include "builtins/common.h" #define ADD_BEFORE(ae, new) \ do { \ ae->prev->next = new; \ new->prev = ae->prev; \ ae->prev = new; \ new->next = ae; \ } while(0) /* * Allocate and return a new array element with index INDEX and value * VALUE. */ ARRAY_ELEMENT * new_array_element(indx, value) arrayind_t indx; char *value; { ARRAY_ELEMENT *r; r = (ARRAY_ELEMENT *)xmalloc(sizeof(ARRAY_ELEMENT)); r->ind = indx; r->value = value ? savestring(value) : (char *)NULL; r->next = r->prev = (ARRAY_ELEMENT *) NULL; return(r); } void destroy_array_element(ae) ARRAY_ELEMENT *ae; { FREE(ae->value); free(ae); } ARRAY * new_array() { ARRAY *r; ARRAY_ELEMENT *head; r =(ARRAY *)xmalloc(sizeof(ARRAY)); r->type = array_indexed; r->max_index = r->max_size = -1; r->num_elements = 0; head = new_array_element(-1, (char *)NULL); /* dummy head */ head->prev = head->next = head; r->head = head; return(r); } void empty_array (a) ARRAY *a; { register ARRAY_ELEMENT *r, *r1; if (a == 0) return; for (r = element_forw(a->head); r != a->head; ) { r1 = element_forw(r); destroy_array_element(r); r = r1; } a->head->next = a->head->prev = a->head; a->max_index = a->max_size = -1; a->num_elements = a->max_size = 0; } void dispose_array(a) ARRAY *a; { if (a == 0) return; empty_array (a); destroy_array_element(a->head); free(a); } ARRAY * dup_array(a) ARRAY *a; { ARRAY *a1; ARRAY_ELEMENT *ae, *new; if (!a) return((ARRAY *) NULL); a1 = new_array(); a1->type = a->type; a1->max_index = a->max_index; a1->num_elements = a->num_elements; a1->max_size = a->max_size; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { new = new_array_element(element_index(ae), element_value(ae)); ADD_BEFORE(a1->head, new); } return(a1); } #ifdef INCLUDE_UNUSED /* * Make and return a new array composed of the elements in array A from * S to E, inclusive. */ ARRAY * dup_array_subrange(array, s, e) ARRAY *array; ARRAY_ELEMENT *s, *e; { ARRAY *a; ARRAY_ELEMENT *p, *n; arrayind_t i; a = new_array (); a->type = array->type; for (p = s, i = 0; p != e; p = element_forw(p), i++) { n = new_array_element (i, element_value(p)); ADD_BEFORE(a->head, n); } a->num_elements = a->max_index = i; return a; } #endif #ifdef INCLUDE_UNUSED ARRAY_ELEMENT * copy_array_element(ae) ARRAY_ELEMENT *ae; { return(ae ? new_array_element(element_index(ae), element_value(ae)) : (ARRAY_ELEMENT *) NULL); } #endif /* * Add a new element with index I and value V to array A (a[i] = v). */ int array_add_element(a, i, v) ARRAY *a; arrayind_t i; char *v; { register ARRAY_ELEMENT *new, *ae; if (!a) return(-1); new = new_array_element(i, v); if (i > array_max_index(a)) { /* * Hook onto the end. This also works for an empty array. * Fast path for the common case of allocating arrays * sequentially. */ ADD_BEFORE(a->head, new); a->max_index = i; a->num_elements++; return(0); } /* * Otherwise we search for the spot to insert it. */ for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { if (element_index(ae) == i) { /* * Replacing an existing element. */ destroy_array_element(new); free(element_value(ae)); ae->value = savestring(v); return(0); } else if (element_index(ae) > i) { ADD_BEFORE(ae, new); a->num_elements++; return(0); } } return (-1); /* problem */ } /* * Delete the element with index I from array A and return it so the * caller can dispose of it. */ ARRAY_ELEMENT * array_delete_element(a, i) ARRAY *a; arrayind_t i; { register ARRAY_ELEMENT *ae; if (!a || array_empty(a)) return((ARRAY_ELEMENT *) NULL); for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) if (element_index(ae) == i) { ae->next->prev = ae->prev; ae->prev->next = ae->next; a->num_elements--; if (i == array_max_index(a)) a->max_index = element_index(ae->prev); return(ae); } return((ARRAY_ELEMENT *) NULL); } /* * Return the value of a[i]. */ char * array_reference(a, i) ARRAY *a; arrayind_t i; { register ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return((char *) NULL); for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) if (element_index(ae) == i) return(element_value(ae)); return((char *) NULL); } #ifdef TEST_ARRAY /* * Walk the array, calling FUNC once for each element, with the array * element as the argument. */ void array_walk(a, func) ARRAY *a; sh_ae_map_func_t *func; { register ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) (*func)(ae); } #endif /* * Return a string that is the concatenation of all the elements in A, * separated by SEP. */ static char * array_to_string_internal (start, end, sep, quoted) ARRAY_ELEMENT *start, *end; char *sep; int quoted; { char *result, *t; ARRAY_ELEMENT *ae; int slen, rsize, rlen, reg; if (start == end) /* XXX - should not happen */ return ((char *)NULL); slen = strlen(sep); result = NULL; for (rsize = rlen = 0, ae = start; ae != end; ae = element_forw(ae)) { if (rsize == 0) result = (char *)xmalloc (rsize = 64); if (element_value(ae)) { t = quoted ? quote_string(element_value(ae)) : element_value(ae); reg = strlen(t); RESIZE_MALLOCED_BUFFER (result, rlen, (reg + slen + 2), rsize, rsize); strcpy(result + rlen, t); rlen += reg; if (quoted && t) free(t); /* * Add a separator only after non-null elements. */ if (element_forw(ae) != end) { strcpy(result + rlen, sep); rlen += slen; } } } if (result) result[rlen] = '\0'; /* XXX */ return(result); } char * array_to_string (a, sep, quoted) ARRAY *a; char *sep; int quoted; { if (a == 0) return((char *)NULL); if (array_empty(a)) return(savestring("")); return (array_to_string_internal (element_forw(a->head), a->head, sep, quoted)); } char * array_to_assignment_string (a) ARRAY *a; { char *result, *indstr, *valstr; ARRAY_ELEMENT *ae; int rsize, rlen, elen; if (a == 0 || array_empty (a)) return((char *)NULL); result = (char *)xmalloc (rsize = 128); result[0] = '('; rlen = 1; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { indstr = itos (element_index(ae)); valstr = element_value (ae) ? sh_double_quote (element_value(ae)) : (char *)NULL; elen = STRLEN (indstr) + 8 + STRLEN (valstr); RESIZE_MALLOCED_BUFFER (result, rlen, (elen + 1), rsize, rsize); result[rlen++] = '['; strcpy (result + rlen, indstr); rlen += STRLEN (indstr); result[rlen++] = ']'; result[rlen++] = '='; if (valstr) { strcpy (result + rlen, valstr); rlen += STRLEN (valstr); } if (element_forw(ae) != a->head) result[rlen++] = ' '; FREE (indstr); FREE (valstr); } RESIZE_MALLOCED_BUFFER (result, rlen, 1, rsize, 8); result[rlen++] = ')'; result[rlen] = '\0'; return(result); } char * quoted_array_assignment_string (a) ARRAY *a; { char *vstr, *sv; sv = array_to_assignment_string (a); if (sv == 0) return ((char *)NULL); vstr = sh_single_quote (sv); free (sv); return (vstr); } #if 0 /* Determine if s2 occurs in s1. If so, return a pointer to the match in s1. The compare is case sensitive. */ static char * sindex (s1, s2) register char *s1, *s2; { register int i, l, len; for (i = 0, l = strlen(s2), len = strlen(s1); (len - i) >= l; i++) if (strncmp (s1 + i, s2, l) == 0) return (s1 + i); return ((char *)NULL); } #endif #if defined (INCLUDE_UNUSED) || defined (TEST_ARRAY) /* * Return an array consisting of elements in S, separated by SEP */ ARRAY * string_to_array(s, sep) char *s, *sep; { ARRAY *a; WORD_LIST *w; if (s == 0) return((ARRAY *)NULL); w = list_string (s, sep, 0); if (w == 0) return((ARRAY *)NULL); a = word_list_to_array (w); return (a); } #endif /* Convenience routines for the shell to translate to and from the form used by the rest of the code. */ WORD_LIST * array_to_word_list(a) ARRAY *a; { WORD_LIST *list; ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return((WORD_LIST *)NULL); list = (WORD_LIST *)NULL; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) list = make_word_list (make_bare_word(element_value(ae)), list); return (REVERSE_LIST(list, WORD_LIST *)); } char ** array_to_argv (a) ARRAY *a; { char **ret, *t; int i; ARRAY_ELEMENT *ae; if (a == 0 || array_empty(a)) return ((char **)NULL); ret = alloc_array (array_num_elements (a) + 1); i = 0; for (ae = element_forw(a->head); ae != a->head; ae = element_forw(ae)) { t = element_value (ae); ret[i++] = t ? savestring (t) : (char *)NULL; } ret[i] = (char *)NULL; return (ret); } ARRAY * assign_word_list (array, list) ARRAY *array; WORD_LIST *list; { register WORD_LIST *l; register arrayind_t i; for (l = list, i = 0; l; l = l->next, i++) array_add_element(array, i, l->word->word); return array; } ARRAY * word_list_to_array (list) WORD_LIST *list; { ARRAY *a; if (list == 0) return((ARRAY *)NULL); a = new_array(); return (assign_word_list (a, list)); } ARRAY * array_quote(array) ARRAY *array; { ARRAY_ELEMENT *a; char *t; if (array == 0 || array->head == 0 || array_empty (array)) return (ARRAY *)NULL; for (a = element_forw(array->head); a != array->head; a = element_forw(a)) { t = quote_string (a->value); FREE(a->value); a->value = t; } return array; } char * array_subrange (a, start, end, quoted) ARRAY *a; arrayind_t start, end; int quoted; { ARRAY_ELEMENT *h, *p; arrayind_t i; p = array_head (a); if (p == 0 || array_empty (a) || start > array_num_elements (a)) return ((char *)NULL); for (i = 0, p = element_forw(p); p != a->head && i < start; i++, p = element_forw(p)) ; if (p == a->head) return ((char *)NULL); for (h = p; p != a->head && i < end; i++, p = element_forw(p)) ; return (array_to_string_internal (h, p, " ", quoted)); } char * array_pat_subst (a, pat, rep, mflags) ARRAY *a; char *pat, *rep; int mflags; { ARRAY *a2; ARRAY_ELEMENT *e; char *t; if (array_head (a) == 0 || array_empty (a)) return ((char *)NULL); a2 = dup_array (a); for (e = element_forw(a2->head); e != a2->head; e = element_forw(e)) { t = pat_subst(element_value(e), pat, rep, mflags); FREE(element_value(e)); e->value = t; } if (mflags & MATCH_QUOTED) array_quote (a2); t = array_to_string (a2, " ", 0); dispose_array (a2); return t; } #if defined (TEST_ARRAY) print_element(ae) ARRAY_ELEMENT *ae; { printf("array[%ld] = %s\n", element_index(ae), element_value(ae)); } print_array(a) ARRAY *a; { printf("\n"); array_walk(a, print_element); } main() { ARRAY *a, *new_a, *copy_of_a; ARRAY_ELEMENT *ae; char *s; a = new_array(); array_add_element(a, 1, "one"); array_add_element(a, 7, "seven"); array_add_element(a, 4, "four"); array_add_element(a, 1029, "one thousand twenty-nine"); array_add_element(a, 12, "twelve"); array_add_element(a, 42, "forty-two"); print_array(a); s = array_to_string (a, " ", 0); printf("s = %s\n", s); copy_of_a = string_to_array(s, " "); printf("copy_of_a:"); print_array(copy_of_a); dispose_array(copy_of_a); printf("\n"); free(s); ae = array_delete_element(a, 4); destroy_array_element(ae); ae = array_delete_element(a, 1029); destroy_array_element(ae); array_add_element(a, 16, "sixteen"); print_array(a); s = array_to_string (a, " ", 0); printf("s = %s\n", s); copy_of_a = string_to_array(s, " "); printf("copy_of_a:"); print_array(copy_of_a); dispose_array(copy_of_a); printf("\n"); free(s); array_add_element(a, 2, "two"); array_add_element(a, 1029, "new one thousand twenty-nine"); array_add_element(a, 0, "zero"); array_add_element(a, 134, ""); print_array(a); s = array_to_string (a, ":", 0); printf("s = %s\n", s); copy_of_a = string_to_array(s, ":"); printf("copy_of_a:"); print_array(copy_of_a); dispose_array(copy_of_a); printf("\n"); free(s); new_a = copy_array(a); print_array(new_a); s = array_to_string (new_a, ":", 0); printf("s = %s\n", s); copy_of_a = string_to_array(s, ":", 0); printf("copy_of_a:"); print_array(copy_of_a); dispose_array(copy_of_a); printf("\n"); free(s); dispose_array(a); dispose_array(new_a); } #endif /* TEST_ARRAY */ #endif /* ARRAY_VARS */ /* arrayfunc.c -- High-level array functions used by other parts of the shell. */ /* Copyright (C) 2001 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (ARRAY_VARS) #if defined (HAVE_UNISTD_H) # include #endif #include #include "shell.h" #include "builtins/common.h" extern char *this_command_name; extern int last_command_exit_value; static void quote_array_assignment_chars __P((WORD_LIST *)); static char *array_value_internal __P((char *, int, int)); /* **************************************************************** */ /* */ /* Functions to manipulate array variables and perform assignments */ /* */ /* **************************************************************** */ /* Convert a shell variable to an array variable. The original value is saved as array[0]. */ SHELL_VAR * convert_var_to_array (var) SHELL_VAR *var; { char *oldval; ARRAY *array; oldval = value_cell (var); array = new_array (); array_add_element (array, 0, oldval); FREE (value_cell (var)); var->value = (char *)array; INVALIDATE_EXPORTSTR (var); VSETATTR (var, att_array); VUNSETATTR (var, att_invisible); return var; } /* Perform an array assignment name[ind]=value. If NAME already exists and is not an array, and IND is 0, perform name=value instead. If NAME exists and is not an array, and IND is not 0, convert it into an array with the existing value as name[0]. If NAME does not exist, just create an array variable, no matter what IND's value may be. */ SHELL_VAR * bind_array_variable (name, ind, value) char *name; arrayind_t ind; char *value; { SHELL_VAR *entry; char *newval; entry = var_lookup (name, shell_variables); if (entry == (SHELL_VAR *) 0) entry = make_new_array_variable (name); else if (readonly_p (entry) || noassign_p (entry)) { if (readonly_p (entry)) report_error ("%s: readonly variable", name); return (entry); } else if (array_p (entry) == 0) entry = convert_var_to_array (entry); /* ENTRY is an array variable, and ARRAY points to the value. */ newval = make_variable_value (entry, value); if (entry->assign_func) (*entry->assign_func) (entry, ind, newval); else array_add_element (array_cell (entry), ind, newval); FREE (newval); return (entry); } /* Parse NAME, a lhs of an assignment statement of the form v[s], and assign VALUE to that array element by calling bind_array_variable(). */ SHELL_VAR * assign_array_element (name, value) char *name, *value; { char *sub, *vname; arrayind_t ind; int sublen; SHELL_VAR *entry; vname = array_variable_name (name, &sub, &sublen); if (vname == 0) return ((SHELL_VAR *)NULL); if ((ALL_ELEMENT_SUB (sub[0]) && sub[1] == ']') || (sublen <= 1)) { free (vname); report_error ("%s: bad array subscript", name); return ((SHELL_VAR *)NULL); } ind = array_expand_index (sub, sublen); if (ind < 0) { free (vname); report_error ("%s: bad array subscript", name); return ((SHELL_VAR *)NULL); } entry = bind_array_variable (vname, ind, value); free (vname); return (entry); } /* Find the array variable corresponding to NAME. If there is no variable, create a new array variable. If the variable exists but is not an array, convert it to an indexed array. If CHECK_FLAGS is non-zero, an existing variable is checked for the readonly or noassign attribute in preparation for assignment (e.g., by the `read' builtin). */ SHELL_VAR * find_or_make_array_variable (name, check_flags) char *name; int check_flags; { SHELL_VAR *var; var = find_variable (name); if (var == 0) var = make_new_array_variable (name); else if (check_flags && (readonly_p (var) || noassign_p (var))) { if (readonly_p (var)) report_error ("%s: readonly variable", name); return ((SHELL_VAR *)NULL); } else if (array_p (var) == 0) var = convert_var_to_array (var); return (var); } /* Perform a compound assignment statement for array NAME, where VALUE is the text between the parens: NAME=( VALUE ) */ SHELL_VAR * assign_array_from_string (name, value) char *name, *value; { SHELL_VAR *var; var = find_or_make_array_variable (name, 1); if (var == 0) return ((SHELL_VAR *)NULL); return (assign_array_var_from_string (var, value)); } /* Sequentially assign the indices of indexed array variable VAR from the words in LIST. */ SHELL_VAR * assign_array_var_from_word_list (var, list) SHELL_VAR *var; WORD_LIST *list; { register arrayind_t i; register WORD_LIST *l; ARRAY *a; for (a = array_cell (var), l = list, i = 0; l; l = l->next, i++) if (var->assign_func) (*var->assign_func) (var, i, l->word->word); else array_add_element (a, i, l->word->word); return var; } /* Perform a compound array assignment: VAR->name=( VALUE ). The VALUE has already had the parentheses stripped. */ SHELL_VAR * assign_array_var_from_string (var, value) SHELL_VAR *var; char *value; { ARRAY *a; WORD_LIST *list, *nlist; char *w, *val, *nval; int ni, len; arrayind_t ind, last_ind; if (value == 0) return var; /* If this is called from declare_builtin, value[0] == '(' and strchr(value, ')') != 0. In this case, we need to extract the value from between the parens before going on. */ if (*value == '(') /*)*/ { ni = 1; val = extract_array_assignment_list (value, &ni); if (val == 0) return var; } else val = value; /* Expand the value string into a list of words, performing all the shell expansions including pathname generation and word splitting. */ /* First we split the string on whitespace, using the shell parser (ksh93 seems to do this). */ list = parse_string_to_word_list (val, "array assign"); /* If we're using [subscript]=value, we need to quote each [ and ] to prevent unwanted filename expansion. */ if (list) quote_array_assignment_chars (list); /* Now that we've split it, perform the shell expansions on each word in the list. */ nlist = list ? expand_words_no_vars (list) : (WORD_LIST *)NULL; dispose_words (list); if (val != value) free (val); a = array_cell (var); /* Now that we are ready to assign values to the array, kill the existing value. */ if (a) empty_array (a); for (last_ind = 0, list = nlist; list; list = list->next) { w = list->word->word; /* We have a word of the form [ind]=value */ if (w[0] == '[') { len = skipsubscript (w, 0); if (w[len] != ']' || w[len+1] != '=') { nval = make_variable_value (var, w); if (var->assign_func) (*var->assign_func) (var, last_ind, nval); else array_add_element (a, last_ind, nval); FREE (nval); last_ind++; continue; } if (len == 1) { report_error ("%s: bad array subscript", w); continue; } if (ALL_ELEMENT_SUB (w[1]) && len == 2) { report_error ("%s: cannot assign to non-numeric index", w); continue; } ind = array_expand_index (w + 1, len); if (ind < 0) { report_error ("%s: bad array subscript", w); continue; } last_ind = ind; val = w + len + 2; } else /* No [ind]=value, just a stray `=' */ { ind = last_ind; val = w; } if (integer_p (var)) this_command_name = (char *)NULL; /* no command name for errors */ nval = make_variable_value (var, val); if (var->assign_func) (*var->assign_func) (var, ind, nval); else array_add_element (a, ind, nval); FREE (nval); last_ind++; } dispose_words (nlist); return (var); } /* For each word in a compound array assignment, if the word looks like [ind]=value, quote the `[' and `]' before the `=' to protect them from unwanted filename expansion. */ static void quote_array_assignment_chars (list) WORD_LIST *list; { char *s, *t, *nword; int saw_eq; WORD_LIST *l; for (l = list; l; l = l->next) { if (l->word == 0 || l->word->word == 0 || l->word->word[0] == '\0') continue; /* should not happen, but just in case... */ /* Don't bother if it doesn't look like [ind]=value */ if (l->word->word[0] != '[' || strchr (l->word->word, '=') == 0) /* ] */ continue; s = nword = (char *)xmalloc (strlen (l->word->word) * 2 + 1); saw_eq = 0; for (t = l->word->word; *t; ) { if (*t == '=') saw_eq = 1; if (saw_eq == 0 && (*t == '[' || *t == ']')) *s++ = '\\'; *s++ = *t++; } *s = '\0'; free (l->word->word); l->word->word = nword; } } /* This function assumes s[i] == '['; returns with s[ret] == ']' if an array subscript is correctly parsed. */ int skipsubscript (s, i) const char *s; int i; { int count, c; for (count = 1; count && (c = s[++i]); ) { if (c == '[') count++; else if (c == ']') count--; } return i; } /* This function is called with SUB pointing to just after the beginning `[' of an array subscript and removes the array element to which SUB expands from array VAR. A subscript of `*' or `@' unsets the array. */ int unbind_array_element (var, sub) SHELL_VAR *var; char *sub; { int len; arrayind_t ind; ARRAY_ELEMENT *ae; len = skipsubscript (sub, 0); if (sub[len] != ']' || len == 0) { builtin_error ("%s[%s: bad array subscript", var->name, sub); return -1; } sub[len] = '\0'; if (ALL_ELEMENT_SUB (sub[0]) && sub[1] == 0) { makunbound (var->name, shell_variables); return (0); } ind = array_expand_index (sub, len+1); if (ind < 0) { builtin_error ("[%s]: bad array subscript", sub); return -1; } ae = array_delete_element (array_cell (var), ind); if (ae) destroy_array_element (ae); return 0; } /* Format and output an array assignment in compound form VAR=(VALUES), suitable for re-use as input. */ void print_array_assignment (var, quoted) SHELL_VAR *var; int quoted; { char *vstr; if (quoted) vstr = quoted_array_assignment_string (array_cell (var)); else vstr = array_to_assignment_string (array_cell (var)); if (vstr == 0) printf ("%s=%s\n", var->name, quoted ? "'()'" : "()"); else { printf ("%s=%s\n", var->name, vstr); free (vstr); } } /***********************************************************************/ /* */ /* Utility functions to manage arrays and their contents for expansion */ /* */ /***********************************************************************/ /* Return 1 if NAME is a properly-formed array reference v[sub]. */ int valid_array_reference (name) char *name; { char *t; int r, len; t = strchr (name, '['); /* ] */ if (t) { *t = '\0'; r = legal_identifier (name); *t = '['; if (r == 0) return 0; /* Check for a properly-terminated non-blank subscript. */ len = skipsubscript (t, 0); if (t[len] != ']' || len == 1) return 0; for (r = 1; r < len; r++) if (whitespace (t[r]) == 0) return 1; return 0; } return 0; } /* Expand the array index beginning at S and extending LEN characters. */ arrayind_t array_expand_index (s, len) char *s; int len; { char *exp, *t; int expok; arrayind_t val; exp = (char *)xmalloc (len); strncpy (exp, s, len - 1); exp[len - 1] = '\0'; t = expand_string_to_string (exp, 0); this_command_name = (char *)NULL; val = evalexp (t, &expok); free (t); free (exp); if (expok == 0) { last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (DISCARD); } return val; } /* Return the name of the variable specified by S without any subscript. If SUBP is non-null, return a pointer to the start of the subscript in *SUBP. If LENP is non-null, the length of the subscript is returned in *LENP. This returns newly-allocated memory. */ char * array_variable_name (s, subp, lenp) char *s, **subp; int *lenp; { char *t, *ret; int ind, ni; t = strchr (s, '['); if (t == 0) return ((char *)NULL); ind = t - s; ni = skipsubscript (s, ind); if (ni <= ind + 1 || s[ni] != ']') { report_error ("%s: bad array subscript", s); return ((char *)NULL); } *t = '\0'; ret = savestring (s); *t++ = '['; /* ] */ if (subp) *subp = t; if (lenp) *lenp = ni - ind; return ret; } /* Return the variable specified by S without any subscript. If SUBP is non-null, return a pointer to the start of the subscript in *SUBP. If LENP is non-null, the length of the subscript is returned in *LENP. */ SHELL_VAR * array_variable_part (s, subp, lenp) char *s, **subp; int *lenp; { char *t; SHELL_VAR *var; t = array_variable_name (s, subp, lenp); if (t == 0) return ((SHELL_VAR *)NULL); var = find_variable (t); free (t); return var; } /* Return a string containing the elements in the array and subscript described by S. If the subscript is * or @, obeys quoting rules akin to the expansion of $* and $@ including double quoting. */ static char * array_value_internal (s, quoted, allow_all) char *s; int quoted, allow_all; { int len; arrayind_t ind; char *retval, *t, *temp; WORD_LIST *l; SHELL_VAR *var; var = array_variable_part (s, &t, &len); if (var == 0) return (char *)NULL; /* [ */ if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']') { if (allow_all == 0) { report_error ("%s: bad array subscript", s); return ((char *)NULL); } else if (array_p (var) == 0) { l = (WORD_LIST *)NULL; l = add_string_to_list (value_cell (var), l); } else { l = array_to_word_list (array_cell (var)); if (l == (WORD_LIST *)NULL) return ((char *) NULL); } if (t[0] == '*' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))) { temp = string_list_dollar_star (l); retval = quote_string (temp); free (temp); } else /* ${name[@]} or unquoted ${name[*]} */ retval = string_list_dollar_at (l, quoted); dispose_words (l); } else { ind = array_expand_index (t, len); if (ind < 0) { report_error ("%s: bad array subscript", var->name); return ((char *)NULL); } if (array_p (var) == 0) return (ind == 0 ? savestring (value_cell (var)) : (char *)NULL); retval = array_reference (array_cell (var), ind); if (retval) retval = quote_escapes (retval); } return retval; } /* Return a string containing the elements described by the array and subscript contained in S, obeying quoting for subscripts * and @. */ char * array_value (s, quoted) char *s; int quoted; { return (array_value_internal (s, quoted, 1)); } /* Return the value of the array indexing expression S as a single string. If ALLOW_ALL is 0, do not allow `@' and `*' subscripts. This is used by other parts of the shell such as the arithmetic expression evaluator in expr.c. */ char * get_array_value (s, allow_all) char *s; int allow_all; { return (array_value_internal (s, 0, allow_all)); } #endif /* ARRAY_VARS */ /* bashhist.c -- bash interface to the GNU history library. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (HISTORY) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "bashtypes.h" #include #include #include "bashansi.h" #include "posixstat.h" #include "filecntl.h" #include "shell.h" #include "flags.h" #include "input.h" #include "parser.h" /* for the struct dstack stuff. */ #include "pathexp.h" /* for the struct ignorevar stuff */ #include "bashhist.h" /* matching prototypes and declarations */ #include "builtins/common.h" #include #include #include #if defined (READLINE) # include "bashline.h" #endif #if !defined (errno) extern int errno; #endif static int histignore_item_func __P((struct ign *)); static struct ignorevar histignore = { "HISTIGNORE", (struct ign *)0, 0, (char *)0, (sh_iv_item_func_t *)histignore_item_func, }; #define HIGN_EXPAND 0x01 /* Declarations of bash history variables. */ /* Non-zero means to remember lines typed to the shell on the history list. This is different than the user-controlled behaviour; this becomes zero when we read lines from a file, for example. */ int remember_on_history = 1; /* The number of lines that Bash has added to this history session. */ int history_lines_this_session; /* The number of lines that Bash has read from the history file. */ int history_lines_in_file; #if defined (BANG_HISTORY) /* Non-zero means do no history expansion on this line, regardless of what history_expansion says. */ int history_expansion_inhibited; #endif /* By default, every line is saved in the history individually. I.e., if the user enters: bash$ for i in a b c > do > echo $i > done Each line will be individually saved in the history. bash$ history 10 for i in a b c 11 do 12 echo $i 13 done 14 history If the variable command_oriented_history is set, multiple lines which form one command will be saved as one history entry. bash$ for i in a b c > do > echo $i > done bash$ history 10 for i in a b c do echo $i done 11 history The user can then recall the whole command all at once instead of just being able to recall one line at a time. */ int command_oriented_history = 1; /* Non-zero means to store newlines in the history list when using command_oriented_history rather than trying to use semicolons. */ int literal_history; /* Non-zero means to append the history to the history file at shell exit, even if the history has been stifled. */ int force_append_history; /* A nit for picking at history saving. Value of 0 means save all lines parsed by the shell on the history. Value of 1 means save all lines that do not start with a space. Value of 2 means save all lines that do not match the last line saved. */ int history_control; /* Set to 1 if the last command was added to the history list successfully as a separate history entry; set to 0 if the line was ignored or added to a previous entry as part of command-oriented-history processing. */ int hist_last_line_added; #if defined (READLINE) /* If non-zero, and readline is being used, the user is offered the chance to re-edit a failed history expansion. */ int history_reediting; /* If non-zero, and readline is being used, don't directly execute a line with history substitution. Reload it into the editing buffer instead and let the user further edit and confirm with a newline. */ int hist_verify; #endif /* READLINE */ /* Non-zero means to not save function definitions in the history list. */ int dont_save_function_defs; /* Variables declared in other files used here. */ extern int current_command_line_count; extern struct dstack dstack; static int bash_history_inhibit_expansion __P((char *, int)); #if defined (READLINE) static void re_edit __P((char *)); #endif static int history_expansion_p __P((char *)); static int shell_comment __P((char *)); static int should_expand __P((char *)); static HIST_ENTRY *last_history_entry __P((void)); static char *expand_histignore_pattern __P((char *)); static int history_should_ignore __P((char *)); /* Is the history expansion starting at string[i] one that should not be expanded? */ static int bash_history_inhibit_expansion (string, i) char *string; int i; { /* The shell uses ! as a pattern negation character in globbing [...] expressions, so let those pass without expansion. */ if (i > 0 && (string[i - 1] == '[') && member (']', string + i + 1)) return (1); /* The shell uses ! as the indirect expansion character, so let those expansions pass as well. */ else if (i > 1 && string[i - 1] == '{' && string[i - 2] == '$' && member ('}', string + i + 1)) return (1); #if defined (EXTENDED_GLOB) else if (extended_glob && i > 1 && string[i+1] == '(' && member (')', string + i + 2)) return (1); #endif else return (0); } void bash_initialize_history () { history_quotes_inhibit_expansion = 1; history_search_delimiter_chars = ";&()|<>"; history_inhibit_expansion_function = bash_history_inhibit_expansion; } void bash_history_reinit (interact) int interact; { #if defined (BANG_HISTORY) history_expansion = interact != 0; history_expansion_inhibited = 1; #endif remember_on_history = interact != 0; history_inhibit_expansion_function = bash_history_inhibit_expansion; } void bash_history_disable () { remember_on_history = 0; #if defined (BANG_HISTORY) history_expansion_inhibited = 1; #endif } void bash_history_enable () { remember_on_history = 1; #if defined (BANG_HISTORY) history_expansion_inhibited = 0; #endif history_inhibit_expansion_function = bash_history_inhibit_expansion; sv_history_control ("HISTCONTROL"); sv_histignore ("HISTIGNORE"); } /* Load the history list from the history file. */ void load_history () { char *hf; struct stat buf; /* Truncate history file for interactive shells which desire it. Note that the history file is automatically truncated to the size of HISTSIZE if the user does not explicitly set the size differently. */ set_if_not ("HISTFILESIZE", get_string_value ("HISTSIZE")); sv_histsize ("HISTFILESIZE"); /* Read the history in HISTFILE into the history list. */ hf = get_string_value ("HISTFILE"); if (hf && *hf && stat (hf, &buf) == 0) { read_history (hf); using_history (); history_lines_in_file = where_history (); } } #ifdef INCLUDE_UNUSED /* Write the existing history out to the history file. */ void save_history () { char *hf; struct stat buf; hf = get_string_value ("HISTFILE"); if (hf && *hf && stat (hf, &buf) == 0) { /* Append only the lines that occurred this session to the history file. */ using_history (); if (history_lines_this_session < where_history () || force_append_history) append_history (history_lines_this_session, hf); else write_history (hf); sv_histsize ("HISTFILESIZE"); } } #endif int maybe_append_history (filename) char *filename; { int fd, result; struct stat buf; result = EXECUTION_SUCCESS; if (history_lines_this_session && (history_lines_this_session < where_history ())) { /* If the filename was supplied, then create it if necessary. */ if (stat (filename, &buf) == -1 && errno == ENOENT) { fd = open (filename, O_WRONLY|O_CREAT, 0600); if (fd < 0) { builtin_error ("%s: cannot create: %s", filename, strerror (errno)); return (EXECUTION_FAILURE); } close (fd); } result = append_history (history_lines_this_session, filename); history_lines_in_file += history_lines_this_session; history_lines_this_session = 0; } return (result); } /* If this is an interactive shell, then append the lines executed this session to the history file. */ int maybe_save_shell_history () { int result; char *hf; struct stat buf; result = 0; if (history_lines_this_session) { hf = get_string_value ("HISTFILE"); if (hf && *hf) { /* If the file doesn't exist, then create it. */ if (stat (hf, &buf) == -1) { int file; file = open (hf, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (file != -1) close (file); } /* Now actually append the lines if the history hasn't been stifled. If the history has been stifled, rewrite the history file. */ using_history (); if (history_lines_this_session <= where_history () || force_append_history) { result = append_history (history_lines_this_session, hf); history_lines_in_file += history_lines_this_session; } else { result = write_history (hf); history_lines_in_file = history_lines_this_session; } history_lines_this_session = 0; sv_histsize ("HISTFILESIZE"); } } return (result); } #if defined (READLINE) /* Tell readline () that we have some text for it to edit. */ static void re_edit (text) char *text; { if (bash_input.type == st_stdin) bash_re_edit (text); } #endif /* READLINE */ /* Return 1 if this line needs history expansion. */ static int history_expansion_p (line) char *line; { register char *s; for (s = line; *s; s++) if (*s == history_expansion_char || *s == history_subst_char) return 1; return 0; } /* Do pre-processing on LINE. If PRINT_CHANGES is non-zero, then print the results of expanding the line if there were any changes. If there is an error, return NULL, otherwise the expanded line is returned. If ADDIT is non-zero the line is added to the history list after history expansion. ADDIT is just a suggestion; REMEMBER_ON_HISTORY can veto, and does. Right now this does history expansion. */ char * pre_process_line (line, print_changes, addit) char *line; int print_changes, addit; { char *history_value; char *return_value; int expanded; return_value = line; expanded = 0; # if defined (BANG_HISTORY) /* History expand the line. If this results in no errors, then add that line to the history if ADDIT is non-zero. */ if (!history_expansion_inhibited && history_expansion && history_expansion_p (line)) { expanded = history_expand (line, &history_value); if (expanded) { if (print_changes) { if (expanded < 0) internal_error ("%s", history_value); #if defined (READLINE) else if (hist_verify == 0 || expanded == 2) #else else #endif fprintf (stderr, "%s\n", history_value); } /* If there was an error, return NULL. */ if (expanded < 0 || expanded == 2) /* 2 == print only */ { free (history_value); # if defined (READLINE) /* New hack. We can allow the user to edit the failed history expansion. */ if (history_reediting && expanded < 0) re_edit (line); # endif /* READLINE */ return ((char *)NULL); } # if defined (READLINE) if (hist_verify && expanded == 1) { re_edit (history_value); return ((char *)NULL); } # endif } /* Let other expansions know that return_value can be free'ed, and that a line has been added to the history list. Note that we only add lines that have something in them. */ expanded = 1; return_value = history_value; } # endif /* BANG_HISTORY */ if (addit && remember_on_history && *return_value) maybe_add_history (return_value); #if 0 if (expanded == 0) return_value = savestring (line); #endif return (return_value); } /* Return 1 if the first non-whitespace character in LINE is a `#', indicating * that the line is a shell comment. */ static int shell_comment (line) char *line; { char *p; for (p = line; p && *p && whitespace (*p); p++) ; return (p && *p == '#'); } #ifdef INCLUDE_UNUSED /* Remove shell comments from LINE. A `#' and anything after it is a comment. This isn't really useful yet, since it doesn't handle quoting. */ static char * filter_comments (line) char *line; { char *p; for (p = line; p && *p && *p != '#'; p++) ; if (p && *p == '#') *p = '\0'; return (line); } #endif /* Add LINE to the history list depending on the value of HISTORY_CONTROL. */ void maybe_add_history (line) char *line; { static int first_line_saved = 0; HIST_ENTRY *temp; hist_last_line_added = 0; /* Don't use the value of history_control to affect the second and subsequent lines of a multi-line command (old code did this only when command_oriented_history is enabled). */ #if 0 if (command_oriented_history && current_command_line_count > 1) #else if (current_command_line_count > 1) #endif { if (first_line_saved && (literal_history || dstack.delimiter_depth != 0 || shell_comment (line) == 0)) bash_add_history (line); return; } /* This is the first line of a (possible multi-line) command. Note whether or not we should save the first line and remember it. */ first_line_saved = 0; switch (history_control) { case 0: first_line_saved = 1; break; case 1: if (*line != ' ') first_line_saved = 1; break; case 3: if (*line == ' ') break; /* FALLTHROUGH if case == 3 (`ignoreboth') */ case 2: using_history (); temp = previous_history (); if (temp == 0 || STREQ (temp->line, line) == 0) first_line_saved = 1; using_history (); break; } if (first_line_saved && history_should_ignore (line) == 0) bash_add_history (line); else first_line_saved = 0; } /* Add a line to the history list. The variable COMMAND_ORIENTED_HISTORY controls the style of history remembering; when non-zero, and LINE is not the first line of a complete parser construct, append LINE to the last history line instead of adding it as a new line. */ void bash_add_history (line) char *line; { int add_it, offset, curlen; HIST_ENTRY *current, *old; char *chars_to_add, *new_line; add_it = 1; if (command_oriented_history && current_command_line_count > 1) { chars_to_add = literal_history ? "\n" : history_delimiting_chars (); using_history (); current = previous_history (); if (current) { /* If the previous line ended with an escaped newline (escaped with backslash, but otherwise unquoted), then remove the quoted newline, since that is what happens when the line is parsed. */ curlen = strlen (current->line); if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\\' && current->line[curlen - 2] != '\\') { current->line[curlen - 1] = '\0'; curlen--; chars_to_add = ""; } new_line = (char *)xmalloc (1 + curlen + strlen (line) + strlen (chars_to_add)); sprintf (new_line, "%s%s%s", current->line, chars_to_add, line); offset = where_history (); old = replace_history_entry (offset, new_line, current->data); free (new_line); if (old) { FREE (old->line); free (old); } add_it = 0; } } if (add_it) { hist_last_line_added = 1; add_history (line); history_lines_this_session++; } using_history (); } int history_number () { using_history (); return (get_string_value ("HISTSIZE") ? history_base + where_history () : 1); } static int should_expand (s) char *s; { char *p; for (p = s; p && *p; p++) { if (*p == '\\') p++; else if (*p == '&') return 1; } return 0; } static int histignore_item_func (ign) struct ign *ign; { if (should_expand (ign->val)) ign->flags |= HIGN_EXPAND; return (0); } void setup_history_ignore (varname) char *varname; { setup_ignore_patterns (&histignore); } static HIST_ENTRY * last_history_entry () { HIST_ENTRY *he; using_history (); he = previous_history (); using_history (); return he; } char * last_history_line () { HIST_ENTRY *he; he = last_history_entry (); if (he == 0) return ((char *)NULL); return he->line; } static char * expand_histignore_pattern (pat) char *pat; { HIST_ENTRY *phe; char *ret; phe = last_history_entry (); if (phe == (HIST_ENTRY *)0) return (savestring (pat)); ret = strcreplace (pat, '&', phe->line, 1); return ret; } /* Return 1 if we should not put LINE into the history according to the patterns in HISTIGNORE. */ static int history_should_ignore (line) char *line; { register int i, match; char *npat; if (histignore.num_ignores == 0) return 0; for (i = match = 0; i < histignore.num_ignores; i++) { if (histignore.ignores[i].flags & HIGN_EXPAND) npat = expand_histignore_pattern (histignore.ignores[i].val); else npat = histignore.ignores[i].val; match = strmatch (npat, line, FNMATCH_EXTFLAG) != FNM_NOMATCH; if (histignore.ignores[i].flags & HIGN_EXPAND) free (npat); if (match) break; } return match; } #endif /* HISTORY */ /* bashline.c -- Bash's interface to the readline library. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (READLINE) #include "bashtypes.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include #endif #if defined (HAVE_GRP_H) # include #endif #include #include "chartypes.h" #include "bashansi.h" #include "shell.h" #include "builtins.h" #include "bashhist.h" #include "bashline.h" #include "execute_cmd.h" #include "findcmd.h" #include "pathexp.h" #include "builtins/common.h" #include #include #include #include #if defined (ALIAS) # include "alias.h" #endif #if defined (PROGRAMMABLE_COMPLETION) # include "pcomplete.h" #endif #if defined (BRACE_COMPLETION) extern int bash_brace_completion __P((int, int)); #endif /* BRACE_COMPLETION */ /* Forward declarations */ /* Functions bound to keys in Readline for Bash users. */ static int shell_expand_line __P((int, int)); static int display_shell_version __P((int, int)); static int operate_and_get_next __P((int, int)); static int bash_ignore_filenames __P((char **)); static int bash_ignore_everything __P((char **)); #if defined (BANG_HISTORY) static char *history_expand_line_internal __P((char *)); static int history_expand_line __P((int, int)); static int tcsh_magic_space __P((int, int)); #endif /* BANG_HISTORY */ #ifdef ALIAS static int alias_expand_line __P((int, int)); #endif #if defined (BANG_HISTORY) && defined (ALIAS) static int history_and_alias_expand_line __P((int, int)); #endif /* Helper functions for Readline. */ static int bash_directory_completion_hook __P((char **)); static int filename_completion_ignore __P((char **)); static int bash_push_line __P((void)); static void cleanup_expansion_error __P((void)); static void maybe_make_readline_line __P((char *)); static void set_up_new_line __P((char *)); static int check_redir __P((int)); static char **attempt_shell_completion __P((const char *, int, int)); static char *variable_completion_function __P((const char *, int)); static char *hostname_completion_function __P((const char *, int)); static char *command_subst_completion_function __P((const char *, int)); static void build_history_completion_array __P((void)); static char *history_completion_generator __P((const char *, int)); static int dynamic_complete_history __P((int, int)); static void initialize_hostname_list __P((void)); static void add_host_name __P((char *)); static void snarf_hosts_from_file __P((char *)); static char **hostnames_matching __P((char *)); static void _ignore_completion_names __P((char **, sh_ignore_func_t *)); static int name_is_acceptable __P((const char *)); static int test_for_directory __P((const char *)); static int return_zero __P((const char *)); static char *bash_dequote_filename __P((char *, int)); static char *quote_word_break_chars __P((char *)); static char *bash_quote_filename __P((char *, int, char *)); static int bash_execute_unix_command __P((int, int)); static void init_unix_command_map __P((void)); static int isolate_sequence __P((char *, int, int, int *)); static int set_saved_history __P((void)); #if defined (ALIAS) static int posix_edit_macros __P((int, int)); #endif #if defined (PROGRAMMABLE_COMPLETION) static int find_cmd_start __P((int)); static int find_cmd_end __P((int)); static char *find_cmd_name __P((int)); static char *prog_complete_return __P((const char *, int)); static char **prog_complete_matches; #endif /* Variables used here but defined in other files. */ extern int current_command_line_count; extern int posixly_correct, no_symbolic_links; extern char *current_prompt_string, *ps1_prompt; extern STRING_INT_ALIST word_token_alist[]; /* SPECIFIC_COMPLETION_FUNCTIONS specifies that we have individual completion functions which indicate what type of completion should be done (at or before point) that can be bound to key sequences with the readline library. */ #define SPECIFIC_COMPLETION_FUNCTIONS #if defined (SPECIFIC_COMPLETION_FUNCTIONS) static int bash_specific_completion __P((int, rl_compentry_func_t *)); static int bash_complete_filename_internal __P((int)); static int bash_complete_username_internal __P((int)); static int bash_complete_hostname_internal __P((int)); static int bash_complete_variable_internal __P((int)); static int bash_complete_command_internal __P((int)); static int bash_complete_filename __P((int, int)); static int bash_possible_filename_completions __P((int, int)); static int bash_complete_username __P((int, int)); static int bash_possible_username_completions __P((int, int)); static int bash_complete_hostname __P((int, int)); static int bash_possible_hostname_completions __P((int, int)); static int bash_complete_variable __P((int, int)); static int bash_possible_variable_completions __P((int, int)); static int bash_complete_command __P((int, int)); static int bash_possible_command_completions __P((int, int)); static char *glob_complete_word __P((const char *, int)); static int bash_glob_completion_internal __P((int)); static int bash_glob_expand_word __P((int, int)); static int bash_glob_list_expansions __P((int, int)); #endif /* SPECIFIC_COMPLETION_FUNCTIONS */ #if defined (VI_MODE) static int vi_edit_and_execute_command __P((int, int)); #endif /* Non-zero once initalize_readline () has been called. */ int bash_readline_initialized = 0; /* If non-zero, we do hostname completion, breaking words at `@' and trying to complete the stuff after the `@' from our own internal host list. */ int perform_hostname_completion = 1; /* If non-zero, we don't do command completion on an empty line. */ int no_empty_command_completion; static char *bash_completer_word_break_characters = " \t\n\"'@><=;|&(:"; static char *bash_nohostname_word_break_characters = " \t\n\"'><=;|&(:"; static rl_hook_func_t *old_rl_startup_hook = (rl_hook_func_t *)NULL; /* What kind of quoting is performed by bash_quote_filename: COMPLETE_DQUOTE = double-quoting the filename COMPLETE_SQUOTE = single_quoting the filename COMPLETE_BSQUOTE = backslash-quoting special chars in the filename */ #define COMPLETE_DQUOTE 1 #define COMPLETE_SQUOTE 2 #define COMPLETE_BSQUOTE 3 static int completion_quoting_style = COMPLETE_BSQUOTE; /* Change the readline VI-mode keymaps into or out of Posix.2 compliance. Called when the shell is put into or out of `posix' mode. */ void posix_readline_initialize (on_or_off) int on_or_off; { if (on_or_off) rl_variable_bind ("comment-begin", "#"); #if defined (VI_MODE) rl_bind_key_in_map (CTRL('I'), on_or_off ? rl_insert : rl_complete, vi_insertion_keymap); #endif } int enable_hostname_completion (on_or_off) int on_or_off; { int old_value; old_value = perform_hostname_completion; if (on_or_off) { perform_hostname_completion = 1; rl_special_prefixes = "$@"; rl_completer_word_break_characters = bash_completer_word_break_characters; } else { perform_hostname_completion = 0; rl_special_prefixes = "$"; rl_completer_word_break_characters = bash_nohostname_word_break_characters; } return (old_value); } /* Called once from parse.y if we are going to use readline. */ void initialize_readline () { if (bash_readline_initialized) return; rl_terminal_name = get_string_value ("TERM"); rl_instream = stdin; rl_outstream = stderr; /* Allow conditional parsing of the ~/.inputrc file. */ rl_readline_name = "Bash"; /* Add bindable names before calling rl_initialize so they may be referenced in the various inputrc files. */ rl_add_defun ("shell-expand-line", shell_expand_line, -1); #ifdef BANG_HISTORY rl_add_defun ("history-expand-line", history_expand_line, -1); rl_add_defun ("magic-space", tcsh_magic_space, -1); #endif #ifdef ALIAS rl_add_defun ("alias-expand-line", alias_expand_line, -1); # ifdef BANG_HISTORY rl_add_defun ("history-and-alias-expand-line", history_and_alias_expand_line, -1); # endif #endif /* Backwards compatibility. */ rl_add_defun ("insert-last-argument", rl_yank_last_arg, -1); rl_add_defun ("operate-and-get-next", operate_and_get_next, -1); rl_add_defun ("display-shell-version", display_shell_version, -1); #if defined (BRACE_COMPLETION) rl_add_defun ("complete-into-braces", bash_brace_completion, -1); #endif #if defined (SPECIFIC_COMPLETION_FUNCTIONS) rl_add_defun ("complete-filename", bash_complete_filename, -1); rl_add_defun ("possible-filename-completions", bash_possible_filename_completions, -1); rl_add_defun ("complete-username", bash_complete_username, -1); rl_add_defun ("possible-username-completions", bash_possible_username_completions, -1); rl_add_defun ("complete-hostname", bash_complete_hostname, -1); rl_add_defun ("possible-hostname-completions", bash_possible_hostname_completions, -1); rl_add_defun ("complete-variable", bash_complete_variable, -1); rl_add_defun ("possible-variable-completions", bash_possible_variable_completions, -1); rl_add_defun ("complete-command", bash_complete_command, -1); rl_add_defun ("possible-command-completions", bash_possible_command_completions, -1); rl_add_defun ("glob-expand-word", bash_glob_expand_word, -1); rl_add_defun ("glob-list-expansions", bash_glob_list_expansions, -1); #endif rl_add_defun ("dynamic-complete-history", dynamic_complete_history, -1); /* Bind defaults before binding our custom shell keybindings. */ if (RL_ISSTATE(RL_STATE_INITIALIZED) == 0) rl_initialize (); /* Bind up our special shell functions. */ rl_bind_key_in_map (CTRL('E'), shell_expand_line, emacs_meta_keymap); /* Bind up our special shell functions. */ #ifdef BANG_HISTORY rl_bind_key_in_map ('^', history_expand_line, emacs_meta_keymap); #endif rl_bind_key_in_map (CTRL ('O'), operate_and_get_next, emacs_standard_keymap); rl_bind_key_in_map (CTRL ('V'), display_shell_version, emacs_ctlx_keymap); /* In Bash, the user can switch editing modes with "set -o [vi emacs]", so it is not necessary to allow C-M-j for context switching. Turn off this occasionally confusing behaviour. */ rl_unbind_key_in_map (CTRL('J'), emacs_meta_keymap); rl_unbind_key_in_map (CTRL('M'), emacs_meta_keymap); #if defined (VI_MODE) rl_unbind_key_in_map (CTRL('E'), vi_movement_keymap); #endif #if defined (BRACE_COMPLETION) rl_bind_key_in_map ('{', bash_brace_completion, emacs_meta_keymap); #endif /* BRACE_COMPLETION */ #if defined (SPECIFIC_COMPLETION_FUNCTIONS) rl_bind_key_in_map ('/', bash_complete_filename, emacs_meta_keymap); rl_bind_key_in_map ('/', bash_possible_filename_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('~', bash_complete_username, emacs_meta_keymap); rl_bind_key_in_map ('~', bash_possible_username_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('@', bash_complete_hostname, emacs_meta_keymap); rl_bind_key_in_map ('@', bash_possible_hostname_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('$', bash_complete_variable, emacs_meta_keymap); rl_bind_key_in_map ('$', bash_possible_variable_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('!', bash_complete_command, emacs_meta_keymap); rl_bind_key_in_map ('!', bash_possible_command_completions, emacs_ctlx_keymap); rl_bind_key_in_map ('*', bash_glob_expand_word, emacs_ctlx_keymap); rl_bind_key_in_map ('g', bash_glob_list_expansions, emacs_ctlx_keymap); #endif /* SPECIFIC_COMPLETION_FUNCTIONS */ rl_bind_key_in_map (TAB, dynamic_complete_history, emacs_meta_keymap); /* Tell the completer that we want a crack first. */ rl_attempted_completion_function = attempt_shell_completion; /* Tell the completer that we might want to follow symbolic links or do other expansion on directory names. */ rl_directory_completion_hook = bash_directory_completion_hook; /* Tell the filename completer we want a chance to ignore some names. */ rl_ignore_some_completions_function = filename_completion_ignore; #if defined (VI_MODE) rl_bind_key_in_map ('v', vi_edit_and_execute_command, vi_movement_keymap); # if defined (ALIAS) rl_bind_key_in_map ('@', posix_edit_macros, vi_movement_keymap); # endif #endif rl_completer_quote_characters = "'\""; /* This sets rl_completer_word_break_characters and rl_special_prefixes to the appropriate values, depending on whether or not hostname completion is enabled. */ enable_hostname_completion (perform_hostname_completion); /* characters that need to be quoted when appearing in filenames. */ rl_filename_quote_characters = " \t\n\\\"'@<>=;|&()#$`?*[!:{"; /*}*/ rl_filename_quoting_function = bash_quote_filename; rl_filename_dequoting_function = bash_dequote_filename; rl_char_is_quoted_p = char_is_quoted; if (posixly_correct) posix_readline_initialize (1); bash_readline_initialized = 1; } /* On Sun systems at least, rl_attempted_completion_function can end up getting set to NULL, and rl_completion_entry_function set to do command word completion if Bash is interrupted while trying to complete a command word. This just resets all the completion functions to the right thing. It's called from throw_to_top_level(). */ void bashline_reinitialize () { tilde_initialize (); rl_attempted_completion_function = attempt_shell_completion; rl_completion_entry_function = NULL; rl_directory_completion_hook = bash_directory_completion_hook; rl_ignore_some_completions_function = filename_completion_ignore; } /* Contains the line to push into readline. */ static char *push_to_readline = (char *)NULL; /* Push the contents of push_to_readline into the readline buffer. */ static int bash_push_line () { if (push_to_readline) { rl_insert_text (push_to_readline); free (push_to_readline); push_to_readline = (char *)NULL; rl_startup_hook = old_rl_startup_hook; } return 0; } /* Call this to set the initial text for the next line to read from readline. */ int bash_re_edit (line) char *line; { FREE (push_to_readline); push_to_readline = savestring (line); old_rl_startup_hook = rl_startup_hook; rl_startup_hook = bash_push_line; return (0); } static int display_shell_version (count, c) int count, c; { rl_crlf (); show_shell_version (0); putc ('\r', rl_outstream); fflush (rl_outstream); rl_on_new_line (); rl_redisplay (); return 0; } /* **************************************************************** */ /* */ /* Readline Stuff */ /* */ /* **************************************************************** */ /* If the user requests hostname completion, then simply build a list of hosts, and complete from that forever more, or at least until HOSTFILE is unset. */ /* THIS SHOULD BE A STRINGLIST. */ /* The kept list of hostnames. */ static char **hostname_list = (char **)NULL; /* The physical size of the above list. */ static int hostname_list_size; /* The number of hostnames in the above list. */ static int hostname_list_length; /* Whether or not HOSTNAME_LIST has been initialized. */ int hostname_list_initialized = 0; /* Initialize the hostname completion table. */ static void initialize_hostname_list () { char *temp; temp = get_string_value ("HOSTFILE"); if (temp == 0) temp = get_string_value ("hostname_completion_file"); if (temp == 0) temp = DEFAULT_HOSTS_FILE; snarf_hosts_from_file (temp); if (hostname_list) hostname_list_initialized++; } /* Add NAME to the list of hosts. */ static void add_host_name (name) char *name; { size_t size; if (hostname_list_length + 2 > hostname_list_size) { hostname_list_size = (hostname_list_size + 32) - (hostname_list_size % 32); size = hostname_list_size * sizeof (char *); hostname_list = (char **)xrealloc (hostname_list, size); } hostname_list[hostname_list_length++] = savestring (name); hostname_list[hostname_list_length] = (char *)NULL; } #define cr_whitespace(c) ((c) == '\r' || (c) == '\n' || whitespace(c)) static void snarf_hosts_from_file (filename) char *filename; { FILE *file; char *temp, buffer[256], name[256]; register int i, start; file = fopen (filename, "r"); if (file == 0) return; while (temp = fgets (buffer, 255, file)) { /* Skip to first character. */ for (i = 0; buffer[i] && cr_whitespace (buffer[i]); i++) ; /* If comment or blank line, ignore. */ if (buffer[i] == '\0' || buffer[i] == '#') continue; /* If `preprocessor' directive, do the include. */ if (strncmp (buffer + i, "$include ", 9) == 0) { char *incfile, *t; /* Find start of filename. */ for (incfile = buffer + i + 9; *incfile && whitespace (*incfile); incfile++) ; /* Find end of filename. */ for (t = incfile; *t && cr_whitespace (*t) == 0; t++) ; *t = '\0'; snarf_hosts_from_file (incfile); continue; } /* Skip internet address if present. */ if (DIGIT (buffer[i])) for (; buffer[i] && cr_whitespace (buffer[i]) == 0; i++); /* Gobble up names. Each name is separated with whitespace. */ while (buffer[i]) { for (; cr_whitespace (buffer[i]); i++) ; if (buffer[i] == '\0' || buffer[i] == '#') break; /* Isolate the current word. */ for (start = i; buffer[i] && cr_whitespace (buffer[i]) == 0; i++) ; if (i == start) continue; strncpy (name, buffer + start, i - start); name[i - start] = '\0'; add_host_name (name); } } fclose (file); } /* Return the hostname list. */ char ** get_hostname_list () { if (hostname_list_initialized == 0) initialize_hostname_list (); return (hostname_list); } void clear_hostname_list () { register int i; if (hostname_list_initialized == 0) return; for (i = 0; i < hostname_list_length; i++) free (hostname_list[i]); hostname_list_length = 0; } /* Return a NULL terminated list of hostnames which begin with TEXT. Initialize the hostname list the first time if neccessary. The array is malloc ()'ed, but not the individual strings. */ static char ** hostnames_matching (text) char *text; { register int i, len, nmatch, rsize; char **result; if (hostname_list_initialized == 0) initialize_hostname_list (); if (hostname_list_initialized == 0) return ((char **)NULL); /* Special case. If TEXT consists of nothing, then the whole list is what is desired. */ if (*text == '\0') { result = alloc_array (1 + hostname_list_length); for (i = 0; i < hostname_list_length; i++) result[i] = hostname_list[i]; result[i] = (char *)NULL; return (result); } /* Scan until found, or failure. */ len = strlen (text); result = (char **)NULL; for (i = nmatch = rsize = 0; i < hostname_list_length; i++) { if (STREQN (text, hostname_list[i], len) == 0) continue; /* OK, it matches. Add it to the list. */ if (nmatch >= (rsize - 1)) { rsize = (rsize + 16) - (rsize % 16); result = (char **)xrealloc (result, rsize * sizeof (char *)); } result[nmatch++] = hostname_list[i]; } if (nmatch) result[nmatch] = (char *)NULL; return (result); } /* The equivalent of the Korn shell C-o operate-and-get-next-history-line editing command. */ static int saved_history_line_to_use = -1; static int set_saved_history () { if (saved_history_line_to_use >= 0) rl_get_previous_history (history_length - saved_history_line_to_use, 0); saved_history_line_to_use = -1; rl_startup_hook = old_rl_startup_hook; return (0); } static int operate_and_get_next (count, c) int count, c; { int where; /* Accept the current line. */ rl_newline (1, c); /* Find the current line, and find the next line to use. */ where = where_history (); if ((history_is_stifled () && (history_length >= history_max_entries)) || (where >= history_length - 1)) saved_history_line_to_use = where; else saved_history_line_to_use = where + 1; old_rl_startup_hook = rl_startup_hook; rl_startup_hook = set_saved_history; return 0; } #if defined (VI_MODE) /* This vi mode command causes VI_EDIT_COMMAND to be run on the current command being entered (if no explicit argument is given), otherwise on a command from the history file. */ #define VI_EDIT_COMMAND "fc -e ${VISUAL:-${EDITOR:-vi}}" static int vi_edit_and_execute_command (count, c) int count, c; { char *command; int r, cclc, rrs; rrs = rl_readline_state; cclc = current_command_line_count; /* Accept the current line. */ rl_newline (1, c); if (rl_explicit_arg) { command = (char *)xmalloc (strlen (VI_EDIT_COMMAND) + 8); sprintf (command, "%s %d", VI_EDIT_COMMAND, count); } else { /* Take the command we were just editing, add it to the history file, then call fc to operate on it. We have to add a dummy command to the end of the history because fc ignores the last command (assumes it's supposed to deal with the command before the `fc'). */ using_history (); bash_add_history (rl_line_buffer); bash_add_history (""); history_lines_this_session++; using_history (); command = savestring (VI_EDIT_COMMAND); } r = parse_and_execute (command, "v", SEVAL_NOHIST); current_command_line_count = cclc; /* Now erase the contents of the current line and undo the effects of the rl_accept_line() above. We don't even want to make the text we just executed available for undoing. */ rl_line_buffer[0] = '\0'; /* XXX */ rl_point = rl_end = 0; rl_done = 0; rl_readline_state = rrs; rl_forced_update_display (); return r; } #endif /* VI_MODE */ #if defined (ALIAS) static int posix_edit_macros (count, key) int count, key; { int c; char alias_name[3], *alias_value, *macro; c = rl_read_key (); alias_name[0] = '_'; alias_name[1] = c; alias_name[2] = '\0'; alias_value = get_alias_value (alias_name); if (alias_value && *alias_value) { macro = savestring (alias_value); rl_push_macro_input (macro); } return 0; } #endif /* **************************************************************** */ /* */ /* How To Do Shell Completion */ /* */ /* **************************************************************** */ #define COMMAND_SEPARATORS ";|&{(`" static int check_redir (ti) int ti; { register int this_char, prev_char; /* Handle the two character tokens `>&', `<&', and `>|'. We are not in a command position after one of these. */ this_char = rl_line_buffer[ti]; prev_char = rl_line_buffer[ti - 1]; if ((this_char == '&' && (prev_char == '<' || prev_char == '>')) || (this_char == '|' && prev_char == '>')) return (1); else if ((this_char == '{' && prev_char == '$') || /* } */ (char_is_quoted (rl_line_buffer, ti))) return (1); return (0); } #if defined (PROGRAMMABLE_COMPLETION) /* * XXX - because of the <= start test, and setting os = s+1, this can * potentially return os > start. This is probably not what we want to * happen, but fix later after 2.05a-release. */ static int find_cmd_start (start) int start; { register int s, os; os = 0; while (((s = skip_to_delim (rl_line_buffer, os, COMMAND_SEPARATORS)) <= start) && rl_line_buffer[s]) os = s+1; return os; } static int find_cmd_end (end) int end; { register int e; e = skip_to_delim (rl_line_buffer, end, COMMAND_SEPARATORS); return e; } static char * find_cmd_name (start) int start; { char *name; register int s, e; for (s = start; whitespace (rl_line_buffer[s]); s++) ; /* skip until a shell break character */ e = skip_to_delim (rl_line_buffer, s, "()<>;&| \t\n"); name = substring (rl_line_buffer, s, e); return (name); } static char * prog_complete_return (text, matchnum) const char *text; int matchnum; { static int ind; if (matchnum == 0) ind = 0; if (prog_complete_matches == 0 || prog_complete_matches[ind] == 0) return (char *)NULL; return (prog_complete_matches[ind++]); } #endif /* PROGRAMMABLE_COMPLETION */ /* Do some completion on TEXT. The indices of TEXT in RL_LINE_BUFFER are at START and END. Return an array of matches, or NULL if none. */ static char ** attempt_shell_completion (text, start, end) const char *text; int start, end; { int in_command_position, ti, saveti, qc; char **matches, *command_separator_chars; command_separator_chars = COMMAND_SEPARATORS; matches = (char **)NULL; rl_ignore_some_completions_function = filename_completion_ignore; /* Determine if this could be a command word. It is if it appears at the start of the line (ignoring preceding whitespace), or if it appears after a character that separates commands. It cannot be a command word if we aren't at the top-level prompt. */ ti = start - 1; saveti = qc = -1; while ((ti > -1) && (whitespace (rl_line_buffer[ti]))) ti--; #if 1 /* If this is an open quote, maybe we're trying to complete a quoted command name. */ if (rl_line_buffer[ti] == '"' || rl_line_buffer[ti] == '\'') { qc = rl_line_buffer[ti]; saveti = ti--; while (ti > -1 && (whitespace (rl_line_buffer[ti]))) ti--; } #endif in_command_position = 0; if (ti < 0) { /* Only do command completion at the start of a line when we are prompting at the top level. */ if (current_prompt_string == ps1_prompt) in_command_position++; } else if (member (rl_line_buffer[ti], command_separator_chars)) { in_command_position++; if (check_redir (ti) == 1) in_command_position = 0; } else { /* This still could be in command position. It is possible that all of the previous words on the line are variable assignments. */ } /* Check that we haven't incorrectly flagged a closed command substitution as indicating we're in a command position. */ if (in_command_position && ti >= 0 && rl_line_buffer[ti] == '`' && *text != '`' && unclosed_pair (rl_line_buffer, end, "`") == 0) in_command_position = 0; /* Special handling for command substitution. If *TEXT is a backquote, it can be the start or end of an old-style command substitution, or unmatched. If it's unmatched, both calls to unclosed_pair will succeed. */ if (*text == '`' && (in_command_position || (unclosed_pair (rl_line_buffer, start, "`") && unclosed_pair (rl_line_buffer, end, "`")))) matches = rl_completion_matches (text, command_subst_completion_function); #if defined (PROGRAMMABLE_COMPLETION) /* Attempt programmable completion. */ if (!matches && in_command_position == 0 && prog_completion_enabled && (num_progcomps () > 0) && current_prompt_string == ps1_prompt) { int s, e, foundcs; char *n; /* XXX - don't free the members */ if (prog_complete_matches) free (prog_complete_matches); prog_complete_matches = (char **)NULL; s = find_cmd_start (start); e = find_cmd_end (end); n = find_cmd_name (s); if (e > s) prog_complete_matches = programmable_completions (n, text, s, e, &foundcs); else foundcs = 0; FREE (n); /* XXX - if we found a COMPSPEC for the command, just return whatever the programmable completion code returns, and disable the default filename completion that readline will do unless the COPT_DEFAULT option has been set with the `-o default' option to complete. */ if (foundcs) { /* If the user specified that the compspec returns filenames, make sure that readline knows it. */ if (foundcs & COPT_FILENAMES) rl_filename_completion_desired = 1; /* Turn what the programmable completion code returns into what readline wants. I should have made compute_lcd_of_matches external... */ matches = rl_completion_matches (text, prog_complete_return); if ((foundcs & COPT_DEFAULT) == 0) rl_attempted_completion_over = 1; /* no default */ return (matches); } } #endif /* New posix-style command substitution or variable name? */ if (!matches && *text == '$') { if (qc != '\'' && text[1] == '(') /* ) */ matches = rl_completion_matches (text, command_subst_completion_function); else matches = rl_completion_matches (text, variable_completion_function); } /* If the word starts in `~', and there is no slash in the word, then try completing this word as a username. */ if (!matches && *text == '~' && !strchr (text, '/')) matches = rl_completion_matches (text, rl_username_completion_function); /* Another one. Why not? If the word starts in '@', then look through the world of known hostnames for completion first. */ if (!matches && perform_hostname_completion && *text == '@') matches = rl_completion_matches (text, hostname_completion_function); /* And last, (but not least) if this word is in a command position, then complete over possible command names, including aliases, functions, and command names. */ if (!matches && in_command_position) { if (start == 0 && end == 0 && text[0] == '\0' && no_empty_command_completion) { matches = (char **)NULL; rl_ignore_some_completions_function = bash_ignore_everything; } else { matches = rl_completion_matches (text, command_word_completion_function); /* If we are attempting command completion and nothing matches, we do not want readline to perform filename completion for us. We still want to be able to complete partial pathnames, so set the completion ignore function to something which will remove filenames and leave directories in the match list. */ if (matches == (char **)NULL) rl_ignore_some_completions_function = bash_ignore_filenames; } } /* This could be a globbing pattern, so try to expand it using pathname expansion. */ if (!matches && glob_pattern_p (text)) { matches = rl_completion_matches (text, glob_complete_word); /* A glob expression that matches more than one filename is problematic. If we match more than one filename, punt. */ if (matches && matches[1]) { free_array (matches); matches = (char **)0; } } return (matches); } /* This is the function to call when the word to complete is in a position where a command word can be found. It grovels $PATH, looking for commands that match. It also scans aliases, function names, and the shell_builtin table. */ char * command_word_completion_function (hint_text, state) const char *hint_text; int state; { static char *hint = (char *)NULL; static char *path = (char *)NULL; static char *val = (char *)NULL; static char *filename_hint = (char *)NULL; static int path_index, hint_len, istate; static int mapping_over, local_index; static SHELL_VAR **varlist = (SHELL_VAR **)NULL; #if defined (ALIAS) static alias_t **alias_list = (alias_t **)NULL; #endif /* ALIAS */ /* We have to map over the possibilities for command words. If we have no state, then make one just for that purpose. */ if (!state) { if (hint) free (hint); mapping_over = 0; val = (char *)NULL; /* If this is an absolute program name, do not check it against aliases, reserved words, functions or builtins. We must check whether or not it is unique, and, if so, whether that filename is executable. */ if (absolute_program (hint_text)) { /* Perform tilde expansion on what's passed, so we don't end up passing filenames with tildes directly to stat(). */ if (*hint_text == '~') hint = bash_tilde_expand (hint_text); else hint = savestring (hint_text); hint_len = strlen (hint); if (filename_hint) free (filename_hint); filename_hint = savestring (hint); mapping_over = 4; istate = 0; goto inner; } hint = savestring (hint_text); hint_len = strlen (hint); path = get_string_value ("PATH"); path_index = 0; /* Initialize the variables for each type of command word. */ local_index = 0; if (varlist) free (varlist); varlist = all_visible_functions (); #if defined (ALIAS) if (alias_list) free (alias_list); alias_list = all_aliases (); #endif /* ALIAS */ } /* mapping_over says what we are currently hacking. Note that every case in this list must fall through when there are no more possibilities. */ switch (mapping_over) { case 0: /* Aliases come first. */ #if defined (ALIAS) while (alias_list && alias_list[local_index]) { register char *alias; alias = alias_list[local_index++]->name; if (STREQN (alias, hint, hint_len)) return (savestring (alias)); } #endif /* ALIAS */ local_index = 0; mapping_over++; case 1: /* Then shell reserved words. */ { while (word_token_alist[local_index].word) { register char *reserved_word; reserved_word = word_token_alist[local_index++].word; if (STREQN (reserved_word, hint, hint_len)) return (savestring (reserved_word)); } local_index = 0; mapping_over++; } case 2: /* Then function names. */ while (varlist && varlist[local_index]) { register char *varname; varname = varlist[local_index++]->name; if (STREQN (varname, hint, hint_len)) return (savestring (varname)); } local_index = 0; mapping_over++; case 3: /* Then shell builtins. */ for (; local_index < num_shell_builtins; local_index++) { /* Ignore it if it doesn't have a function pointer or if it is not currently enabled. */ if (!shell_builtins[local_index].function || (shell_builtins[local_index].flags & BUILTIN_ENABLED) == 0) continue; if (STREQN (shell_builtins[local_index].name, hint, hint_len)) { int i = local_index++; return (savestring (shell_builtins[i].name)); } } local_index = 0; mapping_over++; } /* Repeatedly call filename_completion_function while we have members of PATH left. Question: should we stat each file? Answer: we call executable_file () on each file. */ outer: istate = (val != (char *)NULL); if (!istate) { char *current_path; /* Get the next directory from the path. If there is none, then we are all done. */ if (!path || !path[path_index] || (current_path = extract_colon_unit (path, &path_index)) == 0) return ((char *)NULL); if (*current_path == 0) { free (current_path); current_path = savestring ("."); } if (*current_path == '~') { char *t; t = bash_tilde_expand (current_path); free (current_path); current_path = t; } if (filename_hint) free (filename_hint); filename_hint = (char *)xmalloc (2 + strlen (current_path) + hint_len); sprintf (filename_hint, "%s/%s", current_path, hint); free (current_path); } inner: val = rl_filename_completion_function (filename_hint, istate); istate = 1; if (val == 0) { /* If the hint text is an absolute program, then don't bother searching through PATH. */ if (absolute_program (hint)) return ((char *)NULL); goto outer; } else { int match, freetemp; char *temp; if (absolute_program (hint)) { match = strncmp (val, hint, hint_len) == 0; /* If we performed tilde expansion, restore the original filename. */ if (*hint_text == '~') { int l, tl, vl; vl = strlen (val); tl = strlen (hint_text); l = vl - hint_len; /* # of chars added */ temp = (char *)xmalloc (l + 2 + tl); strcpy (temp, hint_text); strcpy (temp + tl, val + vl - l); } else temp = savestring (val); freetemp = 1; } else { temp = strrchr (val, '/'); if (temp) { temp++; freetemp = match = strncmp (temp, hint, hint_len) == 0; if (match) temp = savestring (temp); } else freetemp = match = 0; } /* If we have found a match, and it is an executable file or a directory name, return it. */ if (match && executable_or_directory (val)) { free (val); val = ""; /* So it won't be NULL. */ return (temp); } else { if (freetemp) free (temp); free (val); goto inner; } } } /* Completion inside an unterminated command substitution. */ static char * command_subst_completion_function (text, state) const char *text; int state; { static char **matches = (char **)NULL; static const char *orig_start; static char *filename_text = (char *)NULL; static int cmd_index, start_len; char *value; if (state == 0) { if (filename_text) free (filename_text); orig_start = text; if (*text == '`') text++; else if (*text == '$' && text[1] == '(') /* ) */ text += 2; start_len = text - orig_start; filename_text = savestring (text); if (matches) free (matches); matches = rl_completion_matches (filename_text, command_word_completion_function); cmd_index = 0; } if (!matches || !matches[cmd_index]) { rl_filename_quoting_desired = 0; /* disable quoting */ return ((char *)NULL); } else { value = (char *)xmalloc (1 + start_len + strlen (matches[cmd_index])); if (start_len == 1) value[0] = *orig_start; else strncpy (value, orig_start, start_len); strcpy (value + start_len, matches[cmd_index]); cmd_index++; return (value); } } /* Okay, now we write the entry_function for variable completion. */ static char * variable_completion_function (text, state) const char *text; int state; { static char **varlist = (char **)NULL; static int varlist_index; static char *varname = (char *)NULL; static int namelen; static int first_char, first_char_loc; if (!state) { if (varname) free (varname); first_char_loc = 0; first_char = text[0]; if (first_char == '$') first_char_loc++; if (text[first_char_loc] == '{') first_char_loc++; varname = savestring (text + first_char_loc); namelen = strlen (varname); if (varlist) free_array (varlist); varlist = all_variables_matching_prefix (varname); varlist_index = 0; } if (!varlist || !varlist[varlist_index]) { return ((char *)NULL); } else { char *value; value = (char *)xmalloc (4 + strlen (varlist[varlist_index])); if (first_char_loc) { value[0] = first_char; if (first_char_loc == 2) value[1] = '{'; } strcpy (value + first_char_loc, varlist[varlist_index]); if (first_char_loc == 2) strcat (value, "}"); varlist_index++; return (value); } } /* How about a completion function for hostnames? */ static char * hostname_completion_function (text, state) const char *text; int state; { static char **list = (char **)NULL; static int list_index = 0; static int first_char, first_char_loc; /* If we don't have any state, make some. */ if (state == 0) { FREE (list); list = (char **)NULL; first_char_loc = 0; first_char = *text; if (first_char == '@') first_char_loc++; list = hostnames_matching ((char *)text+first_char_loc); list_index = 0; } if (list && list[list_index]) { char *t; t = (char *)xmalloc (2 + strlen (list[list_index])); *t = first_char; strcpy (t + first_char_loc, list[list_index]); list_index++; return (t); } return ((char *)NULL); } char * bash_groupname_completion_function (text, state) const char *text; int state; { #if defined (__WIN32__) || defined (__OPENNT) || !defined (HAVE_GRP_H) return ((char *)NULL); #else static char *gname = (char *)NULL; static struct group *grent; static int gnamelen; char *value; if (state == 0) { FREE (gname); gname = savestring (text); gnamelen = strlen (gname); setgrent (); } while (grent = getgrent ()) { if (gnamelen == 0 || (STREQN (gname, grent->gr_name, gnamelen))) break; } if (grent == 0) { endgrent (); return ((char *)NULL); } value = savestring (grent->gr_name); return (value); #endif } /* Functions to perform history and alias expansions on the current line. */ #if defined (BANG_HISTORY) /* Perform history expansion on the current line. If no history expansion is done, pre_process_line() returns what it was passed, so we need to allocate a new line here. */ static char * history_expand_line_internal (line) char *line; { char *new_line; new_line = pre_process_line (line, 0, 0); return (new_line == line) ? savestring (line) : new_line; } #endif /* There was an error in expansion. Let the preprocessor print the error here. */ static void cleanup_expansion_error () { char *to_free; fprintf (rl_outstream, "\r\n"); to_free = pre_process_line (rl_line_buffer, 1, 0); if (to_free != rl_line_buffer) free (to_free); putc ('\r', rl_outstream); rl_forced_update_display (); } /* If NEW_LINE differs from what is in the readline line buffer, add an undo record to get from the readline line buffer contents to the new line and make NEW_LINE the current readline line. */ static void maybe_make_readline_line (new_line) char *new_line; { if (strcmp (new_line, rl_line_buffer) != 0) { rl_point = rl_end; rl_add_undo (UNDO_BEGIN, 0, 0, 0); rl_delete_text (0, rl_point); rl_point = rl_end = 0; rl_insert_text (new_line); rl_add_undo (UNDO_END, 0, 0, 0); } } /* Make NEW_LINE be the current readline line. This frees NEW_LINE. */ static void set_up_new_line (new_line) char *new_line; { int old_point, at_end; old_point = rl_point; at_end = rl_point == rl_end; /* If the line was history and alias expanded, then make that be one thing to undo. */ maybe_make_readline_line (new_line); free (new_line); /* Place rl_point where we think it should go. */ if (at_end) rl_point = rl_end; else if (old_point < rl_end) { rl_point = old_point; if (!whitespace (rl_line_buffer[rl_point])) rl_forward_word (1, 0); } } #if defined (ALIAS) /* Expand aliases in the current readline line. */ static int alias_expand_line (count, ignore) int count, ignore; { char *new_line; new_line = alias_expand (rl_line_buffer); if (new_line) { set_up_new_line (new_line); return (0); } else { cleanup_expansion_error (); return (1); } } #endif #if defined (BANG_HISTORY) /* History expand the line. */ static int history_expand_line (count, ignore) int count, ignore; { char *new_line; new_line = history_expand_line_internal (rl_line_buffer); if (new_line) { set_up_new_line (new_line); return (0); } else { cleanup_expansion_error (); return (1); } } /* Expand history substitutions in the current line and then insert a space (hopefully close to where we were before). */ static int tcsh_magic_space (count, ignore) int count, ignore; { int dist_from_end, old_point; old_point = rl_point; dist_from_end = rl_end - rl_point; if (history_expand_line (count, ignore) == 0) { /* Try a simple heuristic from Stephen Gildea . This works if all expansions were before rl_point or if no expansions were performed. */ rl_point = (old_point == 0) ? old_point : rl_end - dist_from_end; rl_insert (1, ' '); return (0); } else return (1); } #endif /* History and alias expand the line. */ static int history_and_alias_expand_line (count, ignore) int count, ignore; { char *new_line; new_line = pre_process_line (rl_line_buffer, 0, 0); if (new_line == rl_line_buffer) new_line = savestring (new_line); #if defined (ALIAS) if (new_line) { char *alias_line; alias_line = alias_expand (new_line); free (new_line); new_line = alias_line; } #endif /* ALIAS */ if (new_line) { set_up_new_line (new_line); return (0); } else { cleanup_expansion_error (); return (1); } } /* History and alias expand the line, then perform the shell word expansions by calling expand_string. This can't use set_up_new_line() because we want the variable expansions as a separate undo'able set of operations. */ static int shell_expand_line (count, ignore) int count, ignore; { char *new_line; WORD_LIST *expanded_string; new_line = pre_process_line (rl_line_buffer, 0, 0); if (new_line == rl_line_buffer) new_line = savestring (new_line); #if defined (ALIAS) if (new_line) { char *alias_line; alias_line = alias_expand (new_line); free (new_line); new_line = alias_line; } #endif /* ALIAS */ if (new_line) { int old_point = rl_point; int at_end = rl_point == rl_end; /* If the line was history and alias expanded, then make that be one thing to undo. */ maybe_make_readline_line (new_line); free (new_line); /* If there is variable expansion to perform, do that as a separate operation to be undone. */ new_line = savestring (rl_line_buffer); expanded_string = expand_string (new_line, 0); FREE (new_line); if (expanded_string == 0) { new_line = (char *)xmalloc (1); new_line[0] = '\0'; } else { new_line = string_list (expanded_string); dispose_words (expanded_string); } maybe_make_readline_line (new_line); free (new_line); /* Place rl_point where we think it should go. */ if (at_end) rl_point = rl_end; else if (old_point < rl_end) { rl_point = old_point; if (!whitespace (rl_line_buffer[rl_point])) rl_forward_word (1, 0); } return 0; } else { cleanup_expansion_error (); return 1; } } /* Define NO_FORCE_FIGNORE if you want to match filenames that would otherwise be ignored if they are the only possible matches. */ /* #define NO_FORCE_FIGNORE */ /* If FIGNORE is set, then don't match files with the given suffixes when completing filenames. If only one of the possibilities has an acceptable suffix, delete the others, else just return and let the completer signal an error. It is called by the completer when real completions are done on filenames by the completer's internal function, not for completion lists (M-?) and not on "other" completion types, such as hostnames or commands. */ static struct ignorevar fignore = { "FIGNORE", (struct ign *)0, 0, (char *)0, (sh_iv_item_func_t *) 0, }; static void _ignore_completion_names (names, name_func) char **names; sh_ignore_func_t *name_func; { char **newnames; int idx, nidx; #ifdef NO_FORCE_FIGNORE char **oldnames; int oidx; #endif /* If there is only one completion, see if it is acceptable. If it is not, free it up. In any case, short-circuit and return. This is a special case because names[0] is not the prefix of the list of names if there is only one completion; it is the completion itself. */ if (names[1] == (char *)0) { #ifndef NO_FORCE_FIGNORE if ((*name_func) (names[0]) == 0) { free (names[0]); names[0] = (char *)NULL; } #endif return; } /* Allocate space for array to hold list of pointers to matching filenames. The pointers are copied back to NAMES when done. */ for (nidx = 1; names[nidx]; nidx++) ; newnames = alloc_array (nidx + 1); #ifdef NO_FORCE_FIGNORE oldnames = alloc_array (nidx - 1); oidx = 0; #endif newnames[0] = names[0]; for (idx = nidx = 1; names[idx]; idx++) { if ((*name_func) (names[idx])) newnames[nidx++] = names[idx]; else #ifndef NO_FORCE_FIGNORE free (names[idx]); #else oldnames[oidx++] = names[idx]; #endif } newnames[nidx] = (char *)NULL; /* If none are acceptable then let the completer handle it. */ if (nidx == 1) { #ifndef NO_FORCE_FIGNORE free (names[0]); names[0] = (char *)NULL; #else free (oldnames); #endif free (newnames); return; } #ifdef NO_FORCE_FIGNORE while (oidx) free (oldnames[--oidx]); free (oldnames); #endif /* If only one is acceptable, copy it to names[0] and return. */ if (nidx == 2) { free (names[0]); names[0] = newnames[1]; names[1] = (char *)NULL; free (newnames); return; } /* Copy the acceptable names back to NAMES, set the new array end, and return. */ for (nidx = 1; newnames[nidx]; nidx++) names[nidx] = newnames[nidx]; names[nidx] = (char *)NULL; free (newnames); } static int name_is_acceptable (name) const char *name; { struct ign *p; int nlen; for (nlen = strlen (name), p = fignore.ignores; p->val; p++) { if (nlen > p->len && p->len > 0 && STREQ (p->val, &name[nlen - p->len])) return (0); } return (1); } #if 0 static int ignore_dot_names (name) char *name; { return (name[0] != '.'); } #endif static int filename_completion_ignore (names) char **names; { #if 0 if (glob_dot_filenames == 0) _ignore_completion_names (names, ignore_dot_names); #endif setup_ignore_patterns (&fignore); if (fignore.num_ignores == 0) return 0; _ignore_completion_names (names, name_is_acceptable); return 0; } /* Return 1 if NAME is a directory. */ static int test_for_directory (name) const char *name; { struct stat finfo; char *fn; fn = bash_tilde_expand (name); if (stat (fn, &finfo) != 0) { free (fn); return 0; } free (fn); return (S_ISDIR (finfo.st_mode)); } /* Remove files from NAMES, leaving directories. */ static int bash_ignore_filenames (names) char **names; { _ignore_completion_names (names, test_for_directory); return 0; } static int return_zero (name) const char *name; { return 0; } static int bash_ignore_everything (names) char **names; { _ignore_completion_names (names, return_zero); return 0; } /* Handle symbolic link references and other directory name expansions while hacking completion. */ static int bash_directory_completion_hook (dirname) char **dirname; { char *local_dirname, *new_dirname, *t; int return_value, should_expand_dirname; WORD_LIST *wl; return_value = should_expand_dirname = 0; local_dirname = *dirname; #if 0 should_expand_dirname = strchr (local_dirname, '$') || strchr (local_dirname, '`'); #else if (strchr (local_dirname, '$')) should_expand_dirname = 1; else { t = strchr (local_dirname, '`'); if (t && unclosed_pair (local_dirname, strlen (local_dirname), "`") == 0) should_expand_dirname = 1; } #endif if (should_expand_dirname) { new_dirname = savestring (local_dirname); wl = expand_string (new_dirname, 0); if (wl) { *dirname = string_list (wl); /* Tell the completer to replace the directory name only if we actually expanded something. */ return_value = STREQ (local_dirname, *dirname) == 0; free (local_dirname); free (new_dirname); dispose_words (wl); local_dirname = *dirname; } else { free (new_dirname); free (local_dirname); *dirname = (char *)xmalloc (1); **dirname = '\0'; return 1; } } if (!no_symbolic_links && (local_dirname[0] != '.' || local_dirname[1])) { char *temp1, *temp2; int len1, len2; t = get_working_directory ("symlink-hook"); temp1 = make_absolute (local_dirname, t); free (t); temp2 = sh_canonpath (temp1, PATH_CHECKDOTDOT|PATH_CHECKEXISTS); /* If we can't canonicalize, bail. */ if (temp2 == 0) { free (temp1); return 1; } len1 = strlen (temp1); if (temp1[len1 - 1] == '/') { len2 = strlen (temp2); temp2 = (char *)xrealloc (temp2, len2 + 2); temp2[len2] = '/'; temp2[len2 + 1] = '\0'; } free (local_dirname); *dirname = temp2; free (temp1); } return (return_value); } static char **history_completion_array = (char **)NULL; static int harry_size; static int harry_len; static void build_history_completion_array () { register int i, j; HIST_ENTRY **hlist; char **tokens; /* First, clear out the current dynamic history completion list. */ if (harry_size) { for (i = 0; history_completion_array[i]; i++) free (history_completion_array[i]); free (history_completion_array); history_completion_array = (char **)NULL; harry_size = 0; harry_len = 0; } /* Next, grovel each line of history, making each shell-sized token a separate entry in the history_completion_array. */ hlist = history_list (); if (hlist) { for (i = 0; hlist[i]; i++) { /* Separate each token, and place into an array. */ tokens = history_tokenize (hlist[i]->line); for (j = 0; tokens && tokens[j]; j++) { if (harry_len + 2 > harry_size) { harry_size += 10; history_completion_array = (char **)xrealloc (history_completion_array, harry_size * sizeof (char *)); } history_completion_array[harry_len++] = tokens[j]; history_completion_array[harry_len] = (char *)NULL; } free (tokens); } /* Sort the complete list of tokens. */ qsort (history_completion_array, harry_len, sizeof (char *), (QSFUNC *)qsort_string_compare); } } static char * history_completion_generator (hint_text, state) const char *hint_text; int state; { static int local_index, len; static const char *text; /* If this is the first call to the generator, then initialize the list of strings to complete over. */ if (state == 0) { local_index = 0; build_history_completion_array (); text = hint_text; len = strlen (text); } while (history_completion_array && history_completion_array[local_index]) { if (strncmp (text, history_completion_array[local_index++], len) == 0) return (savestring (history_completion_array[local_index - 1])); } return ((char *)NULL); } static int dynamic_complete_history (count, key) int count, key; { int r; rl_compentry_func_t *orig_func; rl_completion_func_t *orig_attempt_func; orig_func = rl_completion_entry_function; orig_attempt_func = rl_attempted_completion_function; rl_completion_entry_function = history_completion_generator; rl_attempted_completion_function = (rl_completion_func_t *)NULL; if (rl_last_func == dynamic_complete_history) r = rl_complete_internal ('?'); else r = rl_complete_internal (TAB); rl_completion_entry_function = orig_func; rl_attempted_completion_function = orig_attempt_func; return r; } #if defined (SPECIFIC_COMPLETION_FUNCTIONS) static int bash_complete_username (ignore, ignore2) int ignore, ignore2; { return bash_complete_username_internal (TAB); } static int bash_possible_username_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_username_internal ('?'); } static int bash_complete_username_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, rl_username_completion_function); } static int bash_complete_filename (ignore, ignore2) int ignore, ignore2; { return bash_complete_filename_internal (TAB); } static int bash_possible_filename_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_filename_internal ('?'); } static int bash_complete_filename_internal (what_to_do) int what_to_do; { rl_compentry_func_t *orig_func; rl_completion_func_t *orig_attempt_func; rl_icppfunc_t *orig_dir_func; const char *orig_rl_completer_word_break_characters; int r; orig_func = rl_completion_entry_function; orig_attempt_func = rl_attempted_completion_function; orig_dir_func = rl_directory_completion_hook; orig_rl_completer_word_break_characters = rl_completer_word_break_characters; rl_completion_entry_function = rl_filename_completion_function; rl_attempted_completion_function = (rl_completion_func_t *)NULL; rl_directory_completion_hook = (rl_icppfunc_t *)NULL; rl_completer_word_break_characters = " \t\n\"\'"; r = rl_complete_internal (what_to_do); rl_completion_entry_function = orig_func; rl_attempted_completion_function = orig_attempt_func; rl_directory_completion_hook = orig_dir_func; rl_completer_word_break_characters = orig_rl_completer_word_break_characters; return r; } static int bash_complete_hostname (ignore, ignore2) int ignore, ignore2; { return bash_complete_hostname_internal (TAB); } static int bash_possible_hostname_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_hostname_internal ('?'); } static int bash_complete_variable (ignore, ignore2) int ignore, ignore2; { return bash_complete_variable_internal (TAB); } static int bash_possible_variable_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_variable_internal ('?'); } static int bash_complete_command (ignore, ignore2) int ignore, ignore2; { return bash_complete_command_internal (TAB); } static int bash_possible_command_completions (ignore, ignore2) int ignore, ignore2; { return bash_complete_command_internal ('?'); } static int bash_complete_hostname_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, hostname_completion_function); } static int bash_complete_variable_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, variable_completion_function); } static int bash_complete_command_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, command_word_completion_function); } static char * glob_complete_word (text, state) const char *text; int state; { static char **matches = (char **)NULL; static int ind; char *ret; if (state == 0) { rl_filename_completion_desired = 1; if (matches) free (matches); matches = shell_glob_filename (text); if (GLOB_FAILED (matches)) matches = (char **)NULL; ind = 0; } ret = matches ? matches[ind] : (char *)NULL; ind++; return ret; } static int bash_glob_completion_internal (what_to_do) int what_to_do; { return bash_specific_completion (what_to_do, glob_complete_word); } static int bash_glob_expand_word (count, key) int count, key; { return bash_glob_completion_internal ('*'); } static int bash_glob_list_expansions (count, key) int count, key; { return bash_glob_completion_internal ('?'); } static int bash_specific_completion (what_to_do, generator) int what_to_do; rl_compentry_func_t *generator; { rl_compentry_func_t *orig_func; rl_completion_func_t *orig_attempt_func; int r; orig_func = rl_completion_entry_function; orig_attempt_func = rl_attempted_completion_function; rl_completion_entry_function = generator; rl_attempted_completion_function = NULL; r = rl_complete_internal (what_to_do); rl_completion_entry_function = orig_func; rl_attempted_completion_function = orig_attempt_func; return r; } #endif /* SPECIFIC_COMPLETION_FUNCTIONS */ /* Filename quoting for completion. */ /* A function to strip unquoted quote characters (single quotes, double quotes, and backslashes). It allows single quotes to appear within double quotes, and vice versa. It should be smarter. */ static char * bash_dequote_filename (text, quote_char) char *text; int quote_char; { char *ret, *p, *r; int l, quoted; l = strlen (text); ret = (char *)xmalloc (l + 1); for (quoted = quote_char, p = text, r = ret; p && *p; p++) { /* Allow backslash-quoted characters to pass through unscathed. */ if (*p == '\\') { *r++ = *++p; if (*p == '\0') break; continue; } /* Close quote. */ if (quoted && *p == quoted) { quoted = 0; continue; } /* Open quote. */ if (quoted == 0 && (*p == '\'' || *p == '"')) { quoted = *p; continue; } *r++ = *p; } *r = '\0'; return ret; } /* Quote characters that the readline completion code would treat as word break characters with backslashes. Pass backslash-quoted characters through without examination. */ static char * quote_word_break_chars (text) char *text; { char *ret, *r, *s; int l; l = strlen (text); ret = (char *)xmalloc ((2 * l) + 1); for (s = text, r = ret; *s; s++) { /* Pass backslash-quoted characters through, including the backslash. */ if (*s == '\\') { *r++ = '\\'; *r++ = *++s; if (*s == '\0') break; continue; } /* OK, we have an unquoted character. Check its presence in rl_completer_word_break_characters. */ if (strchr (rl_completer_word_break_characters, *s)) *r++ = '\\'; *r++ = *s; } *r = '\0'; return ret; } /* Quote a filename using double quotes, single quotes, or backslashes depending on the value of completion_quoting_style. If we're completing using backslashes, we need to quote some additional characters (those that readline treats as word breaks), so we call quote_word_break_chars on the result. */ static char * bash_quote_filename (s, rtype, qcp) char *s; int rtype; char *qcp; { char *rtext, *mtext, *ret; int rlen, cs; rtext = (char *)NULL; /* If RTYPE == MULT_MATCH, it means that there is more than one match. In this case, we do not add the closing quote or attempt to perform tilde expansion. If RTYPE == SINGLE_MATCH, we try to perform tilde expansion, because single and double quotes inhibit tilde expansion by the shell. */ mtext = s; if (mtext[0] == '~' && rtype == SINGLE_MATCH) mtext = bash_tilde_expand (s); cs = completion_quoting_style; /* Might need to modify the default completion style based on *qcp, since it's set to any user-provided opening quote. We also change to single-quoting if there is no user-provided opening quote and the word being completed contains newlines, since those are not quoted correctly using backslashes (a backslash-newline pair is special to the shell parser). */ if (*qcp == '\0' && cs == COMPLETE_BSQUOTE && strchr (mtext, '\n')) cs = COMPLETE_SQUOTE; else if (*qcp == '"') cs = COMPLETE_DQUOTE; else if (*qcp == '\'') cs = COMPLETE_SQUOTE; #if defined (BANG_HISTORY) else if (*qcp == '\0' && history_expansion && cs == COMPLETE_DQUOTE && history_expansion_inhibited == 0 && strchr (mtext, '!')) cs = COMPLETE_BSQUOTE; if (*qcp == '"' && history_expansion && cs == COMPLETE_DQUOTE && history_expansion_inhibited == 0 && strchr (mtext, '!')) { cs = COMPLETE_BSQUOTE; *qcp = '\0'; } #endif switch (cs) { case COMPLETE_DQUOTE: rtext = sh_double_quote (mtext); break; case COMPLETE_SQUOTE: rtext = sh_single_quote (mtext); break; case COMPLETE_BSQUOTE: rtext = sh_backslash_quote (mtext); break; } if (mtext != s) free (mtext); /* We may need to quote additional characters: those that readline treats as word breaks that are not quoted by backslash_quote. */ if (rtext && cs == COMPLETE_BSQUOTE) { mtext = quote_word_break_chars (rtext); free (rtext); rtext = mtext; } /* Leave the opening quote intact. The readline completion code takes care of avoiding doubled opening quotes. */ rlen = strlen (rtext); ret = (char *)xmalloc (rlen + 1); strcpy (ret, rtext); /* If there are multiple matches, cut off the closing quote. */ if (rtype == MULT_MATCH && cs != COMPLETE_BSQUOTE) ret[rlen - 1] = '\0'; free (rtext); return ret; } /* Support for binding readline key sequences to Unix commands. */ static Keymap cmd_xmap; static int bash_execute_unix_command (count, key) int count; /* ignored */ int key; { Keymap ckmap; /* current keymap */ Keymap xkmap; /* unix command executing keymap */ register int i; char *cmd; /* First, we need to find the right command to execute. This is tricky, because we might have already indirected into another keymap. */ ckmap = rl_get_keymap (); if (ckmap != rl_executing_keymap) { /* bogus. we have to search. only handle one level of indirection. */ for (i = 0; i < KEYMAP_SIZE; i++) { if (ckmap[i].type == ISKMAP && (Keymap)ckmap[i].function == rl_executing_keymap) break; } if (i < KEYMAP_SIZE) xkmap = (Keymap)cmd_xmap[i].function; else { rl_crlf (); internal_error ("bash_execute_unix_command: cannot find keymap for command"); rl_forced_update_display (); return 1; } } else xkmap = cmd_xmap; cmd = (char *)xkmap[key].function; if (cmd == 0) { rl_ding (); return 1; } rl_crlf (); /* move to a new line */ cmd = savestring (cmd); parse_and_execute (cmd, "bash_execute_unix_command", 0); /* and restore the readline buffer and display after command execution. */ rl_forced_update_display (); return 0; } static void init_unix_command_map () { cmd_xmap = rl_make_bare_keymap (); } static int isolate_sequence (string, ind, need_dquote, startp) char *string; int ind, need_dquote, *startp; { register int i; int c, passc, delim; for (i = ind; string[i] && whitespace (string[i]); i++) ; /* NEED_DQUOTE means that the first non-white character *must* be `"'. */ if (need_dquote && string[i] != '"') { builtin_error ("%s: first non-whitespace character is not `\"'", string); return -1; } /* We can have delimited strings even if NEED_DQUOTE == 0, like the command string to bind the key sequence to. */ delim = (string[i] == '"' || string[i] == '\'') ? string[i] : 0; if (startp) *startp = delim ? ++i : i; for (passc = 0; c = string[i]; i++) { if (passc) { passc = 0; continue; } if (c == '\\') { passc++; continue; } if (c == delim) break; } if (delim && string[i] != delim) { builtin_error ("%s: no closing `%c'", string, delim); return -1; } return i; } int bind_keyseq_to_unix_command (line) char *line; { Keymap kmap; char *kseq, *value; int i, kstart; if (cmd_xmap == 0) init_unix_command_map (); kmap = rl_get_keymap (); /* We duplicate some of the work done by rl_parse_and_bind here, but this code only has to handle `"keyseq": ["]command["]' and can generate an error for anything else. */ i = isolate_sequence (line, 0, 1, &kstart); if (i < 0) return -1; /* Create the key sequence string to pass to rl_generic_bind */ kseq = substring (line, kstart, i); for ( ; line[i] && line[i] != ':'; i++) ; if (line[i] != ':') { builtin_error ("%s: missing colon separator", line); return -1; } i = isolate_sequence (line, i + 1, 0, &kstart); if (i < 0) return -1; /* Create the value string containing the command to execute. */ value = substring (line, kstart, i); /* Save the command to execute and the key sequence in the CMD_XMAP */ rl_generic_bind (ISMACR, kseq, value, cmd_xmap); /* and bind the key sequence in the current keymap to a function that understands how to execute from CMD_XMAP */ rl_set_key (kseq, bash_execute_unix_command, kmap); return 0; } /* Used by the programmable completion code. Complete TEXT as a filename, but return only directories as matches. Dequotes the filename before attempting to find matches. */ char ** bash_directory_completion_matches (text) const char *text; { char **m1; char *dfn; int qc; qc = (text[0] == '"' || text[0] == '\'') ? text[0] : 0; dfn = bash_dequote_filename ((char *)text, qc); m1 = rl_completion_matches (dfn, rl_filename_completion_function); free (dfn); if (m1 == 0 || m1[0] == 0) return m1; /* We don't bother recomputing the lcd of the matches, because it will just get thrown away by the programmable completion code and recomputed later. */ (void)bash_ignore_filenames (m1); return m1; } #endif /* READLINE */ /* bracecomp.c -- Complete a filename with the possible completions enclosed in csh-style braces such that the list of completions is available to the shell. */ /* Original version by tromey@cns.caltech.edu, Fri Feb 7 1992. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (BRACE_EXPANSION) && defined (READLINE) #include #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "bashansi.h" #include "shell.h" #include /* Find greatest common prefix of two strings. */ static int string_gcd (s1, s2) char *s1, *s2; { register int i; if (s1 == NULL || s2 == NULL) return (0); for (i = 0; *s1 && *s2; ++s1, ++s2, ++i) { if (*s1 != *s2) break; } return (i); } static char * really_munge_braces (array, real_start, real_end, gcd_zero) char **array; int real_start, real_end, gcd_zero; { int start, end, gcd; char *result, *subterm, *x; int result_size, flag, tlen; flag = 0; if (real_start == real_end) { x = array[real_start] ? sh_backslash_quote (array[real_start] + gcd_zero) : sh_backslash_quote (array[0]); return x; } result = (char *)xmalloc (result_size = 16); *result = '\0'; for (start = real_start; start < real_end; start = end + 1) { gcd = strlen (array[start]); for (end = start + 1; end < real_end; end++) { int temp; temp = string_gcd (array[start], array[end]); if (temp <= gcd_zero) break; gcd = temp; } end--; if (gcd_zero == 0 && start == real_start && end != (real_end - 1)) { /* In this case, add in a leading '{', because we are at top level, and there isn't a consistent prefix. */ result_size += 1; result = (char *)xrealloc (result, result_size); result[0] = '{'; result[1] = '\0'; flag++; } /* Make sure we backslash quote every substring we insert into the resultant brace expression. This is so the default filename quoting function won't inappropriately quote the braces. */ if (start == end) { x = savestring (array[start] + gcd_zero); subterm = sh_backslash_quote (x); free (x); } else { /* If there is more than one element in the subarray, insert the (quoted) prefix and an opening brace. */ tlen = gcd - gcd_zero; x = (char *)xmalloc (tlen + 1); strncpy (x, array[start] + gcd_zero, tlen); x[tlen] = '\0'; subterm = sh_backslash_quote (x); free (x); result_size += strlen (subterm) + 1; result = (char *)xrealloc (result, result_size); strcat (result, subterm); free (subterm); strcat (result, "{"); subterm = really_munge_braces (array, start, end + 1, gcd); subterm[strlen (subterm) - 1] = '}'; } result_size += strlen (subterm) + 1; result = (char *)xrealloc (result, result_size); strcat (result, subterm); strcat (result, ","); free (subterm); } if (gcd_zero == 0) result[strlen (result) - 1] = flag ? '}' : '\0'; return (result); } static int hack_braces_completion (names) char **names; { register int i; char *temp; temp = really_munge_braces (names, 1, array_len (names), 0); for (i = 0; names[i]; ++i) { free (names[i]); names[i] = NULL; } names[0] = temp; return 0; } /* We handle quoting ourselves within hack_braces_completion, so we turn off rl_filename_quoting_desired and rl_filename_quoting_function. */ int bash_brace_completion (count, ignore) int count, ignore; { rl_compignore_func_t *orig_ignore_func; rl_compentry_func_t *orig_entry_func; rl_quote_func_t *orig_quoting_func; rl_completion_func_t *orig_attempt_func; int orig_quoting_desired, r; orig_ignore_func = rl_ignore_some_completions_function; orig_attempt_func = rl_attempted_completion_function; orig_entry_func = rl_completion_entry_function; orig_quoting_func = rl_filename_quoting_function; orig_quoting_desired = rl_filename_quoting_desired; rl_completion_entry_function = rl_filename_completion_function; rl_attempted_completion_function = (rl_completion_func_t *)NULL; rl_ignore_some_completions_function = hack_braces_completion; rl_filename_quoting_function = (rl_quote_func_t *)NULL; rl_filename_quoting_desired = 0; r = rl_complete_internal (TAB); rl_ignore_some_completions_function = orig_ignore_func; rl_attempted_completion_function = orig_attempt_func; rl_completion_entry_function = orig_entry_func; rl_filename_quoting_function = orig_quoting_func; rl_filename_quoting_desired = orig_quoting_desired; return r; } #endif /* BRACE_EXPANSION && READLINE */ /* braces.c -- code for doing word expansion in curly braces. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* Stuff in curly braces gets expanded before all other shell expansions. */ #include "config.h" #if defined (BRACE_EXPANSION) #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "bashansi.h" #if defined (SHELL) # include "shell.h" #endif /* SHELL */ #include "general.h" #define brace_whitespace(c) (!(c) || (c) == ' ' || (c) == '\t' || (c) == '\n') /* Basic idea: Segregate the text into 3 sections: preamble (stuff before an open brace), postamble (stuff after the matching close brace) and amble (stuff after preamble, and before postamble). Expand amble, and then tack on the expansions to preamble. Expand postamble, and tack on the expansions to the result so far. */ /* The character which is used to separate arguments. */ int brace_arg_separator = ','; #if defined (__P) static int brace_gobbler __P((char *, int *, int)); static char **expand_amble __P((char *)); static char **array_concat __P((char **, char **)); #else static int brace_gobbler (); static char **expand_amble (); static char **array_concat (); #endif /* Return an array of strings; the brace expansion of TEXT. */ char ** brace_expand (text) char *text; { register int start; char *preamble, *postamble, *amble; char **tack, **result; int i, j, c; /* Find the text of the preamble. */ i = 0; c = brace_gobbler (text, &i, '{'); preamble = (char *)xmalloc (i + 1); strncpy (preamble, text, i); preamble[i] = '\0'; result = (char **)xmalloc (2 * sizeof (char *)); result[0] = preamble; result[1] = (char *)NULL; /* Special case. If we never found an exciting character, then the preamble is all of the text, so just return that. */ if (c != '{') return (result); /* Find the amble. This is the stuff inside this set of braces. */ start = ++i; c = brace_gobbler (text, &i, '}'); /* What if there isn't a matching close brace? */ if (c == 0) { #if defined (NOTDEF) /* Well, if we found an unquoted BRACE_ARG_SEPARATOR between START and I, then this should be an error. Otherwise, it isn't. */ for (j = start; j < i; j++) { if (text[j] == '\\') { j++; continue; } if (text[j] == brace_arg_separator) { free_array (result); report_error ("missing `}'"); throw_to_top_level (); } } #endif free (preamble); /* Same as result[0]; see initialization. */ result[0] = savestring (text); return (result); } #if defined (SHELL) amble = substring (text, start, i); #else amble = (char *)xmalloc (1 + (i - start)); strncpy (amble, &text[start], (i - start)); amble[i - start] = '\0'; #endif #if defined (SHELL) /* If the amble does not contain an unquoted BRACE_ARG_SEPARATOR, then just return without doing any expansion. */ for (j = 0; amble[j]; j++) { if (amble[j] == '\\') { j++; continue; } if (amble[j] == brace_arg_separator) break; } if (!amble[j]) { free (amble); free (preamble); result[0] = savestring (text); return (result); } #endif /* SHELL */ postamble = &text[i + 1]; tack = expand_amble (amble); result = array_concat (result, tack); free (amble); free_array (tack); tack = brace_expand (postamble); result = array_concat (result, tack); free_array (tack); return (result); } /* Expand the text found inside of braces. We simply try to split the text at BRACE_ARG_SEPARATORs into separate strings. We then brace expand each slot which needs it, until there are no more slots which need it. */ static char ** expand_amble (text) char *text; { char **result, **partial; char *tem; int start, i, c; result = (char **)NULL; for (start = 0, i = 0, c = 1; c; start = ++i) { c = brace_gobbler (text, &i, brace_arg_separator); #if defined (SHELL) tem = substring (text, start, i); #else tem = (char *)xmalloc (1 + (i - start)); strncpy (tem, &text[start], (i - start)); tem[i- start] = '\0'; #endif partial = brace_expand (tem); if (!result) result = partial; else { register int lr = array_len (result); register int lp = array_len (partial); register int j; result = (char **)xrealloc (result, (1 + lp + lr) * sizeof (char *)); for (j = 0; j < lp; j++) result[lr + j] = partial[j]; result[lr + j] = (char *)NULL; free (partial); } free (tem); } return (result); } /* Start at INDEX, and skip characters in TEXT. Set INDEX to the index of the character matching SATISFY. This understands about quoting. Return the character that caused us to stop searching; this is either the same as SATISFY, or 0. */ static int brace_gobbler (text, indx, satisfy) char *text; int *indx; int satisfy; { register int i, c, quoted, level, pass_next; #if defined (SHELL) int si; char *t; #endif level = quoted = pass_next = 0; for (i = *indx; c = text[i]; i++) { if (pass_next) { pass_next = 0; continue; } /* A backslash escapes the next character. This allows backslash to escape the quote character in a double-quoted string. */ if (c == '\\' && (quoted == 0 || quoted == '"' || quoted == '`')) { pass_next = 1; continue; } if (quoted) { if (c == quoted) quoted = 0; continue; } if (c == '"' || c == '\'' || c == '`') { quoted = c; continue; } #if defined (SHELL) /* Pass new-style command substitutions through unchanged. */ if (c == '$' && text[i+1] == '(') /* ) */ { si = i + 2; t = extract_command_subst (text, &si); i = si; free (t); continue; } #endif if (c == satisfy && level == 0 && quoted == 0) { /* We ignore an open brace surrounded by whitespace, and also an open brace followed immediately by a close brace preceded by whitespace. */ if (c == '{' && ((!i || brace_whitespace (text[i - 1])) && (brace_whitespace (text[i + 1]) || text[i + 1] == '}'))) continue; #if defined (SHELL) /* If this is being compiled as part of bash, ignore the `{' in a `${}' construct */ if ((c != '{') || i == 0 || (text[i - 1] != '$')) #endif /* SHELL */ break; } if (c == '{') level++; else if (c == '}' && level) level--; } *indx = i; return (c); } /* Return a new array of strings which is the result of appending each string in ARR2 to each string in ARR1. The resultant array is len (arr1) * len (arr2) long. For convenience, ARR1 (and its contents) are free ()'ed. ARR1 can be NULL, in that case, a new version of ARR2 is returned. */ static char ** array_concat (arr1, arr2) char **arr1, **arr2; { register int i, j, len, len1, len2; register char **result; if (arr1 == 0) return (copy_array (arr2)); if (arr2 == 0) return (copy_array (arr1)); len1 = array_len (arr1); len2 = array_len (arr2); result = (char **)xmalloc ((1 + (len1 * len2)) * sizeof (char *)); len = 0; for (i = 0; i < len1; i++) { int strlen_1 = strlen (arr1[i]); for (j = 0; j < len2; j++) { result[len] = (char *)xmalloc (1 + strlen_1 + strlen (arr2[j])); strcpy (result[len], arr1[i]); strcpy (result[len] + strlen_1, arr2[j]); len++; } free (arr1[i]); } free (arr1); result[len] = (char *)NULL; return (result); } #if defined (TEST) #include fatal_error (format, arg1, arg2) char *format, *arg1, *arg2; { report_error (format, arg1, arg2); exit (1); } report_error (format, arg1, arg2) char *format, *arg1, *arg2; { fprintf (stderr, format, arg1, arg2); fprintf (stderr, "\n"); } main () { char example[256]; for (;;) { char **result; int i; fprintf (stderr, "brace_expand> "); if ((!fgets (example, 256, stdin)) || (strncmp (example, "quit", 4) == 0)) break; if (strlen (example)) example[strlen (example) - 1] = '\0'; result = brace_expand (example); for (i = 0; result[i]; i++) printf ("%s\n", result[i]); free_array (result); } } /* * Local variables: * compile-command: "gcc -g -Bstatic -DTEST -o brace_expand braces.c general.o" * end: */ #endif /* TEST */ #endif /* BRACE_EXPANSION */ /* copy_command.c -- copy a COMMAND structure. This is needed primarily for making function definitions, but I'm not sure that anyone else will need it. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #if defined (HAVE_UNISTD_H) # include #endif #include #include "shell.h" static PATTERN_LIST *copy_case_clause __P((PATTERN_LIST *)); static PATTERN_LIST *copy_case_clauses __P((PATTERN_LIST *)); static FOR_COM *copy_for_command __P((FOR_COM *)); #if defined (ARITH_FOR_COMMAND) static ARITH_FOR_COM *copy_arith_for_command __P((ARITH_FOR_COM *)); #endif static GROUP_COM *copy_group_command __P((GROUP_COM *)); static SUBSHELL_COM *copy_subshell_command __P((SUBSHELL_COM *)); static CASE_COM *copy_case_command __P((CASE_COM *)); static WHILE_COM *copy_while_command __P((WHILE_COM *)); static IF_COM *copy_if_command __P((IF_COM *)); #if defined (DPAREN_ARITHMETIC) static ARITH_COM *copy_arith_command __P((ARITH_COM *)); #endif #if defined (COND_COMMAND) static COND_COM *copy_cond_command __P((COND_COM *)); #endif static SIMPLE_COM *copy_simple_command __P((SIMPLE_COM *)); static FUNCTION_DEF *copy_function_def __P((FUNCTION_DEF *)); WORD_DESC * copy_word (w) WORD_DESC *w; { WORD_DESC *new_word; new_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC)); #if 1 new_word->flags = w->flags; #else FASTCOPY ((char *)w, (char *)new_word, sizeof (WORD_DESC)); #endif new_word->word = savestring (w->word); return (new_word); } /* Copy the chain of words in LIST. Return a pointer to the new chain. */ WORD_LIST * copy_word_list (list) WORD_LIST *list; { WORD_LIST *new_list, *temp; for (new_list = (WORD_LIST *)NULL; list; list = list->next) { temp = (WORD_LIST *)xmalloc (sizeof (WORD_LIST)); temp->next = new_list; new_list = temp; new_list->word = copy_word (list->word); } return (REVERSE_LIST (new_list, WORD_LIST *)); } static PATTERN_LIST * copy_case_clause (clause) PATTERN_LIST *clause; { PATTERN_LIST *new_clause; new_clause = (PATTERN_LIST *)xmalloc (sizeof (PATTERN_LIST)); new_clause->patterns = copy_word_list (clause->patterns); new_clause->action = copy_command (clause->action); return (new_clause); } static PATTERN_LIST * copy_case_clauses (clauses) PATTERN_LIST *clauses; { PATTERN_LIST *new_list, *new_clause; for (new_list = (PATTERN_LIST *)NULL; clauses; clauses = clauses->next) { new_clause = copy_case_clause (clauses); new_clause->next = new_list; new_list = new_clause; } return (REVERSE_LIST (new_list, PATTERN_LIST *)); } /* Copy a single redirect. */ REDIRECT * copy_redirect (redirect) REDIRECT *redirect; { REDIRECT *new_redirect; new_redirect = (REDIRECT *)xmalloc (sizeof (REDIRECT)); FASTCOPY ((char *)redirect, (char *)new_redirect, (sizeof (REDIRECT))); switch (redirect->instruction) { case r_reading_until: case r_deblank_reading_until: new_redirect->here_doc_eof = savestring (redirect->here_doc_eof); /*FALLTHROUGH*/ case r_appending_to: case r_output_direction: case r_input_direction: case r_inputa_direction: case r_err_and_out: case r_input_output: case r_output_force: case r_duplicating_input_word: case r_duplicating_output_word: new_redirect->redirectee.filename = copy_word (redirect->redirectee.filename); break; case r_duplicating_input: case r_duplicating_output: case r_close_this: break; } return (new_redirect); } REDIRECT * copy_redirects (list) REDIRECT *list; { REDIRECT *new_list, *temp; for (new_list = (REDIRECT *)NULL; list; list = list->next) { temp = copy_redirect (list); temp->next = new_list; new_list = temp; } return (REVERSE_LIST (new_list, REDIRECT *)); } static FOR_COM * copy_for_command (com) FOR_COM *com; { FOR_COM *new_for; new_for = (FOR_COM *)xmalloc (sizeof (FOR_COM)); new_for->flags = com->flags; new_for->name = copy_word (com->name); new_for->map_list = copy_word_list (com->map_list); new_for->action = copy_command (com->action); return (new_for); } #if defined (ARITH_FOR_COMMAND) static ARITH_FOR_COM * copy_arith_for_command (com) ARITH_FOR_COM *com; { ARITH_FOR_COM *new_arith_for; new_arith_for = (ARITH_FOR_COM *)xmalloc (sizeof (ARITH_FOR_COM)); new_arith_for->flags = com->flags; new_arith_for->line = com->line; new_arith_for->init = copy_word_list (com->init); new_arith_for->test = copy_word_list (com->test); new_arith_for->step = copy_word_list (com->step); new_arith_for->action = copy_command (com->action); return (new_arith_for); } #endif /* ARITH_FOR_COMMAND */ static GROUP_COM * copy_group_command (com) GROUP_COM *com; { GROUP_COM *new_group; new_group = (GROUP_COM *)xmalloc (sizeof (GROUP_COM)); new_group->command = copy_command (com->command); return (new_group); } static SUBSHELL_COM * copy_subshell_command (com) SUBSHELL_COM *com; { SUBSHELL_COM *new_subshell; new_subshell = (SUBSHELL_COM *)xmalloc (sizeof (SUBSHELL_COM)); new_subshell->command = copy_command (com->command); new_subshell->flags = com->flags; return (new_subshell); } static CASE_COM * copy_case_command (com) CASE_COM *com; { CASE_COM *new_case; new_case = (CASE_COM *)xmalloc (sizeof (CASE_COM)); new_case->flags = com->flags; new_case->word = copy_word (com->word); new_case->clauses = copy_case_clauses (com->clauses); return (new_case); } static WHILE_COM * copy_while_command (com) WHILE_COM *com; { WHILE_COM *new_while; new_while = (WHILE_COM *)xmalloc (sizeof (WHILE_COM)); new_while->flags = com->flags; new_while->test = copy_command (com->test); new_while->action = copy_command (com->action); return (new_while); } static IF_COM * copy_if_command (com) IF_COM *com; { IF_COM *new_if; new_if = (IF_COM *)xmalloc (sizeof (IF_COM)); new_if->flags = com->flags; new_if->test = copy_command (com->test); new_if->true_case = copy_command (com->true_case); new_if->false_case = copy_command (com->false_case); return (new_if); } #if defined (DPAREN_ARITHMETIC) static ARITH_COM * copy_arith_command (com) ARITH_COM *com; { ARITH_COM *new_arith; new_arith = (ARITH_COM *)xmalloc (sizeof (ARITH_COM)); new_arith->flags = com->flags; new_arith->exp = copy_word_list (com->exp); new_arith->line = com->line; return (new_arith); } #endif #if defined (COND_COMMAND) static COND_COM * copy_cond_command (com) COND_COM *com; { COND_COM *new_cond; new_cond = (COND_COM *)xmalloc (sizeof (COND_COM)); new_cond->flags = com->flags; new_cond->line = com->line; new_cond->type = com->type; new_cond->op = com->op ? copy_word (com->op) : com->op; new_cond->left = com->left ? copy_cond_command (com->left) : (COND_COM *)NULL; new_cond->right = com->right ? copy_cond_command (com->right) : (COND_COM *)NULL; return (new_cond); } #endif static SIMPLE_COM * copy_simple_command (com) SIMPLE_COM *com; { SIMPLE_COM *new_simple; new_simple = (SIMPLE_COM *)xmalloc (sizeof (SIMPLE_COM)); new_simple->flags = com->flags; new_simple->words = copy_word_list (com->words); new_simple->redirects = copy_redirects (com->redirects); new_simple->line = com->line; return (new_simple); } static FUNCTION_DEF * copy_function_def (com) FUNCTION_DEF *com; { FUNCTION_DEF *new_def; new_def = (FUNCTION_DEF *)xmalloc (sizeof (FUNCTION_DEF)); new_def->name = copy_word (com->name); new_def->command = copy_command (com->command); new_def->flags = com->flags; new_def->line = com->line; return (new_def); } /* Copy the command structure in COMMAND. Return a pointer to the copy. Don't you forget to dispose_command () on this pointer later! */ COMMAND * copy_command (command) COMMAND *command; { COMMAND *new_command; if (command == NULL) return (command); new_command = (COMMAND *)xmalloc (sizeof (COMMAND)); FASTCOPY ((char *)command, (char *)new_command, sizeof (COMMAND)); new_command->flags = command->flags; new_command->line = command->line; if (command->redirects) new_command->redirects = copy_redirects (command->redirects); switch (command->type) { case cm_for: new_command->value.For = copy_for_command (command->value.For); break; #if defined (ARITH_FOR_COMMAND) case cm_arith_for: new_command->value.ArithFor = copy_arith_for_command (command->value.ArithFor); break; #endif #if defined (SELECT_COMMAND) case cm_select: new_command->value.Select = (SELECT_COM *)copy_for_command ((FOR_COM *)command->value.Select); break; #endif case cm_group: new_command->value.Group = copy_group_command (command->value.Group); break; case cm_subshell: new_command->value.Subshell = copy_subshell_command (command->value.Subshell); break; case cm_case: new_command->value.Case = copy_case_command (command->value.Case); break; case cm_until: case cm_while: new_command->value.While = copy_while_command (command->value.While); break; case cm_if: new_command->value.If = copy_if_command (command->value.If); break; #if defined (DPAREN_ARITHMETIC) case cm_arith: new_command->value.Arith = copy_arith_command (command->value.Arith); break; #endif #if defined (COND_COMMAND) case cm_cond: new_command->value.Cond = copy_cond_command (command->value.Cond); break; #endif case cm_simple: new_command->value.Simple = copy_simple_command (command->value.Simple); break; case cm_connection: { CONNECTION *new_connection; new_connection = (CONNECTION *)xmalloc (sizeof (CONNECTION)); new_connection->connector = command->value.Connection->connector; new_connection->first = copy_command (command->value.Connection->first); new_connection->second = copy_command (command->value.Connection->second); new_command->value.Connection = new_connection; break; } case cm_function_def: new_command->value.Function_def = copy_function_def (command->value.Function_def); break; } return (new_command); } /* dispose_command.c -- dispose of a COMMAND structure. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #if defined (HAVE_UNISTD_H) # include #endif #include "bashansi.h" #include "shell.h" /* Dispose of the command structure passed. */ void dispose_command (command) COMMAND *command; { if (command == 0) return; if (command->redirects) dispose_redirects (command->redirects); switch (command->type) { case cm_for: #if defined (SELECT_COMMAND) case cm_select: #endif { register FOR_COM *c; #if defined (SELECT_COMMAND) if (command->type == cm_select) c = (FOR_COM *)command->value.Select; else #endif c = command->value.For; dispose_word (c->name); dispose_words (c->map_list); dispose_command (c->action); free (c); break; } #if defined (ARITH_FOR_COMMAND) case cm_arith_for: { register ARITH_FOR_COM *c; c = command->value.ArithFor; dispose_words (c->init); dispose_words (c->test); dispose_words (c->step); dispose_command (c->action); free (c); break; } #endif /* ARITH_FOR_COMMAND */ case cm_group: { dispose_command (command->value.Group->command); free (command->value.Group); break; } case cm_subshell: { dispose_command (command->value.Subshell->command); free (command->value.Subshell); break; } case cm_case: { register CASE_COM *c; PATTERN_LIST *t, *p; c = command->value.Case; dispose_word (c->word); for (p = c->clauses; p; ) { dispose_words (p->patterns); dispose_command (p->action); t = p; p = p->next; free (t); } free (c); break; } case cm_until: case cm_while: { register WHILE_COM *c; c = command->value.While; dispose_command (c->test); dispose_command (c->action); free (c); break; } case cm_if: { register IF_COM *c; c = command->value.If; dispose_command (c->test); dispose_command (c->true_case); dispose_command (c->false_case); free (c); break; } case cm_simple: { register SIMPLE_COM *c; c = command->value.Simple; dispose_words (c->words); dispose_redirects (c->redirects); free (c); break; } case cm_connection: { register CONNECTION *c; c = command->value.Connection; dispose_command (c->first); dispose_command (c->second); free (c); break; } #if defined (DPAREN_ARITHMETIC) case cm_arith: { register ARITH_COM *c; c = command->value.Arith; dispose_words (c->exp); free (c); break; } #endif /* DPAREN_ARITHMETIC */ #if defined (COND_COMMAND) case cm_cond: { register COND_COM *c; c = command->value.Cond; dispose_cond_node (c); break; } #endif /* COND_COMMAND */ case cm_function_def: { register FUNCTION_DEF *c; c = command->value.Function_def; dispose_word (c->name); dispose_command (c->command); free (c); break; } default: command_error ("dispose_command", CMDERR_BADTYPE, command->type, 0); break; } free (command); } #if defined (COND_COMMAND) /* How to free a node in a conditional command. */ void dispose_cond_node (cond) COND_COM *cond; { if (cond) { if (cond->left) dispose_cond_node (cond->left); if (cond->right) dispose_cond_node (cond->right); if (cond->op) dispose_word (cond->op); free (cond); } } #endif /* COND_COMMAND */ /* How to free a WORD_DESC. */ void dispose_word (w) WORD_DESC *w; { FREE (w->word); free (w); } /* How to get rid of a linked list of words. A WORD_LIST. */ void dispose_words (list) WORD_LIST *list; { WORD_LIST *t; while (list) { t = list; list = list->next; dispose_word (t->word); free (t); } } #ifdef INCLUDE_UNUSED /* How to dispose of an array of pointers to char. This is identical to free_array in stringlib.c. */ void dispose_word_array (array) char **array; { register int count; if (array == 0) return; for (count = 0; array[count]; count++) free (array[count]); free (array); } #endif /* How to dispose of an list of redirections. A REDIRECT. */ void dispose_redirects (list) REDIRECT *list; { register REDIRECT *t; while (list) { t = list; list = list->next; switch (t->instruction) { case r_reading_until: case r_deblank_reading_until: free (t->here_doc_eof); /*FALLTHROUGH*/ case r_output_direction: case r_input_direction: case r_inputa_direction: case r_appending_to: case r_err_and_out: case r_input_output: case r_output_force: case r_duplicating_input_word: case r_duplicating_output_word: dispose_word (t->redirectee.filename); /* FALLTHROUGH */ default: break; } free (t); } } /* error.c -- Functions for handling errors. */ /* Copyright (C) 1993 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #include #if defined (HAVE_UNISTD_H) # include #endif #if defined (PREFER_STDARG) # include #else # if defined (PREFER_VARARGS) # include # endif #endif #include #include #if !defined (errno) extern int errno; #endif /* !errno */ #include "bashansi.h" #include "flags.h" #include "error.h" #include "command.h" #include "general.h" #include "externs.h" #include "input.h" #if defined (HISTORY) # include "bashhist.h" #endif extern int interactive_shell, interactive; extern char *dollar_vars[]; extern char *shell_name; #if defined (JOB_CONTROL) extern pid_t shell_pgrp; extern int give_terminal_to __P((pid_t, int)); #endif /* JOB_CONTROL */ /* The current maintainer of the shell. You change this in the Makefile. */ #if !defined (MAINTAINER) #define MAINTAINER "bash-maintainers@gnu.org" #endif char *the_current_maintainer = MAINTAINER; /* Return the name of the shell or the shell script for error reporting. */ char * get_name_for_error () { char *name; name = (char *)NULL; if (interactive_shell == 0) name = dollar_vars[0]; if (name == 0 && shell_name && *shell_name) name = base_pathname (shell_name); if (name == 0) #if defined (PROGRAM) name = PROGRAM; #else name = "bash"; #endif return (name); } /* Report an error having to do with FILENAME. This does not use sys_error so the filename is not interpreted as a printf-style format string. */ void file_error (filename) const char *filename; { report_error ("%s: %s", filename, strerror (errno)); } #if !defined (USE_VARARGS) void programming_error (reason, arg1, arg2, arg3, arg4, arg5) char *reason; { char *h; #if defined (JOB_CONTROL) give_terminal_to (shell_pgrp); #endif /* JOB_CONTROL */ report_error (reason, arg1, arg2); #if defined (HISTORY) if (remember_on_history) { h = last_history_line (); fprintf (stderr, "last command: %s\n", h ? h : "(null)"); } #endif #if 0 fprintf (stderr, "Report this to %s\n", the_current_maintainer); #endif fprintf (stderr, "Stopping myself..."); fflush (stderr); abort (); } void report_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); if (exit_immediately_on_error) exit (1); } void parser_error (lineno, format, arg1, arg2, arg3, arg4, arg5); int lineno; char *format; va_dcl { char *ename, *iname; ename = get_name_for_error (); iname = bash_input.name ? bash_input.name : "stdin"; if (interactive) fprintf (stderr, "%s: ", ename); else if (interactive_shell) fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); else if (STREQ (ename, iname)) fprintf (stderr, "%s: line %d: ", ename, lineno); else fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); if (exit_immediately_on_error) exit (2); } void fatal_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); exit (2); } void internal_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); } void internal_warning (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: warning: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); } void sys_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "%s: ", get_name_for_error ()); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, ": %s\n", strerror (errno)); } #else /* We have VARARGS support, so use it. */ void #if defined (PREFER_STDARG) programming_error (const char *format, ...) #else programming_error (format, va_alist) const char *format; va_dcl #endif { va_list args; char *h; #if defined (JOB_CONTROL) give_terminal_to (shell_pgrp, 0); #endif /* JOB_CONTROL */ #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); #if defined (HISTORY) if (remember_on_history) { h = last_history_line (); fprintf (stderr, "last command: %s\n", h ? h : "(null)"); } #endif #if 0 fprintf (stderr, "Report this to %s\n", the_current_maintainer); #endif fprintf (stderr, "Stopping myself..."); fflush (stderr); abort (); } void #if defined (PREFER_STDARG) report_error (const char *format, ...) #else report_error (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); if (exit_immediately_on_error) exit (1); } void #if defined (PREFER_STDARG) fatal_error (const char *format, ...) #else fatal_error (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); exit (2); } void #if defined (PREFER_STDARG) internal_error (const char *format, ...) #else internal_error (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); } void #if defined (PREFER_STDARG) internal_warning (const char *format, ...) #else internal_warning (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: warning: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); } void #if defined (PREFER_STDARG) sys_error (const char *format, ...) #else sys_error (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf (stderr, "%s: ", get_name_for_error ()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, ": %s\n", strerror (errno)); va_end (args); } /* An error from the parser takes the general form shell_name: input file name: line number: message The input file name and line number are omitted if the shell is currently interactive. If the shell is not currently interactive, the input file name is inserted only if it is different from the shell name. */ void #if defined (PREFER_STDARG) parser_error (int lineno, const char *format, ...) #else parser_error (lineno, format, va_alist) int lineno; const char *format; va_dcl #endif { va_list args; char *ename, *iname; ename = get_name_for_error (); iname = bash_input.name ? bash_input.name : "stdin"; if (interactive) fprintf (stderr, "%s: ", ename); else if (interactive_shell) fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); else if (STREQ (ename, iname)) fprintf (stderr, "%s: line %d: ", ename, lineno); else fprintf (stderr, "%s: %s: line %d: ", ename, iname, lineno); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); if (exit_immediately_on_error) exit (2); } #ifdef DEBUG void #if defined (PREFER_STDARG) itrace (const char *format, ...) #else itrace (format, va_alist) const char *format; va_dcl #endif { va_list args; fprintf(stderr, "TRACE: pid %ld: ", (long)getpid()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (stderr, format, args); fprintf (stderr, "\n"); va_end (args); fflush(stderr); } /* A trace function for silent debugging -- doesn't require a control terminal. */ void #if defined (PREFER_STDARG) trace (const char *format, ...) #else trace (format, va_alist) const char *format; va_dcl #endif { va_list args; static FILE *tracefp = (FILE *)NULL; if (tracefp == NULL) tracefp = fopen("/tmp/bash-trace.log", "a+"); if (tracefp == NULL) tracefp = stderr; else fcntl (fileno (tracefp), F_SETFD, 1); /* close-on-exec */ fprintf(tracefp, "TRACE: pid %ld: ", (long)getpid()); #if defined (PREFER_STDARG) va_start (args, format); #else va_start (args); #endif vfprintf (tracefp, format, args); fprintf (tracefp, "\n"); va_end (args); fflush(tracefp); } #endif /* USE_VARARGS */ #endif /* DEBUG */ static char *cmd_error_table[] = { "unknown command error", /* CMDERR_DEFAULT */ "bad command type", /* CMDERR_BADTYPE */ "bad connector", /* CMDERR_BADCONN */ "bad jump", /* CMDERR_BADJUMP */ 0 }; void command_error (func, code, e, flags) const char *func; int code, e, flags; /* flags currently unused */ { if (code > CMDERR_LAST) code = CMDERR_DEFAULT; programming_error ("%s: %s: %d", func, cmd_error_table[code], e); } char * command_errstr (code) int code; { if (code > CMDERR_LAST) code = CMDERR_DEFAULT; return (cmd_error_table[code]); } /* eval.c -- reading and evaluating commands. */ /* Copyright (C) 1996 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "bashansi.h" #include #include "shell.h" #include "flags.h" #include "trap.h" #include "builtins/common.h" #include "input.h" #include "execute_cmd.h" #if defined (HISTORY) # include "bashhist.h" #endif extern int EOF_reached; extern int indirection_level; extern int posixly_correct; extern int subshell_environment, running_under_emacs; extern int last_command_exit_value, stdin_redir; extern int need_here_doc; extern int current_command_number, current_command_line_count, line_number; extern int expand_aliases; /* Read and execute commands until EOF is reached. This assumes that the input source has already been initialized. */ int reader_loop () { int our_indirection_level; COMMAND *current_command = (COMMAND *)NULL; USE_VAR(current_command); our_indirection_level = ++indirection_level; while (EOF_Reached == 0) { int code; code = setjmp (top_level); #if defined (PROCESS_SUBSTITUTION) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ if (interactive_shell && signal_is_ignored (SIGINT) == 0) set_signal_handler (SIGINT, sigint_sighandler); if (code != NOT_JUMPED) { indirection_level = our_indirection_level; switch (code) { /* Some kind of throw to top_level has occured. */ case FORCE_EOF: case EXITPROG: current_command = (COMMAND *)NULL; if (exit_immediately_on_error) variable_context = 0; /* not in a function */ EOF_Reached = EOF; goto exec_done; case DISCARD: last_command_exit_value = 1; if (subshell_environment) { current_command = (COMMAND *)NULL; EOF_Reached = EOF; goto exec_done; } /* Obstack free command elements, etc. */ if (current_command) { dispose_command (current_command); current_command = (COMMAND *)NULL; } break; default: command_error ("reader_loop", CMDERR_BADJUMP, code, 0); } } executing = 0; dispose_used_env_vars (); #if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA) /* Attempt to reclaim memory allocated with alloca (). */ (void) alloca (0); #endif if (read_command () == 0) { if (interactive_shell == 0 && read_but_dont_execute) { last_command_exit_value = EXECUTION_SUCCESS; dispose_command (global_command); global_command = (COMMAND *)NULL; } else if (current_command = global_command) { global_command = (COMMAND *)NULL; current_command_number++; executing = 1; stdin_redir = 0; execute_command (current_command); exec_done: if (current_command) { dispose_command (current_command); current_command = (COMMAND *)NULL; } QUIT; } } else { /* Parse error, maybe discard rest of stream if not interactive. */ if (interactive == 0) EOF_Reached = EOF; } if (just_one_command) EOF_Reached = EOF; } indirection_level--; return (last_command_exit_value); } static sighandler alrm_catcher(i) int i; { printf ("\007timed out waiting for input: auto-logout\n"); jump_to_top_level (EXITPROG); SIGRETURN (0); } /* Send an escape sequence to emacs term mode to tell it the current working directory. */ static void send_pwd_to_eterm () { char *pwd; pwd = get_string_value ("PWD"); if (pwd == 0) pwd = get_working_directory ("eterm"); fprintf (stderr, "\032/%s\n", pwd); } /* Call the YACC-generated parser and return the status of the parse. Input is read from the current input stream (bash_input). yyparse leaves the parsed command in the global variable GLOBAL_COMMAND. This is where PROMPT_COMMAND is executed. */ int parse_command () { int r; char *command_to_execute; need_here_doc = 0; run_pending_traps (); /* Allow the execution of a random command just before the printing of each primary prompt. If the shell variable PROMPT_COMMAND is set then the value of it is the command to execute. */ if (interactive && bash_input.type != st_string) { command_to_execute = get_string_value ("PROMPT_COMMAND"); if (command_to_execute) execute_prompt_command (command_to_execute); if (running_under_emacs == 2) send_pwd_to_eterm (); /* Yuck */ } current_command_line_count = 0; r = yyparse (); if (need_here_doc) gather_here_documents (); return (r); } /* Read and parse a command, returning the status of the parse. The command is left in the globval variable GLOBAL_COMMAND for use by reader_loop. This is where the shell timeout code is executed. */ int read_command () { SHELL_VAR *tmout_var; int tmout_len, result; SigHandler *old_alrm; set_current_prompt_level (1); global_command = (COMMAND *)NULL; /* Only do timeouts if interactive. */ tmout_var = (SHELL_VAR *)NULL; tmout_len = 0; old_alrm = (SigHandler *)NULL; if (interactive) { tmout_var = find_variable ("TMOUT"); if (tmout_var && tmout_var->value) { tmout_len = atoi (tmout_var->value); if (tmout_len > 0) { old_alrm = set_signal_handler (SIGALRM, alrm_catcher); alarm (tmout_len); } } } QUIT; current_command_line_count = 0; result = parse_command (); if (interactive && tmout_var && (tmout_len > 0)) { alarm(0); set_signal_handler (SIGALRM, old_alrm); } return (result); } /* execute_command.c -- Execute a COMMAND structure. */ /* Copyright (C) 1987,1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) #pragma alloca #endif /* _AIX && RISC6000 && !__GNUC__ */ #include #include "chartypes.h" #include "bashtypes.h" #ifndef _MINIX # include #endif #include "filecntl.h" #include "posixstat.h" #include #ifndef _MINIX # include #endif #if defined (HAVE_UNISTD_H) # include #endif #include "posixtime.h" #if defined (HAVE_SYS_RESOURCE_H) && !defined (RLIMTYPE) # include #endif #if defined (HAVE_SYS_TIMES_H) && defined (HAVE_TIMES) # include #endif #include #if !defined (errno) extern int errno; #endif #include "bashansi.h" #include "memalloc.h" #include "shell.h" #include /* use <...> so we pick it up from the build directory */ #include "flags.h" #include "builtins.h" #include "hashlib.h" #include "jobs.h" #include "execute_cmd.h" #include "findcmd.h" #include "redir.h" #include "trap.h" #include "pathexp.h" #include "hashcmd.h" #if defined (COND_COMMAND) # include "test.h" #endif #include "builtins/common.h" #include "builtins/builtext.h" /* list of builtins */ #include #include #if defined (BUFFERED_INPUT) # include "input.h" #endif #if defined (ALIAS) # include "alias.h" #endif #if defined (HISTORY) # include "bashhist.h" #endif extern int posixly_correct; extern int breaking, continuing, loop_level; extern int expand_aliases; extern int parse_and_execute_level, running_trap, trap_line_number; extern int command_string_index, line_number; extern int dot_found_in_search; extern int already_making_children; extern char **temporary_env, **function_env, **builtin_env; extern char *the_printed_command, *shell_name; extern pid_t last_command_subst_pid; extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin; extern char **subshell_argv, **subshell_envp; extern int subshell_argc; #if 0 extern char *glob_argv_flags; #endif extern int close __P((int)); /* Static functions defined and used in this file. */ static void close_pipes __P((int, int)); static void do_piping __P((int, int)); static void bind_lastarg __P((char *)); static int shell_control_structure __P((enum command_type)); static void cleanup_redirects __P((REDIRECT *)); #if defined (JOB_CONTROL) static int restore_signal_mask __P((sigset_t *)); #endif static void async_redirect_stdin __P((void)); static int builtin_status __P((int)); static int execute_for_command __P((FOR_COM *)); #if defined (SELECT_COMMAND) static int print_index_and_element __P((int, int, WORD_LIST *)); static void indent __P((int, int)); static void print_select_list __P((WORD_LIST *, int, int, int)); static char *select_query __P((WORD_LIST *, int, char *)); static int execute_select_command __P((SELECT_COM *)); #endif #if defined (DPAREN_ARITHMETIC) static int execute_arith_command __P((ARITH_COM *)); #endif #if defined (COND_COMMAND) static int execute_cond_node __P((COND_COM *)); static int execute_cond_command __P((COND_COM *)); #endif #if defined (COMMAND_TIMING) static int mkfmt __P((char *, int, int, time_t, int)); static void print_formatted_time __P((FILE *, char *, time_t, int, time_t, int, time_t, int, int)); static int time_command __P((COMMAND *, int, int, int, struct fd_bitmap *)); #endif #if defined (ARITH_FOR_COMMAND) static long eval_arith_for_expr __P((WORD_LIST *, int *)); static int execute_arith_for_command __P((ARITH_FOR_COM *)); #endif static int execute_case_command __P((CASE_COM *)); static int execute_while_command __P((WHILE_COM *)); static int execute_until_command __P((WHILE_COM *)); static int execute_while_or_until __P((WHILE_COM *, int)); static int execute_if_command __P((IF_COM *)); static int execute_null_command __P((REDIRECT *, int, int, int, pid_t)); static void fix_assignment_words __P((WORD_LIST *)); static int execute_simple_command __P((SIMPLE_COM *, int, int, int, struct fd_bitmap *)); static int execute_builtin __P((sh_builtin_func_t *, WORD_LIST *, int, int)); static int execute_function __P((SHELL_VAR *, WORD_LIST *, int, struct fd_bitmap *, int, int)); static int execute_builtin_or_function __P((WORD_LIST *, sh_builtin_func_t *, SHELL_VAR *, REDIRECT *, struct fd_bitmap *, int)); static void execute_subshell_builtin_or_function __P((WORD_LIST *, REDIRECT *, sh_builtin_func_t *, SHELL_VAR *, int, int, int, struct fd_bitmap *, int)); static void execute_disk_command __P((WORD_LIST *, REDIRECT *, char *, int, int, int, struct fd_bitmap *, int)); static char *getinterp __P((char *, int, int *)); static void initialize_subshell __P((void)); static int execute_in_subshell __P((COMMAND *, int, int, int, struct fd_bitmap *)); static int execute_pipeline __P((COMMAND *, int, int, int, struct fd_bitmap *)); static int execute_connection __P((COMMAND *, int, int, int, struct fd_bitmap *)); static int execute_intern_function __P((WORD_DESC *, COMMAND *)); /* The line number that the currently executing function starts on. */ static int function_line_number; /* Set to 1 if fd 0 was the subject of redirection to a subshell. Global so that reader_loop can set it to zero before executing a command. */ int stdin_redir; /* The name of the command that is currently being executed. `test' needs this, for example. */ char *this_command_name; static COMMAND *currently_executing_command; struct stat SB; /* used for debugging */ static int special_builtin_failed; /* For catching RETURN in a function. */ int return_catch_flag; int return_catch_value; procenv_t return_catch; /* The value returned by the last synchronous command. */ int last_command_exit_value; /* The list of redirections to perform which will undo the redirections that I made in the shell. */ REDIRECT *redirection_undo_list = (REDIRECT *)NULL; /* The list of redirections to perform which will undo the internal redirections performed by the `exec' builtin. These are redirections that must be undone even when exec discards redirection_undo_list. */ REDIRECT *exec_redirection_undo_list = (REDIRECT *)NULL; /* Non-zero if we have just forked and are currently running in a subshell environment. */ int subshell_environment; /* Currently-executing shell function. */ SHELL_VAR *this_shell_function; struct fd_bitmap *current_fds_to_close = (struct fd_bitmap *)NULL; #define FD_BITMAP_DEFAULT_SIZE 32 /* Functions to allocate and deallocate the structures used to pass information from the shell to its children about file descriptors to close. */ struct fd_bitmap * new_fd_bitmap (size) int size; { struct fd_bitmap *ret; ret = (struct fd_bitmap *)xmalloc (sizeof (struct fd_bitmap)); ret->size = size; if (size) { ret->bitmap = (char *)xmalloc (size); bzero (ret->bitmap, size); } else ret->bitmap = (char *)NULL; return (ret); } void dispose_fd_bitmap (fdbp) struct fd_bitmap *fdbp; { FREE (fdbp->bitmap); free (fdbp); } void close_fd_bitmap (fdbp) struct fd_bitmap *fdbp; { register int i; if (fdbp) { for (i = 0; i < fdbp->size; i++) if (fdbp->bitmap[i]) { close (i); fdbp->bitmap[i] = 0; } } } /* Return the line number of the currently executing command. */ int executing_line_number () { if (executing && variable_context == 0 && currently_executing_command && currently_executing_command->type == cm_simple) return currently_executing_command->value.Simple->line; else if (running_trap) return trap_line_number; else return line_number; } /* Execute the command passed in COMMAND. COMMAND is exactly what read_command () places into GLOBAL_COMMAND. See "command.h" for the details of the command structure. EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible return values. Executing a command with nothing in it returns EXECUTION_SUCCESS. */ int execute_command (command) COMMAND *command; { struct fd_bitmap *bitmap; int result; current_fds_to_close = (struct fd_bitmap *)NULL; bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE); begin_unwind_frame ("execute-command"); add_unwind_protect (dispose_fd_bitmap, (char *)bitmap); /* Just do the command, but not asynchronously. */ result = execute_command_internal (command, 0, NO_PIPE, NO_PIPE, bitmap); dispose_fd_bitmap (bitmap); discard_unwind_frame ("execute-command"); #if defined (PROCESS_SUBSTITUTION) /* don't unlink fifos if we're in a shell function; wait until the function returns. */ if (variable_context == 0) unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ return (result); } /* Return 1 if TYPE is a shell control structure type. */ static int shell_control_structure (type) enum command_type type; { switch (type) { case cm_for: #if defined (ARITH_FOR_COMMAND) case cm_arith_for: #endif #if defined (SELECT_COMMAND) case cm_select: #endif #if defined (DPAREN_ARITHMETIC) case cm_arith: #endif #if defined (COND_COMMAND) case cm_cond: #endif case cm_case: case cm_while: case cm_until: case cm_if: case cm_group: return (1); default: return (0); } } /* A function to use to unwind_protect the redirection undo list for loops. */ static void cleanup_redirects (list) REDIRECT *list; { do_redirections (list, 1, 0, 0); dispose_redirects (list); } #if 0 /* Function to unwind_protect the redirections for functions and builtins. */ static void cleanup_func_redirects (list) REDIRECT *list; { do_redirections (list, 1, 0, 0); } #endif void dispose_exec_redirects () { if (exec_redirection_undo_list) { dispose_redirects (exec_redirection_undo_list); exec_redirection_undo_list = (REDIRECT *)NULL; } } #if defined (JOB_CONTROL) /* A function to restore the signal mask to its proper value when the shell is interrupted or errors occur while creating a pipeline. */ static int restore_signal_mask (set) sigset_t *set; { return (sigprocmask (SIG_SETMASK, set, (sigset_t *)NULL)); } #endif /* JOB_CONTROL */ #ifdef DEBUG /* A debugging function that can be called from gdb, for instance. */ void open_files () { register int i; int f, fd_table_size; fd_table_size = getdtablesize (); fprintf (stderr, "pid %ld open files:", (long)getpid ()); for (i = 3; i < fd_table_size; i++) { if ((f = fcntl (i, F_GETFD, 0)) != -1) fprintf (stderr, " %d (%s)", i, f ? "close" : "open"); } fprintf (stderr, "\n"); } #endif static void async_redirect_stdin () { int fd; fd = open ("/dev/null", O_RDONLY); if (fd > 0) { dup2 (fd, 0); close (fd); } else if (fd < 0) internal_error ("cannot redirect standard input from /dev/null: %s", strerror (errno)); } #define DESCRIBE_PID(pid) do { if (interactive) describe_pid (pid); } while (0) /* Execute the command passed in COMMAND, perhaps doing it asynchrounously. COMMAND is exactly what read_command () places into GLOBAL_COMMAND. ASYNCHROUNOUS, if non-zero, says to do this command in the background. PIPE_IN and PIPE_OUT are file descriptors saying where input comes from and where it goes. They can have the value of NO_PIPE, which means I/O is stdin/stdout. FDS_TO_CLOSE is a list of file descriptors to close once the child has been forked. This list often contains the unusable sides of pipes, etc. EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible return values. Executing a command with nothing in it returns EXECUTION_SUCCESS. */ int execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous; int pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { int exec_result, invert, ignore_return, was_debug_trap, was_error_trap; REDIRECT *my_undo_list, *exec_undo_list; volatile pid_t last_pid; if (command == 0 || breaking || continuing || read_but_dont_execute) return (EXECUTION_SUCCESS); run_pending_traps (); if (running_trap == 0) currently_executing_command = command; invert = (command->flags & CMD_INVERT_RETURN) != 0; /* If we're inverting the return value and `set -e' has been executed, we don't want a failing command to inadvertently cause the shell to exit. */ if (exit_immediately_on_error && invert) /* XXX */ command->flags |= CMD_IGNORE_RETURN; /* XXX */ exec_result = EXECUTION_SUCCESS; /* If a command was being explicitly run in a subshell, or if it is a shell control-structure, and it has a pipe, then we do the command in a subshell. */ if (command->type == cm_subshell && (command->flags & CMD_NO_FORK)) return (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)); if (command->type == cm_subshell || (command->flags & (CMD_WANT_SUBSHELL|CMD_FORCE_SUBSHELL)) || (shell_control_structure (command->type) && (pipe_out != NO_PIPE || pipe_in != NO_PIPE || asynchronous))) { pid_t paren_pid; /* Fork a subshell, turn off the subshell bit, turn off job control and call execute_command () on the command again. */ paren_pid = make_child (savestring (make_command_string (command)), asynchronous); if (paren_pid == 0) exit (execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close)); /* NOTREACHED */ else { close_pipes (pipe_in, pipe_out); #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) unlink_fifo_list (); #endif /* If we are part of a pipeline, and not the end of the pipeline, then we should simply return and let the last command in the pipe be waited for. If we are not in a pipeline, or are the last command in the pipeline, then we wait for the subshell and return its exit status as usual. */ if (pipe_out != NO_PIPE) return (EXECUTION_SUCCESS); stop_pipeline (asynchronous, (COMMAND *)NULL); if (asynchronous == 0) { last_command_exit_value = wait_for (paren_pid); /* If we have to, invert the return value. */ if (invert) exec_result = ((last_command_exit_value == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS); else exec_result = last_command_exit_value; return (last_command_exit_value = exec_result); } else { DESCRIBE_PID (paren_pid); run_pending_traps (); return (EXECUTION_SUCCESS); } } } #if defined (COMMAND_TIMING) if (command->flags & CMD_TIME_PIPELINE) { if (asynchronous) { command->flags |= CMD_FORCE_SUBSHELL; exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); } else { exec_result = time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close); if (running_trap == 0) currently_executing_command = (COMMAND *)NULL; } return (exec_result); } #endif /* COMMAND_TIMING */ if (shell_control_structure (command->type) && command->redirects) stdin_redir = stdin_redirects (command->redirects); /* Handle WHILE FOR CASE etc. with redirections. (Also '&' input redirection.) */ if (do_redirections (command->redirects, 1, 1, 0) != 0) { cleanup_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; dispose_exec_redirects (); return (EXECUTION_FAILURE); } if (redirection_undo_list) { my_undo_list = (REDIRECT *)copy_redirects (redirection_undo_list); dispose_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; } else my_undo_list = (REDIRECT *)NULL; if (exec_redirection_undo_list) { exec_undo_list = (REDIRECT *)copy_redirects (exec_redirection_undo_list); dispose_redirects (exec_redirection_undo_list); exec_redirection_undo_list = (REDIRECT *)NULL; } else exec_undo_list = (REDIRECT *)NULL; if (my_undo_list || exec_undo_list) begin_unwind_frame ("loop_redirections"); if (my_undo_list) add_unwind_protect ((Function *)cleanup_redirects, my_undo_list); if (exec_undo_list) add_unwind_protect ((Function *)dispose_redirects, exec_undo_list); ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; QUIT; switch (command->type) { case cm_simple: { /* We can't rely on this variable retaining its value across a call to execute_simple_command if a longjmp occurs as the result of a `return' builtin. This is true for sure with gcc. */ last_pid = last_made_pid; was_debug_trap = signal_is_trapped (DEBUG_TRAP) && signal_is_ignored (DEBUG_TRAP) == 0; was_error_trap = signal_is_trapped (ERROR_TRAP) && signal_is_ignored (ERROR_TRAP) == 0; if (ignore_return && command->value.Simple) command->value.Simple->flags |= CMD_IGNORE_RETURN; if (command->flags & CMD_STDIN_REDIR) command->value.Simple->flags |= CMD_STDIN_REDIR; exec_result = execute_simple_command (command->value.Simple, pipe_in, pipe_out, asynchronous, fds_to_close); /* The temporary environment should be used for only the simple command immediately following its definition. */ dispose_used_env_vars (); #if (defined (ultrix) && defined (mips)) || defined (C_ALLOCA) /* Reclaim memory allocated with alloca () on machines which may be using the alloca emulation code. */ (void) alloca (0); #endif /* (ultrix && mips) || C_ALLOCA */ /* If we forked to do the command, then we must wait_for () the child. */ /* XXX - this is something to watch out for if there are problems when the shell is compiled without job control. */ if (already_making_children && pipe_out == NO_PIPE && last_pid != last_made_pid) { stop_pipeline (asynchronous, (COMMAND *)NULL); if (asynchronous) { DESCRIBE_PID (last_made_pid); } else #if !defined (JOB_CONTROL) /* Do not wait for asynchronous processes started from startup files. */ if (last_made_pid != last_asynchronous_pid) #endif /* When executing a shell function that executes other commands, this causes the last simple command in the function to be waited for twice. */ exec_result = wait_for (last_made_pid); #if defined (RECYCLES_PIDS) /* LynxOS, for one, recycles pids very quickly -- so quickly that a new process may have the same pid as the last one created. This has been reported to fix the problem. */ if (exec_result == 0) last_made_pid = NO_PID; #endif } } if (was_debug_trap) { last_command_exit_value = exec_result; run_debug_trap (); } if (was_error_trap && ignore_return == 0 && invert == 0 && exec_result != EXECUTION_SUCCESS) { last_command_exit_value = exec_result; run_error_trap (); } if (ignore_return == 0 && invert == 0 && ((posixly_correct && interactive == 0 && special_builtin_failed) || (exit_immediately_on_error && (exec_result != EXECUTION_SUCCESS)))) { last_command_exit_value = exec_result; run_pending_traps (); jump_to_top_level (EXITPROG); } break; case cm_for: if (ignore_return) command->value.For->flags |= CMD_IGNORE_RETURN; exec_result = execute_for_command (command->value.For); break; #if defined (ARITH_FOR_COMMAND) case cm_arith_for: if (ignore_return) command->value.ArithFor->flags |= CMD_IGNORE_RETURN; exec_result = execute_arith_for_command (command->value.ArithFor); break; #endif #if defined (SELECT_COMMAND) case cm_select: if (ignore_return) command->value.Select->flags |= CMD_IGNORE_RETURN; exec_result = execute_select_command (command->value.Select); break; #endif case cm_case: if (ignore_return) command->value.Case->flags |= CMD_IGNORE_RETURN; exec_result = execute_case_command (command->value.Case); break; case cm_while: if (ignore_return) command->value.While->flags |= CMD_IGNORE_RETURN; exec_result = execute_while_command (command->value.While); break; case cm_until: if (ignore_return) command->value.While->flags |= CMD_IGNORE_RETURN; exec_result = execute_until_command (command->value.While); break; case cm_if: if (ignore_return) command->value.If->flags |= CMD_IGNORE_RETURN; exec_result = execute_if_command (command->value.If); break; case cm_group: /* This code can be executed from either of two paths: an explicit '{}' command, or via a function call. If we are executed via a function call, we have already taken care of the function being executed in the background (down there in execute_simple_command ()), and this command should *not* be marked as asynchronous. If we are executing a regular '{}' group command, and asynchronous == 1, we must want to execute the whole command in the background, so we need a subshell, and we want the stuff executed in that subshell (this group command) to be executed in the foreground of that subshell (i.e. there will not be *another* subshell forked). What we do is to force a subshell if asynchronous, and then call execute_command_internal again with asynchronous still set to 1, but with the original group command, so the printed command will look right. The code above that handles forking off subshells will note that both subshell and async are on, and turn off async in the child after forking the subshell (but leave async set in the parent, so the normal call to describe_pid is made). This turning off async is *crucial*; if it is not done, this will fall into an infinite loop of executions through this spot in subshell after subshell until the process limit is exhausted. */ if (asynchronous) { command->flags |= CMD_FORCE_SUBSHELL; exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); } else { if (ignore_return && command->value.Group->command) command->value.Group->command->flags |= CMD_IGNORE_RETURN; exec_result = execute_command_internal (command->value.Group->command, asynchronous, pipe_in, pipe_out, fds_to_close); } break; case cm_connection: exec_result = execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close); break; #if defined (DPAREN_ARITHMETIC) case cm_arith: if (ignore_return) command->value.Arith->flags |= CMD_IGNORE_RETURN; exec_result = execute_arith_command (command->value.Arith); break; #endif #if defined (COND_COMMAND) case cm_cond: if (ignore_return) command->value.Cond->flags |= CMD_IGNORE_RETURN; exec_result = execute_cond_command (command->value.Cond); break; #endif case cm_function_def: exec_result = execute_intern_function (command->value.Function_def->name, command->value.Function_def->command); break; default: command_error ("execute_command", CMDERR_BADTYPE, command->type, 0); } if (my_undo_list) { do_redirections (my_undo_list, 1, 0, 0); dispose_redirects (my_undo_list); } if (exec_undo_list) dispose_redirects (exec_undo_list); if (my_undo_list || exec_undo_list) discard_unwind_frame ("loop_redirections"); /* Invert the return value if we have to */ if (invert) exec_result = (exec_result == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; last_command_exit_value = exec_result; run_pending_traps (); if (running_trap == 0) currently_executing_command = (COMMAND *)NULL; return (last_command_exit_value); } #if defined (COMMAND_TIMING) #if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) extern struct timeval *difftimeval __P((struct timeval *, struct timeval *, struct timeval *)); extern struct timeval *addtimeval __P((struct timeval *, struct timeval *, struct timeval *)); extern int timeval_to_cpu __P((struct timeval *, struct timeval *, struct timeval *)); #endif #define POSIX_TIMEFORMAT "real %2R\nuser %2U\nsys %2S" #define BASH_TIMEFORMAT "\nreal\t%3lR\nuser\t%3lU\nsys\t%3lS" static int precs[] = { 0, 100, 10, 1 }; /* Expand one `%'-prefixed escape sequence from a time format string. */ static int mkfmt (buf, prec, lng, sec, sec_fraction) char *buf; int prec, lng; time_t sec; int sec_fraction; { time_t min; char abuf[INT_STRLEN_BOUND(time_t) + 1]; int ind, aind; ind = 0; abuf[sizeof(abuf) - 1] = '\0'; /* If LNG is non-zero, we want to decompose SEC into minutes and seconds. */ if (lng) { min = sec / 60; sec %= 60; aind = sizeof(abuf) - 2; do abuf[aind--] = (min % 10) + '0'; while (min /= 10); aind++; while (abuf[aind]) buf[ind++] = abuf[aind++]; buf[ind++] = 'm'; } /* Now add the seconds. */ aind = sizeof (abuf) - 2; do abuf[aind--] = (sec % 10) + '0'; while (sec /= 10); aind++; while (abuf[aind]) buf[ind++] = abuf[aind++]; /* We want to add a decimal point and PREC places after it if PREC is nonzero. PREC is not greater than 3. SEC_FRACTION is between 0 and 999. */ if (prec != 0) { buf[ind++] = '.'; for (aind = 1; aind <= prec; aind++) { buf[ind++] = (sec_fraction / precs[aind]) + '0'; sec_fraction %= precs[aind]; } } if (lng) buf[ind++] = 's'; buf[ind] = '\0'; return (ind); } /* Interpret the format string FORMAT, interpolating the following escape sequences: %[prec][l][RUS] where the optional `prec' is a precision, meaning the number of characters after the decimal point, the optional `l' means to format using minutes and seconds (MMmNN[.FF]s), like the `times' builtin', and the last character is one of R number of seconds of `real' time U number of seconds of `user' time S number of seconds of `system' time An occurrence of `%%' in the format string is translated to a `%'. The result is printed to FP, a pointer to a FILE. The other variables are the seconds and thousandths of a second of real, user, and system time, resectively. */ static void print_formatted_time (fp, format, rs, rsf, us, usf, ss, ssf, cpu) FILE *fp; char *format; time_t rs; int rsf; time_t us; int usf; time_t ss; int ssf, cpu; { int prec, lng, len; char *str, *s, ts[INT_STRLEN_BOUND (time_t) + sizeof ("mSS.FFFF")]; time_t sum; int sum_frac; int sindex, ssize; len = strlen (format); ssize = (len + 64) - (len % 64); str = (char *)xmalloc (ssize); sindex = 0; for (s = format; *s; s++) { if (*s != '%' || s[1] == '\0') { RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64); str[sindex++] = *s; } else if (s[1] == '%') { s++; RESIZE_MALLOCED_BUFFER (str, sindex, 1, ssize, 64); str[sindex++] = *s; } else if (s[1] == 'P') { s++; if (cpu > 10000) cpu = 10000; sum = cpu / 100; sum_frac = (cpu % 100) * 10; len = mkfmt (ts, 2, 0, sum, sum_frac); RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64); strcpy (str + sindex, ts); sindex += len; } else { prec = 3; /* default is three places past the decimal point. */ lng = 0; /* default is to not use minutes or append `s' */ s++; if (DIGIT (*s)) /* `precision' */ { prec = *s++ - '0'; if (prec > 3) prec = 3; } if (*s == 'l') /* `length extender' */ { lng = 1; s++; } if (*s == 'R' || *s == 'E') len = mkfmt (ts, prec, lng, rs, rsf); else if (*s == 'U') len = mkfmt (ts, prec, lng, us, usf); else if (*s == 'S') len = mkfmt (ts, prec, lng, ss, ssf); else { internal_error ("bad format character in time format: %c", *s); free (str); return; } RESIZE_MALLOCED_BUFFER (str, sindex, len, ssize, 64); strcpy (str + sindex, ts); sindex += len; } } str[sindex] = '\0'; fprintf (fp, "%s\n", str); fflush (fp); free (str); } static int time_command (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous, pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { int rv, posix_time, old_flags; time_t rs, us, ss; int rsf, usf, ssf; int cpu; char *time_format; #if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) struct timeval real, user, sys; struct timeval before, after; struct timezone dtz; struct rusage selfb, selfa, kidsb, kidsa; /* a = after, b = before */ #else # if defined (HAVE_TIMES) clock_t tbefore, tafter, real, user, sys; struct tms before, after; # endif #endif #if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) gettimeofday (&before, &dtz); getrusage (RUSAGE_SELF, &selfb); getrusage (RUSAGE_CHILDREN, &kidsb); #else # if defined (HAVE_TIMES) tbefore = times (&before); # endif #endif posix_time = (command->flags & CMD_TIME_POSIX); old_flags = command->flags; command->flags &= ~(CMD_TIME_PIPELINE|CMD_TIME_POSIX); rv = execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close); command->flags = old_flags; rs = us = ss = 0; rsf = usf = ssf = cpu = 0; #if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) gettimeofday (&after, &dtz); getrusage (RUSAGE_SELF, &selfa); getrusage (RUSAGE_CHILDREN, &kidsa); difftimeval (&real, &before, &after); timeval_to_secs (&real, &rs, &rsf); addtimeval (&user, difftimeval(&after, &selfb.ru_utime, &selfa.ru_utime), difftimeval(&before, &kidsb.ru_utime, &kidsa.ru_utime)); timeval_to_secs (&user, &us, &usf); addtimeval (&sys, difftimeval(&after, &selfb.ru_stime, &selfa.ru_stime), difftimeval(&before, &kidsb.ru_stime, &kidsa.ru_stime)); timeval_to_secs (&sys, &ss, &ssf); cpu = timeval_to_cpu (&real, &user, &sys); #else # if defined (HAVE_TIMES) tafter = times (&after); real = tafter - tbefore; clock_t_to_secs (real, &rs, &rsf); user = (after.tms_utime - before.tms_utime) + (after.tms_cutime - before.tms_cutime); clock_t_to_secs (user, &us, &usf); sys = (after.tms_stime - before.tms_stime) + (after.tms_cstime - before.tms_cstime); clock_t_to_secs (sys, &ss, &ssf); cpu = (real == 0) ? 0 : ((user + sys) * 10000) / real; # else rs = us = ss = 0; rsf = usf = ssf = cpu = 0; # endif #endif if (posix_time) time_format = POSIX_TIMEFORMAT; else if ((time_format = get_string_value ("TIMEFORMAT")) == 0) time_format = BASH_TIMEFORMAT; if (time_format && *time_format) print_formatted_time (stderr, time_format, rs, rsf, us, usf, ss, ssf, cpu); return rv; } #endif /* COMMAND_TIMING */ /* Execute a command that's supposed to be in a subshell. This must be called after make_child and we must be running in the child process. The caller will return or exit() immediately with the value this returns. */ static int execute_in_subshell (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous; int pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { int user_subshell, return_code, function_value, should_redir_stdin, invert; int ois; COMMAND *tcom; USE_VAR(user_subshell); USE_VAR(invert); USE_VAR(tcom); USE_VAR(asynchronous); should_redir_stdin = (asynchronous && (command->flags & CMD_STDIN_REDIR) && pipe_in == NO_PIPE && stdin_redirects (command->redirects) == 0); invert = (command->flags & CMD_INVERT_RETURN) != 0; user_subshell = command->type == cm_subshell || ((command->flags & CMD_WANT_SUBSHELL) != 0); command->flags &= ~(CMD_FORCE_SUBSHELL | CMD_WANT_SUBSHELL | CMD_INVERT_RETURN); /* If a command is asynchronous in a subshell (like ( foo ) & or the special case of an asynchronous GROUP command where the the subshell bit is turned on down in case cm_group: below), turn off `asynchronous', so that two subshells aren't spawned. This seems semantically correct to me. For example, ( foo ) & seems to say ``do the command `foo' in a subshell environment, but don't wait for that subshell to finish'', and "{ foo ; bar ; } &" seems to me to be like functions or builtins in the background, which executed in a subshell environment. I just don't see the need to fork two subshells. */ /* Don't fork again, we are already in a subshell. A `doubly async' shell is not interactive, however. */ if (asynchronous) { #if defined (JOB_CONTROL) /* If a construct like ( exec xxx yyy ) & is given while job control is active, we want to prevent exec from putting the subshell back into the original process group, carefully undoing all the work we just did in make_child. */ original_pgrp = -1; #endif /* JOB_CONTROL */ ois = interactive_shell; interactive_shell = 0; /* This test is to prevent alias expansion by interactive shells that run `(command) &' but to allow scripts that have enabled alias expansion with `shopt -s expand_alias' to continue to expand aliases. */ if (ois != interactive_shell) expand_aliases = 0; asynchronous = 0; } /* Subshells are neither login nor interactive. */ login_shell = interactive = 0; subshell_environment = user_subshell ? SUBSHELL_PAREN : SUBSHELL_ASYNC; reset_terminating_signals (); /* in sig.c */ /* Cancel traps, in trap.c. */ restore_original_signals (); if (asynchronous) setup_async_signals (); #if defined (JOB_CONTROL) set_sigchld_handler (); #endif /* JOB_CONTROL */ set_sigint_handler (); #if defined (JOB_CONTROL) /* Delete all traces that there were any jobs running. This is only for subshells. */ without_job_control (); #endif /* JOB_CONTROL */ if (fds_to_close) close_fd_bitmap (fds_to_close); do_piping (pipe_in, pipe_out); /* If this is a user subshell, set a flag if stdin was redirected. This is used later to decide whether to redirect fd 0 to /dev/null for async commands in the subshell. This adds more sh compatibility, but I'm not sure it's the right thing to do. */ if (user_subshell) { stdin_redir = stdin_redirects (command->redirects); restore_default_signal (0); } /* If this is an asynchronous command (command &), we want to redirect the standard input from /dev/null in the absence of any specific redirection involving stdin. */ if (should_redir_stdin && stdin_redir == 0) async_redirect_stdin (); /* Do redirections, then dispose of them before recursive call. */ if (command->redirects) { if (do_redirections (command->redirects, 1, 0, 0) != 0) exit (invert ? EXECUTION_SUCCESS : EXECUTION_FAILURE); dispose_redirects (command->redirects); command->redirects = (REDIRECT *)NULL; } tcom = (command->type == cm_subshell) ? command->value.Subshell->command : command; /* Make sure the subshell inherits any CMD_IGNORE_RETURN flag. */ if ((command->flags & CMD_IGNORE_RETURN) && tcom != command) tcom->flags |= CMD_IGNORE_RETURN; /* If this is a simple command, tell execute_disk_command that it might be able to get away without forking and simply exec. This means things like ( sleep 10 ) will only cause one fork. If we're timing the command or inverting its return value, however, we cannot do this optimization. */ if (user_subshell && (tcom->type == cm_simple || tcom->type == cm_subshell) && ((tcom->flags & CMD_TIME_PIPELINE) == 0) && ((tcom->flags & CMD_INVERT_RETURN) == 0)) { tcom->flags |= CMD_NO_FORK; if (tcom->type == cm_simple) tcom->value.Simple->flags |= CMD_NO_FORK; } invert = (tcom->flags & CMD_INVERT_RETURN) != 0; tcom->flags &= ~CMD_INVERT_RETURN; /* If we're inside a function while executing this subshell, we need to handle a possible `return'. */ function_value = 0; if (return_catch_flag) function_value = setjmp (return_catch); if (function_value) return_code = return_catch_value; else return_code = execute_command_internal (tcom, asynchronous, NO_PIPE, NO_PIPE, fds_to_close); /* If we are asked to, invert the return value. */ if (invert) return_code = (return_code == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; /* If we were explicitly placed in a subshell with (), we need to do the `shell cleanup' things, such as running traps[0]. */ if (user_subshell && signal_is_trapped (0)) { last_command_exit_value = return_code; return_code = run_exit_trap (); } return (return_code); /* NOTREACHED */ } static int execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous, pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { int prev, fildes[2], new_bitmap_size, dummyfd, ignore_return, exec_result; COMMAND *cmd; struct fd_bitmap *fd_bitmap; #if defined (JOB_CONTROL) sigset_t set, oset; BLOCK_CHILD (set, oset); #endif /* JOB_CONTROL */ ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; prev = pipe_in; cmd = command; while (cmd && cmd->type == cm_connection && cmd->value.Connection && cmd->value.Connection->connector == '|') { /* Make a pipeline between the two commands. */ if (pipe (fildes) < 0) { sys_error ("pipe error"); #if defined (JOB_CONTROL) terminate_current_pipeline (); kill_current_pipeline (); #endif /* JOB_CONTROL */ last_command_exit_value = EXECUTION_FAILURE; /* The unwind-protects installed below will take care of closing all of the open file descriptors. */ throw_to_top_level (); return (EXECUTION_FAILURE); /* XXX */ } /* Here is a problem: with the new file close-on-exec code, the read end of the pipe (fildes[0]) stays open in the first process, so that process will never get a SIGPIPE. There is no way to signal the first process that it should close fildes[0] after forking, so it remains open. No SIGPIPE is ever sent because there is still a file descriptor open for reading connected to the pipe. We take care of that here. This passes around a bitmap of file descriptors that must be closed after making a child process in execute_simple_command. */ /* We need fd_bitmap to be at least as big as fildes[0]. If fildes[0] is less than fds_to_close->size, then use fds_to_close->size. */ new_bitmap_size = (fildes[0] < fds_to_close->size) ? fds_to_close->size : fildes[0] + 8; fd_bitmap = new_fd_bitmap (new_bitmap_size); /* Now copy the old information into the new bitmap. */ xbcopy ((char *)fds_to_close->bitmap, (char *)fd_bitmap->bitmap, fds_to_close->size); /* And mark the pipe file descriptors to be closed. */ fd_bitmap->bitmap[fildes[0]] = 1; /* In case there are pipe or out-of-processes errors, we want all these file descriptors to be closed when unwind-protects are run, and the storage used for the bitmaps freed up. */ begin_unwind_frame ("pipe-file-descriptors"); add_unwind_protect (dispose_fd_bitmap, fd_bitmap); add_unwind_protect (close_fd_bitmap, fd_bitmap); if (prev >= 0) add_unwind_protect (close, prev); dummyfd = fildes[1]; add_unwind_protect (close, dummyfd); #if defined (JOB_CONTROL) add_unwind_protect (restore_signal_mask, &oset); #endif /* JOB_CONTROL */ if (ignore_return && cmd->value.Connection->first) cmd->value.Connection->first->flags |= CMD_IGNORE_RETURN; execute_command_internal (cmd->value.Connection->first, asynchronous, prev, fildes[1], fd_bitmap); if (prev >= 0) close (prev); prev = fildes[0]; close (fildes[1]); dispose_fd_bitmap (fd_bitmap); discard_unwind_frame ("pipe-file-descriptors"); cmd = cmd->value.Connection->second; } /* Now execute the rightmost command in the pipeline. */ if (ignore_return && cmd) cmd->flags |= CMD_IGNORE_RETURN; exec_result = execute_command_internal (cmd, asynchronous, prev, pipe_out, fds_to_close); if (prev >= 0) close (prev); #if defined (JOB_CONTROL) UNBLOCK_CHILD (oset); #endif return (exec_result); } static int execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close) COMMAND *command; int asynchronous, pipe_in, pipe_out; struct fd_bitmap *fds_to_close; { #if 0 REDIRECT *tr, *tl; #endif REDIRECT *rp; COMMAND *tc, *second; int ignore_return, exec_result; ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; switch (command->value.Connection->connector) { /* Do the first command asynchronously. */ case '&': tc = command->value.Connection->first; if (tc == 0) return (EXECUTION_SUCCESS); rp = tc->redirects; if (ignore_return) tc->flags |= CMD_IGNORE_RETURN; tc->flags |= CMD_AMPERSAND; /* If this shell was compiled without job control support, if we are currently in a subshell via `( xxx )', or if job control is not active then the standard input for an asynchronous command is forced to /dev/null. */ #if defined (JOB_CONTROL) if ((subshell_environment || !job_control) && !stdin_redir) #else if (!stdin_redir) #endif /* JOB_CONTROL */ { #if 0 rd.filename = make_bare_word ("/dev/null"); tr = make_redirection (0, r_inputa_direction, rd); tr->next = tc->redirects; tc->redirects = tr; #endif tc->flags |= CMD_STDIN_REDIR; } exec_result = execute_command_internal (tc, 1, pipe_in, pipe_out, fds_to_close); if (tc->flags & CMD_STDIN_REDIR) { #if 0 /* Remove the redirection we added above. It matters, especially for loops, which call execute_command () multiple times with the same command. */ tr = tc->redirects; do { tl = tc->redirects; tc->redirects = tc->redirects->next; } while (tc->redirects && tc->redirects != rp); tl->next = (REDIRECT *)NULL; dispose_redirects (tr); #endif tc->flags &= ~CMD_STDIN_REDIR; } second = command->value.Connection->second; if (second) { if (ignore_return) second->flags |= CMD_IGNORE_RETURN; exec_result = execute_command_internal (second, asynchronous, pipe_in, pipe_out, fds_to_close); } break; /* Just call execute command on both sides. */ case ';': if (ignore_return) { if (command->value.Connection->first) command->value.Connection->first->flags |= CMD_IGNORE_RETURN; if (command->value.Connection->second) command->value.Connection->second->flags |= CMD_IGNORE_RETURN; } QUIT; execute_command (command->value.Connection->first); QUIT; exec_result = execute_command_internal (command->value.Connection->second, asynchronous, pipe_in, pipe_out, fds_to_close); break; case '|': exec_result = execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close); break; case AND_AND: case OR_OR: if (asynchronous) { /* If we have something like `a && b &' or `a || b &', run the && or || stuff in a subshell. Force a subshell and just call execute_command_internal again. Leave asynchronous on so that we get a report from the parent shell about the background job. */ command->flags |= CMD_FORCE_SUBSHELL; exec_result = execute_command_internal (command, 1, pipe_in, pipe_out, fds_to_close); break; } /* Execute the first command. If the result of that is successful and the connector is AND_AND, or the result is not successful and the connector is OR_OR, then execute the second command, otherwise return. */ if (command->value.Connection->first) command->value.Connection->first->flags |= CMD_IGNORE_RETURN; exec_result = execute_command (command->value.Connection->first); QUIT; if (((command->value.Connection->connector == AND_AND) && (exec_result == EXECUTION_SUCCESS)) || ((command->value.Connection->connector == OR_OR) && (exec_result != EXECUTION_SUCCESS))) { if (ignore_return && command->value.Connection->second) command->value.Connection->second->flags |= CMD_IGNORE_RETURN; exec_result = execute_command (command->value.Connection->second); } break; default: command_error ("execute_connection", CMDERR_BADCONN, command->value.Connection->connector, 0); jump_to_top_level (DISCARD); exec_result = EXECUTION_FAILURE; } return exec_result; } #define REAP() \ do \ { \ if (!interactive_shell) \ reap_dead_jobs (); \ } \ while (0) /* Execute a FOR command. The syntax is: FOR word_desc IN word_list; DO command; DONE */ static int execute_for_command (for_command) FOR_COM *for_command; { register WORD_LIST *releaser, *list; SHELL_VAR *v; char *identifier; int retval; #if 0 SHELL_VAR *old_value = (SHELL_VAR *)NULL; /* Remember the old value of x. */ #endif if (check_identifier (for_command->name, 1) == 0) { if (posixly_correct && interactive_shell == 0) { last_command_exit_value = EX_USAGE; jump_to_top_level (EXITPROG); } return (EXECUTION_FAILURE); } loop_level++; identifier = for_command->name->word; list = releaser = expand_words_no_vars (for_command->map_list); begin_unwind_frame ("for"); add_unwind_protect (dispose_words, releaser); #if 0 if (lexical_scoping) { old_value = copy_variable (find_variable (identifier)); if (old_value) add_unwind_protect (dispose_variable, old_value); } #endif if (for_command->flags & CMD_IGNORE_RETURN) for_command->action->flags |= CMD_IGNORE_RETURN; for (retval = EXECUTION_SUCCESS; list; list = list->next) { QUIT; this_command_name = (char *)NULL; v = bind_variable (identifier, list->word->word); if (readonly_p (v) || noassign_p (v)) { if (readonly_p (v) && interactive_shell == 0 && posixly_correct) { last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (FORCE_EOF); } else { run_unwind_frame ("for"); loop_level--; return (EXECUTION_FAILURE); } } retval = execute_command (for_command->action); REAP (); QUIT; if (breaking) { breaking--; break; } if (continuing) { continuing--; if (continuing) break; } } loop_level--; #if 0 if (lexical_scoping) { if (!old_value) makunbound (identifier, shell_variables); else { SHELL_VAR *new_value; new_value = bind_variable (identifier, value_cell(old_value)); new_value->attributes = old_value->attributes; dispose_variable (old_value); } } #endif dispose_words (releaser); discard_unwind_frame ("for"); return (retval); } #if defined (ARITH_FOR_COMMAND) /* Execute an arithmetic for command. The syntax is for (( init ; step ; test )) do body done The execution should be exactly equivalent to eval \(\( init \)\) while eval \(\( test \)\) ; do body; eval \(\( step \)\) done */ static long eval_arith_for_expr (l, okp) WORD_LIST *l; int *okp; { WORD_LIST *new; long expresult; new = expand_words_no_vars (l); if (new) { if (echo_command_at_execute) xtrace_print_arith_cmd (new); expresult = evalexp (new->word->word, okp); dispose_words (new); } else { expresult = 0; if (okp) *okp = 1; } return (expresult); } static int execute_arith_for_command (arith_for_command) ARITH_FOR_COM *arith_for_command; { long expresult; int expok, body_status; body_status = EXECUTION_SUCCESS; loop_level++; if (arith_for_command->flags & CMD_IGNORE_RETURN) arith_for_command->action->flags |= CMD_IGNORE_RETURN; this_command_name = "(("; /* )) for expression error messages */ if (variable_context) line_number = arith_for_command->line - function_line_number; /* Evaluate the initialization expression. */ expresult = eval_arith_for_expr (arith_for_command->init, &expok); if (expok == 0) return (EXECUTION_FAILURE); while (1) { /* Evaluate the test expression. */ expresult = eval_arith_for_expr (arith_for_command->test, &expok); if (expok == 0) { body_status = EXECUTION_FAILURE; break; } REAP (); if (expresult == 0) break; /* Execute the body of the arithmetic for command. */ QUIT; body_status = execute_command (arith_for_command->action); QUIT; /* Handle any `break' or `continue' commands executed by the body. */ if (breaking) { breaking--; break; } if (continuing) { continuing--; if (continuing) break; } /* Evaluate the step expression. */ expresult = eval_arith_for_expr (arith_for_command->step, &expok); if (expok == 0) { body_status = EXECUTION_FAILURE; break; } } loop_level--; return (body_status); } #endif #if defined (SELECT_COMMAND) static int LINES, COLS, tabsize; #define RP_SPACE ") " #define RP_SPACE_LEN 2 /* XXX - does not handle numbers > 1000000 at all. */ #define NUMBER_LEN(s) \ ((s < 10) ? 1 \ : ((s < 100) ? 2 \ : ((s < 1000) ? 3 \ : ((s < 10000) ? 4 \ : ((s < 100000) ? 5 \ : 6))))) static int print_index_and_element (len, ind, list) int len, ind; WORD_LIST *list; { register WORD_LIST *l; register int i; if (list == 0) return (0); for (i = ind, l = list; l && --i; l = l->next) ; fprintf (stderr, "%*d%s%s", len, ind, RP_SPACE, l->word->word); return (STRLEN (l->word->word)); } static void indent (from, to) int from, to; { while (from < to) { if ((to / tabsize) > (from / tabsize)) { putc ('\t', stderr); from += tabsize - from % tabsize; } else { putc (' ', stderr); from++; } } } static void print_select_list (list, list_len, max_elem_len, indices_len) WORD_LIST *list; int list_len, max_elem_len, indices_len; { int ind, row, elem_len, pos, cols, rows; int first_column_indices_len, other_indices_len; if (list == 0) { putc ('\n', stderr); return; } cols = max_elem_len ? COLS / max_elem_len : 1; if (cols == 0) cols = 1; rows = list_len ? list_len / cols + (list_len % cols != 0) : 1; cols = list_len ? list_len / rows + (list_len % rows != 0) : 1; if (rows == 1) { rows = cols; cols = 1; } first_column_indices_len = NUMBER_LEN (rows); other_indices_len = indices_len; for (row = 0; row < rows; row++) { ind = row; pos = 0; while (1) { indices_len = (pos == 0) ? first_column_indices_len : other_indices_len; elem_len = print_index_and_element (indices_len, ind + 1, list); elem_len += indices_len + RP_SPACE_LEN; ind += rows; if (ind >= list_len) break; indent (pos + elem_len, pos + max_elem_len); pos += max_elem_len; } putc ('\n', stderr); } } /* Print the elements of LIST, one per line, preceded by an index from 1 to LIST_LEN. Then display PROMPT and wait for the user to enter a number. If the number is between 1 and LIST_LEN, return that selection. If EOF is read, return a null string. If a blank line is entered, or an invalid number is entered, the loop is executed again. */ static char * select_query (list, list_len, prompt) WORD_LIST *list; int list_len; char *prompt; { int max_elem_len, indices_len, len; long reply; WORD_LIST *l; char *repl_string, *t; t = get_string_value ("LINES"); LINES = (t && *t) ? atoi (t) : 24; t = get_string_value ("COLUMNS"); COLS = (t && *t) ? atoi (t) : 80; #if 0 t = get_string_value ("TABSIZE"); tabsize = (t && *t) ? atoi (t) : 8; if (tabsize <= 0) tabsize = 8; #else tabsize = 8; #endif max_elem_len = 0; for (l = list; l; l = l->next) { len = STRLEN (l->word->word); if (len > max_elem_len) max_elem_len = len; } indices_len = NUMBER_LEN (list_len); max_elem_len += indices_len + RP_SPACE_LEN + 2; while (1) { print_select_list (list, list_len, max_elem_len, indices_len); fprintf (stderr, "%s", prompt); fflush (stderr); QUIT; if (read_builtin ((WORD_LIST *)NULL) == EXECUTION_FAILURE) { putchar ('\n'); return ((char *)NULL); } repl_string = get_string_value ("REPLY"); if (*repl_string == 0) continue; if (legal_number (repl_string, &reply) == 0) return ""; if (reply < 1 || reply > list_len) return ""; for (l = list; l && --reply; l = l->next) ; return (l->word->word); } } /* Execute a SELECT command. The syntax is: SELECT word IN list DO command_list DONE Only `break' or `return' in command_list will terminate the command. */ static int execute_select_command (select_command) SELECT_COM *select_command; { WORD_LIST *releaser, *list; SHELL_VAR *v; char *identifier, *ps3_prompt, *selection; int retval, list_len; if (check_identifier (select_command->name, 1) == 0) return (EXECUTION_FAILURE); loop_level++; identifier = select_command->name->word; /* command and arithmetic substitution, parameter and variable expansion, word splitting, pathname expansion, and quote removal. */ list = releaser = expand_words_no_vars (select_command->map_list); list_len = list_length (list); if (list == 0 || list_len == 0) { if (list) dispose_words (list); return (EXECUTION_SUCCESS); } begin_unwind_frame ("select"); add_unwind_protect (dispose_words, releaser); if (select_command->flags & CMD_IGNORE_RETURN) select_command->action->flags |= CMD_IGNORE_RETURN; retval = EXECUTION_SUCCESS; while (1) { ps3_prompt = get_string_value ("PS3"); if (ps3_prompt == 0) ps3_prompt = "#? "; QUIT; selection = select_query (list, list_len, ps3_prompt); QUIT; if (selection == 0) break; v = bind_variable (identifier, selection); if (readonly_p (v) || noassign_p (v)) { if (readonly_p (v) && interactive_shell == 0 && posixly_correct) { last_command_exit_value = EXECUTION_FAILURE; jump_to_top_level (FORCE_EOF); } else { run_unwind_frame ("select"); return (EXECUTION_FAILURE); } } retval = execute_command (select_command->action); REAP (); QUIT; if (breaking) { breaking--; break; } if (continuing) { continuing--; if (continuing) break; } } loop_level--; run_unwind_frame ("select"); return (retval); } #endif /* SELECT_COMMAND */ /* Execute a CASE command. The syntax is: CASE word_desc IN pattern_list ESAC. The pattern_list is a linked list of pattern clauses; each clause contains some patterns to compare word_desc against, and an associated command to execute. */ static int execute_case_command (case_command) CASE_COM *case_command; { register WORD_LIST *list; WORD_LIST *wlist, *es; PATTERN_LIST *clauses; char *word, *pattern; int retval, match, ignore_return; /* Posix.2 specifies that the WORD is tilde expanded. */ if (member ('~', case_command->word->word)) { word = bash_tilde_expand (case_command->word->word); free (case_command->word->word); case_command->word->word = word; } wlist = expand_word_unsplit (case_command->word, 0); word = wlist ? string_list (wlist) : savestring (""); dispose_words (wlist); retval = EXECUTION_SUCCESS; ignore_return = case_command->flags & CMD_IGNORE_RETURN; begin_unwind_frame ("case"); add_unwind_protect ((Function *)xfree, word); #define EXIT_CASE() goto exit_case_command for (clauses = case_command->clauses; clauses; clauses = clauses->next) { QUIT; for (list = clauses->patterns; list; list = list->next) { /* Posix.2 specifies to tilde expand each member of the pattern list. */ if (member ('~', list->word->word)) { pattern = bash_tilde_expand (list->word->word); free (list->word->word); list->word->word = pattern; } es = expand_word_leave_quoted (list->word, 0); if (es && es->word && es->word->word && *(es->word->word)) pattern = quote_string_for_globbing (es->word->word, QGLOB_CVTNULL); else { pattern = (char *)xmalloc (1); pattern[0] = '\0'; } /* Since the pattern does not undergo quote removal (as per Posix.2, section 3.9.4.3), the strmatch () call must be able to recognize backslashes as escape characters. */ match = strmatch (pattern, word, FNMATCH_EXTFLAG) != FNM_NOMATCH; free (pattern); dispose_words (es); if (match) { if (clauses->action && ignore_return) clauses->action->flags |= CMD_IGNORE_RETURN; retval = execute_command (clauses->action); EXIT_CASE (); } QUIT; } } exit_case_command: free (word); discard_unwind_frame ("case"); return (retval); } #define CMD_WHILE 0 #define CMD_UNTIL 1 /* The WHILE command. Syntax: WHILE test DO action; DONE. Repeatedly execute action while executing test produces EXECUTION_SUCCESS. */ static int execute_while_command (while_command) WHILE_COM *while_command; { return (execute_while_or_until (while_command, CMD_WHILE)); } /* UNTIL is just like WHILE except that the test result is negated. */ static int execute_until_command (while_command) WHILE_COM *while_command; { return (execute_while_or_until (while_command, CMD_UNTIL)); } /* The body for both while and until. The only difference between the two is that the test value is treated differently. TYPE is CMD_WHILE or CMD_UNTIL. The return value for both commands should be EXECUTION_SUCCESS if no commands in the body are executed, and the status of the last command executed in the body otherwise. */ static int execute_while_or_until (while_command, type) WHILE_COM *while_command; int type; { int return_value, body_status; body_status = EXECUTION_SUCCESS; loop_level++; while_command->test->flags |= CMD_IGNORE_RETURN; if (while_command->flags & CMD_IGNORE_RETURN) while_command->action->flags |= CMD_IGNORE_RETURN; while (1) { return_value = execute_command (while_command->test); REAP (); /* Need to handle `break' in the test when we would break out of the loop. The job control code will set `breaking' to loop_level when a job in a loop is stopped with SIGTSTP. If the stopped job is in the loop test, `breaking' will not be reset unless we do this, and the shell will cease to execute commands. */ if (type == CMD_WHILE && return_value != EXECUTION_SUCCESS) { if (breaking) breaking--; break; } if (type == CMD_UNTIL && return_value == EXECUTION_SUCCESS) { if (breaking) breaking--; break; } QUIT; body_status = execute_command (while_command->action); QUIT; if (breaking) { breaking--; break; } if (continuing) { continuing--; if (continuing) break; } } loop_level--; return (body_status); } /* IF test THEN command [ELSE command]. IF also allows ELIF in the place of ELSE IF, but the parser makes *that* stupidity transparent. */ static int execute_if_command (if_command) IF_COM *if_command; { int return_value; if_command->test->flags |= CMD_IGNORE_RETURN; return_value = execute_command (if_command->test); if (return_value == EXECUTION_SUCCESS) { QUIT; if (if_command->true_case && (if_command->flags & CMD_IGNORE_RETURN)) if_command->true_case->flags |= CMD_IGNORE_RETURN; return (execute_command (if_command->true_case)); } else { QUIT; if (if_command->false_case && (if_command->flags & CMD_IGNORE_RETURN)) if_command->false_case->flags |= CMD_IGNORE_RETURN; return (execute_command (if_command->false_case)); } } #if defined (DPAREN_ARITHMETIC) static int execute_arith_command (arith_command) ARITH_COM *arith_command; { int expok; long expresult; WORD_LIST *new; expresult = 0; this_command_name = "(("; /* )) */ /* If we're in a function, update the line number information. */ if (variable_context) line_number = arith_command->line - function_line_number; new = expand_words (arith_command->exp); /* If we're tracing, make a new word list with `((' at the front and `))' at the back and print it. */ if (echo_command_at_execute) xtrace_print_arith_cmd (new); expresult = evalexp (new->word->word, &expok); dispose_words (new); if (expok == 0) return (EXECUTION_FAILURE); return (expresult == 0 ? EXECUTION_FAILURE : EXECUTION_SUCCESS); } #endif /* DPAREN_ARITHMETIC */ #if defined (COND_COMMAND) static char *nullstr = ""; static int execute_cond_node (cond) COND_COM *cond; { int result, invert, patmatch; char *arg1, *arg2; invert = (cond->flags & CMD_INVERT_RETURN); if (cond->type == COND_EXPR) result = execute_cond_node (cond->left); else if (cond->type == COND_OR) { result = execute_cond_node (cond->left); if (result != EXECUTION_SUCCESS) result = execute_cond_node (cond->right); } else if (cond->type == COND_AND) { result = execute_cond_node (cond->left); if (result == EXECUTION_SUCCESS) result = execute_cond_node (cond->right); } else if (cond->type == COND_UNARY) { arg1 = cond_expand_word (cond->left->op, 0); if (arg1 == 0) arg1 = nullstr; if (echo_command_at_execute) xtrace_print_cond_term (cond->type, invert, cond->op, arg1, (char *)NULL); result = unary_test (cond->op->word, arg1) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; if (arg1 != nullstr) free (arg1); } else if (cond->type == COND_BINARY) { patmatch = ((cond->op->word[1] == '=') && (cond->op->word[2] == '\0') && (cond->op->word[0] == '!' || cond->op->word[0] == '=') || (cond->op->word[0] == '=' && cond->op->word[1] == '\0')); arg1 = cond_expand_word (cond->left->op, 0); if (arg1 == 0) arg1 = nullstr; arg2 = cond_expand_word (cond->right->op, patmatch); if (arg2 == 0) arg2 = nullstr; if (echo_command_at_execute) xtrace_print_cond_term (cond->type, invert, cond->op, arg1, arg2); result = binary_test (cond->op->word, arg1, arg2, TEST_PATMATCH|TEST_ARITHEXP) ? EXECUTION_SUCCESS : EXECUTION_FAILURE; if (arg1 != nullstr) free (arg1); if (arg2 != nullstr) free (arg2); } else { command_error ("execute_cond_node", CMDERR_BADTYPE, cond->type, 0); jump_to_top_level (DISCARD); result = EXECUTION_FAILURE; } if (invert) result = (result == EXECUTION_SUCCESS) ? EXECUTION_FAILURE : EXECUTION_SUCCESS; return result; } static int execute_cond_command (cond_command) COND_COM *cond_command; { int result; result = EXECUTION_SUCCESS; this_command_name = "[["; /* If we're in a function, update the line number information. */ if (variable_context) line_number = cond_command->line - function_line_number; #if 0 debug_print_cond_command (cond_command); #endif last_command_exit_value = result = execute_cond_node (cond_command); return (result); } #endif /* COND_COMMAND */ static void bind_lastarg (arg) char *arg; { SHELL_VAR *var; if (arg == 0) arg = ""; var = bind_variable ("_", arg); VUNSETATTR (var, att_exported); } /* Execute a null command. Fork a subshell if the command uses pipes or is to be run asynchronously. This handles all the side effects that are supposed to take place. */ static int execute_null_command (redirects, pipe_in, pipe_out, async, old_last_command_subst_pid) REDIRECT *redirects; int pipe_in, pipe_out, async; pid_t old_last_command_subst_pid; { if (pipe_in != NO_PIPE || pipe_out != NO_PIPE || async) { /* We have a null command, but we really want a subshell to take care of it. Just fork, do piping and redirections, and exit. */ if (make_child ((char *)NULL, async) == 0) { /* Cancel traps, in trap.c. */ restore_original_signals (); /* XXX */ do_piping (pipe_in, pipe_out); subshell_environment = SUBSHELL_ASYNC; if (do_redirections (redirects, 1, 0, 0) == 0) exit (EXECUTION_SUCCESS); else exit (EXECUTION_FAILURE); } else { close_pipes (pipe_in, pipe_out); #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) unlink_fifo_list (); #endif return (EXECUTION_SUCCESS); } } else { /* Even if there aren't any command names, pretend to do the redirections that are specified. The user expects the side effects to take place. If the redirections fail, then return failure. Otherwise, if a command substitution took place while expanding the command or a redirection, return the value of that substitution. Otherwise, return EXECUTION_SUCCESS. */ if (do_redirections (redirects, 0, 0, 0) != 0) return (EXECUTION_FAILURE); else if (old_last_command_subst_pid != last_command_subst_pid) return (last_command_exit_value); else return (EXECUTION_SUCCESS); } } /* This is a hack to suppress word splitting for assignment statements given as arguments to builtins with the ASSIGNMENT_BUILTIN flag set. */ static void fix_assignment_words (words) WORD_LIST *words; { WORD_LIST *w; struct builtin *b; if (words == 0) return; b = builtin_address_internal (words->word->word, 0); if (b == 0 || (b->flags & ASSIGNMENT_BUILTIN) == 0) return; for (w = words; w; w = w->next) if (w->word->flags & W_ASSIGNMENT) w->word->flags |= (W_NOSPLIT|W_NOGLOB); } /* The meaty part of all the executions. We have to start hacking the real execution of commands here. Fork a process, set things up, execute the command. */ static int execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close) SIMPLE_COM *simple_command; int pipe_in, pipe_out, async; struct fd_bitmap *fds_to_close; { WORD_LIST *words, *lastword; char *command_line, *lastarg, *temp; int first_word_quoted, result, builtin_is_special, already_forked, dofork; pid_t old_last_command_subst_pid, old_last_async_pid; sh_builtin_func_t *builtin; SHELL_VAR *func; result = EXECUTION_SUCCESS; special_builtin_failed = builtin_is_special = 0; command_line = (char *)0; /* If we're in a function, update the line number information. */ if (variable_context) line_number = simple_command->line - function_line_number; /* Remember what this command line looks like at invocation. */ command_string_index = 0; print_simple_command (simple_command); first_word_quoted = simple_command->words ? (simple_command->words->word->flags & W_QUOTED): 0; old_last_command_subst_pid = last_command_subst_pid; old_last_async_pid = last_asynchronous_pid; already_forked = dofork = 0; /* If we're in a pipeline or run in the background, set DOFORK so we make the child early, before word expansion. This keeps assignment statements from affecting the parent shell's environment when they should not. */ dofork = pipe_in != NO_PIPE || pipe_out != NO_PIPE || async; /* Something like `%2 &' should restart job 2 in the background, not cause the shell to fork here. */ if (dofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE && simple_command->words && simple_command->words->word && simple_command->words->word->word && (simple_command->words->word->word[0] == '%')) dofork = 0; if (dofork) { /* XXX memory leak if expand_words() error causes a jump_to_top_level */ command_line = savestring (the_printed_command); /* Do this now, because execute_disk_command will do it anyway in the vast majority of cases. */ maybe_make_export_env (); if (make_child (command_line, async) == 0) { already_forked = 1; simple_command->flags |= CMD_NO_FORK; subshell_environment = (pipe_in != NO_PIPE || pipe_out != NO_PIPE) ? (SUBSHELL_PIPE|SUBSHELL_FORK) : (SUBSHELL_ASYNC|SUBSHELL_FORK); /* We need to do this before piping to handle some really pathological cases where one of the pipe file descriptors is < 2. */ if (fds_to_close) close_fd_bitmap (fds_to_close); do_piping (pipe_in, pipe_out); pipe_in = pipe_out = NO_PIPE; last_asynchronous_pid = old_last_async_pid; } else { close_pipes (pipe_in, pipe_out); #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) unlink_fifo_list (); #endif command_line = (char *)NULL; /* don't free this. */ bind_lastarg ((char *)NULL); return (result); } } /* If we are re-running this as the result of executing the `command' builtin, do not expand the command words a second time. */ if ((simple_command->flags & CMD_INHIBIT_EXPANSION) == 0) { current_fds_to_close = fds_to_close; fix_assignment_words (simple_command->words); words = expand_words (simple_command->words); current_fds_to_close = (struct fd_bitmap *)NULL; } else words = copy_word_list (simple_command->words); /* It is possible for WORDS not to have anything left in it. Perhaps all the words consisted of `$foo', and there was no variable `$foo'. */ if (words == 0) { result = execute_null_command (simple_command->redirects, pipe_in, pipe_out, already_forked ? 0 : async, old_last_command_subst_pid); if (already_forked) exit (result); else { bind_lastarg ((char *)NULL); set_pipestatus_from_exit (result); return (result); } } lastarg = (char *)NULL; begin_unwind_frame ("simple-command"); if (echo_command_at_execute) xtrace_print_word_list (words); builtin = (sh_builtin_func_t *)NULL; func = (SHELL_VAR *)NULL; if ((simple_command->flags & CMD_NO_FUNCTIONS) == 0) { /* Posix.2 says special builtins are found before functions. We don't set builtin_is_special anywhere other than here, because this path is followed only when the `command' builtin is *not* being used, and we don't want to exit the shell if a special builtin executed with `command builtin' fails. `command' is not a special builtin. */ if (posixly_correct) { builtin = find_special_builtin (words->word->word); if (builtin) builtin_is_special = 1; } if (builtin == 0) func = find_function (words->word->word); } add_unwind_protect (dispose_words, words); QUIT; /* Bind the last word in this command to "$_" after execution. */ for (lastword = words; lastword->next; lastword = lastword->next) ; lastarg = lastword->word->word; #if defined (JOB_CONTROL) /* Is this command a job control related thing? */ if (words->word->word[0] == '%' && already_forked == 0) { this_command_name = async ? "bg" : "fg"; last_shell_builtin = this_shell_builtin; this_shell_builtin = builtin_address (this_command_name); result = (*this_shell_builtin) (words); goto return_result; } /* One other possiblilty. The user may want to resume an existing job. If they do, find out whether this word is a candidate for a running job. */ if (job_control && already_forked == 0 && async == 0 && !first_word_quoted && !words->next && words->word->word[0] && !simple_command->redirects && pipe_in == NO_PIPE && pipe_out == NO_PIPE && (temp = get_string_value ("auto_resume"))) { char *word; register int i; int wl, cl, exact_p, substring_p, match, started_status; register PROCESS *p; word = words->word->word; exact_p = STREQ (temp, "exact"); substring_p = STREQ (temp, "substring"); wl = strlen (word); for (i = job_slots - 1; i > -1; i--) { if (jobs[i] == 0 || (JOBSTATE (i) != JSTOPPED)) continue; p = jobs[i]->pipe; do { if (exact_p) { cl = strlen (p->command); match = STREQN (p->command, word, cl); } else if (substring_p) match = strindex (p->command, word) != (char *)0; else match = STREQN (p->command, word, wl); if (match == 0) { p = p->next; continue; } run_unwind_frame ("simple-command"); this_command_name = "fg"; last_shell_builtin = this_shell_builtin; this_shell_builtin = builtin_address ("fg"); started_status = start_job (i, 1); return ((started_status < 0) ? EXECUTION_FAILURE : started_status); } while (p != jobs[i]->pipe); } } #endif /* JOB_CONTROL */ /* Remember the name of this command globally. */ this_command_name = words->word->word; QUIT; /* This command could be a shell builtin or a user-defined function. We have already found special builtins by this time, so we do not set builtin_is_special. If this is a function or builtin, and we have pipes, then fork a subshell in here. Otherwise, just execute the command directly. */ if (func == 0 && builtin == 0) builtin = find_shell_builtin (this_command_name); last_shell_builtin = this_shell_builtin; this_shell_builtin = builtin; if (builtin || func) { if (already_forked) { /* reset_terminating_signals (); */ /* XXX */ /* Cancel traps, in trap.c. */ restore_original_signals (); if (async) { if ((simple_command->flags & CMD_STDIN_REDIR) && pipe_in == NO_PIPE && (stdin_redirects (simple_command->redirects) == 0)) async_redirect_stdin (); setup_async_signals (); } execute_subshell_builtin_or_function (words, simple_command->redirects, builtin, func, pipe_in, pipe_out, async, fds_to_close, simple_command->flags); } else { result = execute_builtin_or_function (words, builtin, func, simple_command->redirects, fds_to_close, simple_command->flags); if (builtin) { if (result > EX_SHERRBASE) { result = builtin_status (result); if (builtin_is_special) special_builtin_failed = 1; } /* In POSIX mode, if there are assignment statements preceding a special builtin, they persist after the builtin completes. */ if (posixly_correct && builtin_is_special && temporary_env) merge_temporary_env (); } else /* function */ { if (result == EX_USAGE) result = EX_BADUSAGE; else if (result > EX_SHERRBASE) result = EXECUTION_FAILURE; } set_pipestatus_from_exit (result); goto return_result; } } if (command_line == 0) command_line = savestring (the_printed_command); execute_disk_command (words, simple_command->redirects, command_line, pipe_in, pipe_out, async, fds_to_close, simple_command->flags); return_result: bind_lastarg (lastarg); FREE (command_line); run_unwind_frame ("simple-command"); return (result); } /* Translate the special builtin exit statuses. We don't really need a function for this; it's a placeholder for future work. */ static int builtin_status (result) int result; { int r; switch (result) { case EX_USAGE: r = EX_BADUSAGE; break; case EX_REDIRFAIL: case EX_BADSYNTAX: case EX_BADASSIGN: case EX_EXPFAIL: r = EXECUTION_FAILURE; break; default: r = EXECUTION_SUCCESS; break; } return (r); } static int execute_builtin (builtin, words, flags, subshell) sh_builtin_func_t *builtin; WORD_LIST *words; int flags, subshell; { int old_e_flag, result, eval_unwind; old_e_flag = exit_immediately_on_error; /* The eval builtin calls parse_and_execute, which does not know about the setting of flags, and always calls the execution functions with flags that will exit the shell on an error if -e is set. If the eval builtin is being called, and we're supposed to ignore the exit value of the command, we turn the -e flag off ourselves, then restore it when the command completes. */ if (subshell == 0 && builtin == eval_builtin && (flags & CMD_IGNORE_RETURN)) { begin_unwind_frame ("eval_builtin"); unwind_protect_int (exit_immediately_on_error); exit_immediately_on_error = 0; eval_unwind = 1; } else eval_unwind = 0; /* The temporary environment for a builtin is supposed to apply to all commands executed by that builtin. Currently, this is a problem only with the `source' and `eval' builtins. */ if (builtin == source_builtin || builtin == eval_builtin) { if (subshell == 0) begin_unwind_frame ("builtin_env"); if (temporary_env) { builtin_env = copy_array (temporary_env); if (subshell == 0) add_unwind_protect (dispose_builtin_env, (char *)NULL); dispose_used_env_vars (); } /* Otherwise we inherit builtin_env from our caller. */ } /* `return' does a longjmp() back to a saved environment in execute_function. If a variable assignment list preceded the command, and the shell is running in POSIX mode, we need to merge that into the shell_variables table, since `return' is a POSIX special builtin. */ if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env) { begin_unwind_frame ("return_temp_env"); add_unwind_protect (merge_temporary_env, (char *)NULL); } result = ((*builtin) (words->next)); /* This shouldn't happen, but in case `return' comes back instead of longjmp'ing, we need to unwind. */ if (posixly_correct && subshell == 0 && builtin == return_builtin && temporary_env) discard_unwind_frame ("return_temp_env"); if (subshell == 0 && (builtin == source_builtin || builtin == eval_builtin)) { /* In POSIX mode, if any variable assignments precede the `.' or `eval' builtin, they persist after the builtin completes, since `.' and `eval' are special builtins. */ if (posixly_correct && builtin_env) merge_builtin_env (); run_unwind_frame ("builtin_env"); } if (eval_unwind) { exit_immediately_on_error += old_e_flag; discard_unwind_frame ("eval_builtin"); } return (result); } static int execute_function (var, words, flags, fds_to_close, async, subshell) SHELL_VAR *var; WORD_LIST *words; int flags; struct fd_bitmap *fds_to_close; int async, subshell; { int return_val, result; COMMAND *tc, *fc; char *debug_trap, *error_trap; USE_VAR(fc); tc = (COMMAND *)copy_command (function_cell (var)); if (tc && (flags & CMD_IGNORE_RETURN)) tc->flags |= CMD_IGNORE_RETURN; if (subshell == 0) { begin_unwind_frame ("function_calling"); push_context (); add_unwind_protect (pop_context, (char *)NULL); unwind_protect_int (line_number); unwind_protect_int (return_catch_flag); unwind_protect_jmp_buf (return_catch); add_unwind_protect (dispose_command, (char *)tc); unwind_protect_pointer (this_shell_function); unwind_protect_int (loop_level); } this_shell_function = var; make_funcname_visible (1); debug_trap = TRAP_STRING(DEBUG_TRAP); error_trap = TRAP_STRING(ERROR_TRAP); /* The order of the unwind protects for debug_trap and error_trap is important here! unwind-protect commands are run in reverse order of registration. If this causes problems, take out the xfree unwind-protect calls and live with the small memory leak. */ if (debug_trap) { if (subshell == 0) { debug_trap = savestring (debug_trap); add_unwind_protect (xfree, debug_trap); add_unwind_protect (set_debug_trap, debug_trap); } restore_default_signal (DEBUG_TRAP); } if (error_trap) { if (subshell == 0) { error_trap = savestring (error_trap); add_unwind_protect (xfree, error_trap); add_unwind_protect (set_error_trap, error_trap); } restore_default_signal (ERROR_TRAP); } /* The temporary environment for a function is supposed to apply to all commands executed within the function body. */ if (temporary_env) { function_env = copy_array (temporary_env); /* In POSIX mode, variable assignments preceding function names are supposed to persist in the environment after the function returns, as if a special builtin command had been executed. */ if (subshell == 0) { if (posixly_correct) add_unwind_protect (merge_function_env, (char *)NULL); else add_unwind_protect (dispose_function_env, (char *)NULL); } dispose_used_env_vars (); } /* Otherwise, we inherit function_env from our caller. */ remember_args (words->next, 1); /* Number of the line on which the function body starts. */ line_number = function_line_number = tc->line; if (subshell) { #if defined (JOB_CONTROL) stop_pipeline (async, (COMMAND *)NULL); #endif fc = (tc->type == cm_group) ? tc->value.Group->command : tc; if (fc && (flags & CMD_IGNORE_RETURN)) fc->flags |= CMD_IGNORE_RETURN; variable_context++; } else fc = tc; return_catch_flag++; return_val = setjmp (return_catch); if (return_val) result = return_catch_value; else result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close); if (subshell == 0) run_unwind_frame ("function_calling"); if (variable_context == 0 || this_shell_function == 0) make_funcname_visible (0); return (result); } /* A convenience routine for use by other parts of the shell to execute a particular shell function. */ int execute_shell_function (var, words) SHELL_VAR *var; WORD_LIST *words; { int ret; struct fd_bitmap *bitmap; bitmap = new_fd_bitmap (FD_BITMAP_DEFAULT_SIZE); begin_unwind_frame ("execute-shell-function"); add_unwind_protect (dispose_fd_bitmap, (char *)bitmap); ret = execute_function (var, words, 0, bitmap, 0, 0); dispose_fd_bitmap (bitmap); discard_unwind_frame ("execute-shell-function"); return ret; } /* Execute a shell builtin or function in a subshell environment. This routine does not return; it only calls exit(). If BUILTIN is non-null, it points to a function to call to execute a shell builtin; otherwise VAR points at the body of a function to execute. WORDS is the arguments to the command, REDIRECTS specifies redirections to perform before the command is executed. */ static void execute_subshell_builtin_or_function (words, redirects, builtin, var, pipe_in, pipe_out, async, fds_to_close, flags) WORD_LIST *words; REDIRECT *redirects; sh_builtin_func_t *builtin; SHELL_VAR *var; int pipe_in, pipe_out, async; struct fd_bitmap *fds_to_close; int flags; { int result, r; #if defined (JOB_CONTROL) int jobs_hack; jobs_hack = (builtin == jobs_builtin) && ((subshell_environment & SUBSHELL_ASYNC) == 0 || pipe_out != NO_PIPE); #endif /* A subshell is neither a login shell nor interactive. */ login_shell = interactive = 0; subshell_environment = SUBSHELL_ASYNC; maybe_make_export_env (); /* XXX - is this needed? */ #if defined (JOB_CONTROL) /* Eradicate all traces of job control after we fork the subshell, so all jobs begun by this subshell are in the same process group as the shell itself. */ /* Allow the output of `jobs' to be piped. */ if (jobs_hack) kill_current_pipeline (); else without_job_control (); set_sigchld_handler (); #endif /* JOB_CONTROL */ set_sigint_handler (); if (fds_to_close) close_fd_bitmap (fds_to_close); do_piping (pipe_in, pipe_out); if (do_redirections (redirects, 1, 0, 0) != 0) exit (EXECUTION_FAILURE); if (builtin) { /* Give builtins a place to jump back to on failure, so we don't go back up to main(). */ result = setjmp (top_level); if (result == EXITPROG) exit (last_command_exit_value); else if (result) exit (EXECUTION_FAILURE); else { r = execute_builtin (builtin, words, flags, 1); if (r == EX_USAGE) r = EX_BADUSAGE; exit (r); } } else exit (execute_function (var, words, flags, fds_to_close, async, 1)); } /* Execute a builtin or function in the current shell context. If BUILTIN is non-null, it is the builtin command to execute, otherwise VAR points to the body of a function. WORDS are the command's arguments, REDIRECTS are the redirections to perform. FDS_TO_CLOSE is the usual bitmap of file descriptors to close. If BUILTIN is exec_builtin, the redirections specified in REDIRECTS are not undone before this function returns. */ static int execute_builtin_or_function (words, builtin, var, redirects, fds_to_close, flags) WORD_LIST *words; sh_builtin_func_t *builtin; SHELL_VAR *var; REDIRECT *redirects; struct fd_bitmap *fds_to_close; int flags; { int result; REDIRECT *saved_undo_list; sh_builtin_func_t *saved_this_shell_builtin; if (do_redirections (redirects, 1, 1, 0) != 0) { cleanup_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; dispose_exec_redirects (); return (EX_REDIRFAIL); /* was EXECUTION_FAILURE */ } saved_this_shell_builtin = this_shell_builtin; saved_undo_list = redirection_undo_list; /* Calling the "exec" builtin changes redirections forever. */ if (builtin == exec_builtin) { dispose_redirects (saved_undo_list); saved_undo_list = exec_redirection_undo_list; exec_redirection_undo_list = (REDIRECT *)NULL; } else dispose_exec_redirects (); if (saved_undo_list) { begin_unwind_frame ("saved redirects"); add_unwind_protect (cleanup_redirects, (char *)saved_undo_list); } redirection_undo_list = (REDIRECT *)NULL; if (builtin) result = execute_builtin (builtin, words, flags, 0); else result = execute_function (var, words, flags, fds_to_close, 0, 0); /* If we are executing the `command' builtin, but this_shell_builtin is set to `exec_builtin', we know that we have something like `command exec [redirection]', since otherwise `exec' would have overwritten the shell and we wouldn't get here. In this case, we want to behave as if the `command' builtin had not been specified and preserve the redirections. */ if (builtin == command_builtin && this_shell_builtin == exec_builtin) { if (saved_undo_list) dispose_redirects (saved_undo_list); redirection_undo_list = exec_redirection_undo_list; saved_undo_list = exec_redirection_undo_list = (REDIRECT *)NULL; discard_unwind_frame ("saved_redirects"); } if (saved_undo_list) { redirection_undo_list = saved_undo_list; discard_unwind_frame ("saved redirects"); } if (redirection_undo_list) { cleanup_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; } return (result); } void setup_async_signals () { #if defined (__BEOS__) set_signal_handler (SIGHUP, SIG_IGN); /* they want csh-like behavior */ #endif #if defined (JOB_CONTROL) if (job_control == 0) #endif { set_signal_handler (SIGINT, SIG_IGN); set_signal_ignored (SIGINT); set_signal_handler (SIGQUIT, SIG_IGN); set_signal_ignored (SIGQUIT); } } /* Execute a simple command that is hopefully defined in a disk file somewhere. 1) fork () 2) connect pipes 3) look up the command 4) do redirections 5) execve () 6) If the execve failed, see if the file has executable mode set. If so, and it isn't a directory, then execute its contents as a shell script. Note that the filename hashing stuff has to take place up here, in the parent. This is probably why the Bourne style shells don't handle it, since that would require them to go through this gnarly hair, for no good reason. */ static void execute_disk_command (words, redirects, command_line, pipe_in, pipe_out, async, fds_to_close, cmdflags) WORD_LIST *words; REDIRECT *redirects; char *command_line; int pipe_in, pipe_out, async; struct fd_bitmap *fds_to_close; int cmdflags; { char *pathname, *command, **args; int nofork; pid_t pid; nofork = (cmdflags & CMD_NO_FORK); /* Don't fork, just exec, if no pipes */ pathname = words->word->word; #if defined (RESTRICTED_SHELL) if (restricted && strchr (pathname, '/')) { internal_error ("%s: restricted: cannot specify `/' in command names", pathname); last_command_exit_value = EXECUTION_FAILURE; return; } #endif /* RESTRICTED_SHELL */ command = search_for_command (pathname); if (command) { maybe_make_export_env (); put_command_name_into_env (command); } /* We have to make the child before we check for the non-existence of COMMAND, since we want the error messages to be redirected. */ /* If we can get away without forking and there are no pipes to deal with, don't bother to fork, just directly exec the command. */ if (nofork && pipe_in == NO_PIPE && pipe_out == NO_PIPE) pid = 0; else pid = make_child (savestring (command_line), async); if (pid == 0) { int old_interactive; #if 0 /* This has been disabled for the time being. */ #if !defined (ARG_MAX) || ARG_MAX >= 10240 if (posixly_correct == 0) put_gnu_argv_flags_into_env ((long)getpid (), glob_argv_flags); #endif #endif /* Cancel traps, in trap.c. */ restore_original_signals (); /* restore_original_signals may have undone the work done by make_child to ensure that SIGINT and SIGQUIT are ignored in asynchronous children. */ if (async) { if ((cmdflags & CMD_STDIN_REDIR) && pipe_in == NO_PIPE && (stdin_redirects (redirects) == 0)) async_redirect_stdin (); setup_async_signals (); } /* This functionality is now provided by close-on-exec of the file descriptors manipulated by redirection and piping. Some file descriptors still need to be closed in all children because of the way bash does pipes; fds_to_close is a bitmap of all such file descriptors. */ if (fds_to_close) close_fd_bitmap (fds_to_close); do_piping (pipe_in, pipe_out); old_interactive = interactive; if (async) interactive = 0; subshell_environment = SUBSHELL_FORK; if (redirects && (do_redirections (redirects, 1, 0, 0) != 0)) { #if defined (PROCESS_SUBSTITUTION) /* Try to remove named pipes that may have been created as the result of redirections. */ unlink_fifo_list (); #endif /* PROCESS_SUBSTITUTION */ exit (EXECUTION_FAILURE); } if (async) interactive = old_interactive; if (command == 0) { internal_error ("%s: command not found", pathname); exit (EX_NOTFOUND); /* Posix.2 says the exit status is 127 */ } /* Execve expects the command name to be in args[0]. So we leave it there, in the same format that the user used to type it in. */ args = word_list_to_argv (words, 0, 0, (int *)NULL); exit (shell_execve (command, args, export_env)); } else { /* Make sure that the pipes are closed in the parent. */ close_pipes (pipe_in, pipe_out); #if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) unlink_fifo_list (); #endif FREE (command); } } /* CPP defines to decide whether a particular index into the #! line corresponds to a valid interpreter name or argument character, or whitespace. The MSDOS define is to allow \r to be treated the same as \n. */ #if !defined (MSDOS) # define STRINGCHAR(ind) \ (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n') # define WHITECHAR(ind) \ (ind < sample_len && whitespace (sample[ind])) #else /* MSDOS */ # define STRINGCHAR(ind) \ (ind < sample_len && !whitespace (sample[ind]) && sample[ind] != '\n' && sample[ind] != '\r') # define WHITECHAR(ind) \ (ind < sample_len && whitespace (sample[ind])) #endif /* MSDOS */ static char * getinterp (sample, sample_len, endp) char *sample; int sample_len, *endp; { register int i; char *execname; int start; /* Find the name of the interpreter to exec. */ for (i = 2; i < sample_len && whitespace (sample[i]); i++) ; for (start = i; STRINGCHAR(i); i++) ; execname = substring (sample, start, i); if (endp) *endp = i; return execname; } #if !defined (HAVE_HASH_BANG_EXEC) /* If the operating system on which we're running does not handle the #! executable format, then help out. SAMPLE is the text read from the file, SAMPLE_LEN characters. COMMAND is the name of the script; it and ARGS, the arguments given by the user, will become arguments to the specified interpreter. ENV is the environment to pass to the interpreter. The word immediately following the #! is the interpreter to execute. A single argument to the interpreter is allowed. */ static int execute_shell_script (sample, sample_len, command, args, env) char *sample; int sample_len; char *command; char **args, **env; { char *execname, *firstarg; int i, start, size_increment, larry; /* Find the name of the interpreter to exec. */ execname = getinterp (sample, sample_len, &i); size_increment = 1; /* Now the argument, if any. */ for (firstarg = (char *)NULL, start = i; WHITECHAR(i); i++) ; /* If there is more text on the line, then it is an argument for the interpreter. */ if (STRINGCHAR(i)) { for (start = i; STRINGCHAR(i); i++) ; firstarg = substring ((char *)sample, start, i); size_increment = 2; } larry = array_len (args) + size_increment; args = (char **)xrealloc ((char *)args, (1 + larry) * sizeof (char *)); for (i = larry - 1; i; i--) args[i] = args[i - size_increment]; args[0] = execname; if (firstarg) { args[1] = firstarg; args[2] = command; } else args[1] = command; args[larry] = (char *)NULL; return (shell_execve (execname, args, env)); } #undef STRINGCHAR #undef WHITECHAR #endif /* !HAVE_HASH_BANG_EXEC */ static void initialize_subshell () { #if defined (ALIAS) /* Forget about any aliases that we knew of. We are in a subshell. */ delete_all_aliases (); #endif /* ALIAS */ #if defined (HISTORY) /* Forget about the history lines we have read. This is a non-interactive subshell. */ history_lines_this_session = 0; #endif #if defined (JOB_CONTROL) /* Forget about the way job control was working. We are in a subshell. */ without_job_control (); set_sigchld_handler (); #endif /* JOB_CONTROL */ /* Reset the values of the shell flags and options. */ reset_shell_flags (); reset_shell_options (); reset_shopt_options (); /* Zero out builtin_env, since this could be a shell script run from a sourced file with a temporary environment supplied to the `source/.' builtin. Such variables are not supposed to be exported (empirical testing with sh and ksh). */ builtin_env = 0; clear_unwind_protect_list (0); /* We're no longer inside a shell function. */ variable_context = return_catch_flag = 0; /* If we're not interactive, close the file descriptor from which we're reading the current shell script. */ if (interactive_shell == 0) unset_bash_input (1); } #if defined (HAVE_SETOSTYPE) && defined (_POSIX_SOURCE) # define SETOSTYPE(x) __setostype(x) #else # define SETOSTYPE(x) #endif #define READ_SAMPLE_BUF(file, buf, len) \ do \ { \ fd = open(file, O_RDONLY); \ if (fd >= 0) \ { \ len = read (fd, buf, 80); \ close (fd); \ } \ else \ len = -1; \ } \ while (0) /* Call execve (), handling interpreting shell scripts, and handling exec failures. */ int shell_execve (command, args, env) char *command; char **args, **env; { struct stat finfo; int larray, i, fd; char sample[80]; int sample_len; SETOSTYPE (0); /* Some systems use for USG/POSIX semantics */ execve (command, args, env); i = errno; /* error from execve() */ SETOSTYPE (1); /* If we get to this point, then start checking out the file. Maybe it is something we can hack ourselves. */ if (i != ENOEXEC) { if ((stat (command, &finfo) == 0) && (S_ISDIR (finfo.st_mode))) internal_error ("%s: is a directory", command); else { #if defined (HAVE_HASH_BANG_EXEC) READ_SAMPLE_BUF (command, sample, sample_len); if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') { char *interp; interp = getinterp (sample, sample_len, (int *)NULL); errno = i; sys_error ("%s: %s: bad interpreter", command, interp ? interp : ""); FREE (interp); return (EX_NOEXEC); } #endif errno = i; file_error (command); } return ((i == ENOENT) ? EX_NOTFOUND : EX_NOEXEC); /* XXX Posix.2 says that exit status is 126 */ } /* This file is executable. If it begins with #!, then help out people with losing operating systems. Otherwise, check to see if it is a binary file by seeing if the contents of the first line (or up to 80 characters) are in the ASCII set. If it's a text file, execute the contents as shell commands, otherwise return 126 (EX_BINARY_FILE). */ READ_SAMPLE_BUF (command, sample, sample_len); if (sample_len == 0) return (EXECUTION_SUCCESS); /* Is this supposed to be an executable script? If so, the format of the line is "#! interpreter [argument]". A single argument is allowed. The BSD kernel restricts the length of the entire line to 32 characters (32 bytes being the size of the BSD exec header), but we allow 80 characters. */ if (sample_len > 0) { #if !defined (HAVE_HASH_BANG_EXEC) if (sample_len > 2 && sample[0] == '#' && sample[1] == '!') return (execute_shell_script (sample, sample_len, command, args, env)); else #endif if (check_binary_file (sample, sample_len)) { internal_error ("%s: cannot execute binary file", command); return (EX_BINARY_FILE); } } /* We have committed to attempting to execute the contents of this file as shell commands. */ initialize_subshell (); set_sigint_handler (); /* Insert the name of this shell into the argument list. */ larray = array_len (args) + 1; args = (char **)xrealloc ((char *)args, (1 + larray) * sizeof (char *)); for (i = larray - 1; i; i--) args[i] = args[i - 1]; args[0] = shell_name; args[1] = command; args[larray] = (char *)NULL; if (args[0][0] == '-') args[0]++; #if defined (RESTRICTED_SHELL) if (restricted) change_flag ('r', FLAG_OFF); #endif if (subshell_argv) { /* Can't free subshell_argv[0]; that is shell_name. */ for (i = 1; i < subshell_argc; i++) free (subshell_argv[i]); free (subshell_argv); } dispose_command (currently_executing_command); /* XXX */ currently_executing_command = (COMMAND *)NULL; subshell_argc = larray; subshell_argv = args; subshell_envp = env; unbind_args (); /* remove the positional parameters */ longjmp (subshell_top_level, 1); /*NOTREACHED*/ } static int execute_intern_function (name, function) WORD_DESC *name; COMMAND *function; { SHELL_VAR *var; if (check_identifier (name, posixly_correct) == 0) { if (posixly_correct && interactive_shell == 0) { last_command_exit_value = EX_USAGE; jump_to_top_level (EXITPROG); } return (EXECUTION_FAILURE); } var = find_function (name->word); if (var && (readonly_p (var) || noassign_p (var))) { if (readonly_p (var)) internal_error ("%s: readonly function", var->name); return (EXECUTION_FAILURE); } bind_function (name->word, function); return (EXECUTION_SUCCESS); } #if defined (INCLUDE_UNUSED) #if defined (PROCESS_SUBSTITUTION) void close_all_files () { register int i, fd_table_size; fd_table_size = getdtablesize (); if (fd_table_size > 256) /* clamp to a reasonable value */ fd_table_size = 256; for (i = 3; i < fd_table_size; i++) close (i); } #endif /* PROCESS_SUBSTITUTION */ #endif static void close_pipes (in, out) int in, out; { if (in >= 0) close (in); if (out >= 0) close (out); } /* Redirect input and output to be from and to the specified pipes. NO_PIPE and REDIRECT_BOTH are handled correctly. */ static void do_piping (pipe_in, pipe_out) int pipe_in, pipe_out; { if (pipe_in != NO_PIPE) { if (dup2 (pipe_in, 0) < 0) sys_error ("cannot duplicate fd %d to fd 0", pipe_in); if (pipe_in > 0) close (pipe_in); } if (pipe_out != NO_PIPE) { if (pipe_out != REDIRECT_BOTH) { if (dup2 (pipe_out, 1) < 0) sys_error ("cannot duplicate fd %d to fd 1", pipe_out); if (pipe_out == 0 || pipe_out > 1) close (pipe_out); } else { if (dup2 (1, 2) < 0) sys_error ("cannot duplicate fd 1 to fd 2"); } } } /* expr.c -- arithmetic expression evaluation. */ /* Copyright (C) 1990, 1991 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* All arithmetic is done as long integers with no checking for overflow (though division by 0 is caught and flagged as an error). The following operators are handled, grouped into a set of levels in order of decreasing precedence. "id++", "id--" [post-increment and post-decrement] "++id", "--id" [pre-increment and pre-decrement] "-", "+" [(unary operators)] "!", "~" "**" [(exponentiation)] "*", "/", "%" "+", "-" "<<", ">>" "<=", ">=", "<", ">" "==", "!=" "&" "^" "|" "&&" "||" "expr ? expr : expr" "=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|=" (Note that most of these operators have special meaning to bash, and an entire expression should be quoted, e.g. "a=$a+1" or "a=a+1" to ensure that it is passed intact to the evaluator when using `let'. When using the $[] or $(( )) forms, the text between the `[' and `]' or `((' and `))' is treated as if in double quotes.) Sub-expressions within parentheses have a precedence level greater than all of the above levels and are evaluated first. Within a single prece- dence group, evaluation is left-to-right, except for the arithmetic assignment operator (`='), which is evaluated right-to-left (as in C). The expression evaluator returns the value of the expression (assignment statements have as a value what is returned by the RHS). The `let' builtin, on the other hand, returns 0 if the last expression evaluates to a non-zero, and 1 otherwise. Implementation is a recursive-descent parser. Chet Ramey chet@ins.CWRU.Edu */ #include "config.h" #include #include "bashansi.h" #if defined (HAVE_UNISTD_H) # ifdef _MINIX # include # endif # include #endif #include "chartypes.h" #include "shell.h" /* Because of the $((...)) construct, expressions may include newlines. Here is a macro which accepts newlines, tabs and spaces as whitespace. */ #define cr_whitespace(c) (whitespace(c) || ((c) == '\n')) /* Size be which the expression stack grows when neccessary. */ #define EXPR_STACK_GROW_SIZE 10 /* Maximum amount of recursion allowed. This prevents a non-integer variable such as "num=num+2" from infinitely adding to itself when "let num=num+2" is given. */ #define MAX_EXPR_RECURSION_LEVEL 1024 /* The Tokens. Singing "The Lion Sleeps Tonight". */ #define EQEQ 1 /* "==" */ #define NEQ 2 /* "!=" */ #define LEQ 3 /* "<=" */ #define GEQ 4 /* ">=" */ #define STR 5 /* string */ #define NUM 6 /* number */ #define LAND 7 /* "&&" Logical AND */ #define LOR 8 /* "||" Logical OR */ #define LSH 9 /* "<<" Left SHift */ #define RSH 10 /* ">>" Right SHift */ #define OP_ASSIGN 11 /* op= expassign as in Posix.2 */ #define COND 12 /* exp1 ? exp2 : exp3 */ #define POWER 13 /* exp1**exp2 */ #define PREINC 14 /* ++var */ #define PREDEC 15 /* --var */ #define POSTINC 16 /* var++ */ #define POSTDEC 17 /* var-- */ #define EQ '=' #define GT '>' #define LT '<' #define PLUS '+' #define MINUS '-' #define MUL '*' #define DIV '/' #define MOD '%' #define NOT '!' #define LPAR '(' #define RPAR ')' #define BAND '&' /* Bitwise AND */ #define BOR '|' /* Bitwise OR. */ #define BXOR '^' /* Bitwise eXclusive OR. */ #define BNOT '~' /* Bitwise NOT; Two's complement. */ #define QUES '?' #define COL ':' #define COMMA ',' /* This should be the function corresponding to the operator with the highest precedence. */ #define EXP_HIGHEST expcomma static char *expression; /* The current expression */ static char *tp; /* token lexical position */ static char *lasttp; /* pointer to last token position */ static int curtok; /* the current token */ static int lasttok; /* the previous token */ static int assigntok; /* the OP in OP= */ static char *tokstr; /* current token string */ static long tokval; /* current token value */ static int noeval; /* set to 1 if no assignment to be done */ static procenv_t evalbuf; static void readtok __P((void)); /* lexical analyzer */ static long strlong __P((char *)); static void evalerror __P((char *)); static void pushexp __P((void)); static void popexp __P((void)); static long subexpr __P((char *)); static long expcomma __P((void)); static long expassign __P((void)); static long expcond __P((void)); static long explor __P((void)); static long expland __P((void)); static long expbor __P((void)); static long expbxor __P((void)); static long expband __P((void)); static long exp5 __P((void)); static long exp4 __P((void)); static long expshift __P((void)); static long exp3 __P((void)); static long exp2 __P((void)); static long exppower __P((void)); static long exp1 __P((void)); static long exp0 __P((void)); /* A structure defining a single expression context. */ typedef struct { int curtok, lasttok; char *expression, *tp, *lasttp; long tokval; char *tokstr; int noeval; } EXPR_CONTEXT; #ifdef INCLUDE_UNUSED /* Not used yet. */ typedef struct { char *tokstr; long tokval; } LVALUE; #endif /* Global var which contains the stack of expression contexts. */ static EXPR_CONTEXT **expr_stack; static int expr_depth; /* Location in the stack. */ static int expr_stack_size; /* Number of slots already allocated. */ extern char *this_command_name; #define SAVETOK(X) \ do { \ (X)->curtok = curtok; \ (X)->lasttok = lasttok; \ (X)->tp = tp; \ (X)->lasttp = lasttp; \ (X)->tokval = tokval; \ (X)->tokstr = tokstr; \ (X)->noeval = noeval; \ } while (0) #define RESTORETOK(X) \ do { \ curtok = (X)->curtok; \ lasttok = (X)->lasttok; \ tp = (X)->tp; \ lasttp = (X)->lasttp; \ tokval = (X)->tokval; \ tokstr = (X)->tokstr; \ noeval = (X)->noeval; \ } while (0) /* Push and save away the contents of the globals describing the current expression context. */ static void pushexp () { EXPR_CONTEXT *context; if (expr_depth >= MAX_EXPR_RECURSION_LEVEL) evalerror ("expression recursion level exceeded"); if (expr_depth >= expr_stack_size) { expr_stack_size += EXPR_STACK_GROW_SIZE; expr_stack = (EXPR_CONTEXT **)xrealloc (expr_stack, expr_stack_size * sizeof (EXPR_CONTEXT *)); } context = (EXPR_CONTEXT *)xmalloc (sizeof (EXPR_CONTEXT)); context->expression = expression; SAVETOK(context); expr_stack[expr_depth++] = context; } /* Pop the the contents of the expression context stack into the globals describing the current expression context. */ static void popexp () { EXPR_CONTEXT *context; if (expr_depth == 0) evalerror ("recursion stack underflow"); context = expr_stack[--expr_depth]; expression = context->expression; RESTORETOK (context); free (context); } /* Evaluate EXPR, and return the arithmetic result. If VALIDP is non-null, a zero is stored into the location to which it points if the expression is invalid, non-zero otherwise. If a non-zero value is returned in *VALIDP, the return value of evalexp() may be used. The `while' loop after the longjmp is caught relies on the above implementation of pushexp and popexp leaving in expr_stack[0] the values that the variables had when the program started. That is, the first things saved are the initial values of the variables that were assigned at program startup or by the compiler. Therefore, it is safe to let the loop terminate when expr_depth == 0, without freeing up any of the expr_depth[0] stuff. */ long evalexp (expr, validp) char *expr; int *validp; { long val; #if 0 procenv_t old_evalbuf; #endif val = 0; #if 0 /* Save the value of evalbuf to protect it around possible recursive calls to evalexp (). */ COPY_PROCENV (evalbuf, old_evalbuf); #endif if (setjmp (evalbuf)) { FREE (tokstr); FREE (expression); tokstr = expression = (char *)NULL; while (--expr_depth > 0) { if (expr_stack[expr_depth]->tokstr) free (expr_stack[expr_depth]->tokstr); if (expr_stack[expr_depth]->expression) free (expr_stack[expr_depth]->expression); free (expr_stack[expr_depth]); } free (expr_stack[expr_depth]); /* free the allocated EXPR_CONTEXT */ if (validp) *validp = 0; return (0); } val = subexpr (expr); #if 0 /* Restore the value of evalbuf so that any subsequent longjmp calls will have a valid location to jump to. */ COPY_PROCENV (old_evalbuf, evalbuf); #endif if (validp) *validp = 1; return (val); } static long subexpr (expr) char *expr; { long val; char *p; for (p = expr; p && *p && cr_whitespace (*p); p++) ; if (p == NULL || *p == '\0') return (0); pushexp (); curtok = lasttok = 0; expression = savestring (expr); tp = expression; tokstr = (char *)NULL; tokval = 0; readtok (); val = EXP_HIGHEST (); if (curtok != 0) evalerror ("syntax error in expression"); FREE (tokstr); FREE (expression); popexp (); return val; } static long expcomma () { register long value; value = expassign (); while (curtok == COMMA) { readtok (); value = expassign (); } return value; } static long expassign () { register long value; char *lhs, *rhs; value = expcond (); if (curtok == EQ || curtok == OP_ASSIGN) { int special, op; long lvalue; special = curtok == OP_ASSIGN; if (lasttok != STR) evalerror ("attempted assignment to non-variable"); if (special) { op = assigntok; /* a OP= b */ lvalue = value; } lhs = savestring (tokstr); readtok (); value = expassign (); if (special) { switch (op) { case MUL: lvalue *= value; break; case DIV: lvalue /= value; break; case MOD: lvalue %= value; break; case PLUS: lvalue += value; break; case MINUS: lvalue -= value; break; case LSH: lvalue <<= value; break; case RSH: lvalue >>= value; break; case BAND: lvalue &= value; break; case BOR: lvalue |= value; break; case BXOR: lvalue ^= value; break; default: free (lhs); evalerror ("bug: bad expassign token"); break; } value = lvalue; } rhs = itos (value); if (noeval == 0) (void)bind_int_variable (lhs, rhs); free (rhs); free (lhs); FREE (tokstr); tokstr = (char *)NULL; /* For freeing on errors. */ } return (value); } /* Conditional expression (expr?expr:expr) */ static long expcond () { long cval, val1, val2, rval; int set_noeval; set_noeval = 0; rval = cval = explor (); if (curtok == QUES) /* found conditional expr */ { readtok (); if (curtok == 0 || curtok == COL) evalerror ("expression expected"); if (cval == 0) { set_noeval = 1; noeval++; } val1 = EXP_HIGHEST (); if (set_noeval) noeval--; if (curtok != COL) evalerror ("`:' expected for conditional expression"); readtok (); if (curtok == 0) evalerror ("expression expected"); set_noeval = 0; if (cval) { set_noeval = 1; noeval++; } val2 = explor (); if (set_noeval) noeval--; rval = cval ? val1 : val2; lasttok = COND; } return rval; } /* Logical OR. */ static long explor () { register long val1, val2; int set_noeval; val1 = expland (); while (curtok == LOR) { set_noeval = 0; if (val1 != 0) { noeval++; set_noeval = 1; } readtok (); val2 = expland (); if (set_noeval) noeval--; val1 = val1 || val2; lasttok = LOR; } return (val1); } /* Logical AND. */ static long expland () { register long val1, val2; int set_noeval; val1 = expbor (); while (curtok == LAND) { set_noeval = 0; if (val1 == 0) { set_noeval = 1; noeval++; } readtok (); val2 = expbor (); if (set_noeval) noeval--; val1 = val1 && val2; lasttok = LAND; } return (val1); } /* Bitwise OR. */ static long expbor () { register long val1, val2; val1 = expbxor (); while (curtok == BOR) { readtok (); val2 = expbxor (); val1 = val1 | val2; } return (val1); } /* Bitwise XOR. */ static long expbxor () { register long val1, val2; val1 = expband (); while (curtok == BXOR) { readtok (); val2 = expband (); val1 = val1 ^ val2; } return (val1); } /* Bitwise AND. */ static long expband () { register long val1, val2; val1 = exp5 (); while (curtok == BAND) { readtok (); val2 = exp5 (); val1 = val1 & val2; } return (val1); } static long exp5 () { register long val1, val2; val1 = exp4 (); while ((curtok == EQEQ) || (curtok == NEQ)) { int op = curtok; readtok (); val2 = exp4 (); if (op == EQEQ) val1 = (val1 == val2); else if (op == NEQ) val1 = (val1 != val2); } return (val1); } static long exp4 () { register long val1, val2; val1 = expshift (); while ((curtok == LEQ) || (curtok == GEQ) || (curtok == LT) || (curtok == GT)) { int op = curtok; readtok (); val2 = expshift (); if (op == LEQ) val1 = val1 <= val2; else if (op == GEQ) val1 = val1 >= val2; else if (op == LT) val1 = val1 < val2; else /* (op == GT) */ val1 = val1 > val2; } return (val1); } /* Left and right shifts. */ static long expshift () { register long val1, val2; val1 = exp3 (); while ((curtok == LSH) || (curtok == RSH)) { int op = curtok; readtok (); val2 = exp3 (); if (op == LSH) val1 = val1 << val2; else val1 = val1 >> val2; } return (val1); } static long exp3 () { register long val1, val2; val1 = exp2 (); while ((curtok == PLUS) || (curtok == MINUS)) { int op = curtok; readtok (); val2 = exp2 (); if (op == PLUS) val1 += val2; else if (op == MINUS) val1 -= val2; } return (val1); } static long exp2 () { register long val1, val2; val1 = exppower (); while ((curtok == MUL) || (curtok == DIV) || (curtok == MOD)) { int op = curtok; readtok (); val2 = exppower (); if (((op == DIV) || (op == MOD)) && (val2 == 0)) evalerror ("division by 0"); if (op == MUL) val1 *= val2; else if (op == DIV) val1 /= val2; else if (op == MOD) val1 %= val2; } return (val1); } static long exppower () { register long val1, val2, c; val1 = exp1 (); if (curtok == POWER) { readtok (); val2 = exp1 (); if (val2 == 0) return (1); if (val2 < 0) evalerror ("exponent less than 0"); for (c = 1; val2--; c *= val1) ; val1 = c; } return (val1); } static long exp1 () { register long val; if (curtok == NOT) { readtok (); val = !exp1 (); } else if (curtok == BNOT) { readtok (); val = ~exp1 (); } else val = exp0 (); return (val); } static long exp0 () { register long val = 0, v2; char *vincdec; int stok; /* XXX - might need additional logic here to decide whether or not pre-increment or pre-decrement is legal at this point. */ if (curtok == PREINC || curtok == PREDEC) { stok = lasttok = curtok; readtok (); if (curtok != STR) /* readtok() catches this */ evalerror ("identifier expected after pre-increment or pre-decrement"); v2 = tokval + ((stok == PREINC) ? 1 : -1); vincdec = itos (v2); if (noeval == 0) (void)bind_int_variable (tokstr, vincdec); free (vincdec); val = v2; curtok = NUM; /* make sure --x=7 is flagged as an error */ readtok (); } else if (curtok == MINUS) { readtok (); val = - exp0 (); } else if (curtok == PLUS) { readtok (); val = exp0 (); } else if (curtok == LPAR) { readtok (); val = EXP_HIGHEST (); if (curtok != RPAR) evalerror ("missing `)'"); /* Skip over closing paren. */ readtok (); } else if ((curtok == NUM) || (curtok == STR)) { val = tokval; if (curtok == STR && (*tp == '+' || *tp == '-') && tp[1] == *tp && (tp[2] == '\0' || (ISALNUM ((unsigned char)tp[2]) == 0))) { /* post-increment or post-decrement */ v2 = val + ((*tp == '+') ? 1 : -1); vincdec = itos (v2); if (noeval == 0) (void)bind_int_variable (tokstr, vincdec); free (vincdec); tp += 2; curtok = NUM; /* make sure x++=7 is flagged as an error */ } readtok (); } else evalerror ("syntax error: operand expected"); return (val); } /* Lexical analyzer/token reader for the expression evaluator. Reads the next token and puts its value into curtok, while advancing past it. Updates value of tp. May also set tokval (for number) or tokstr (for string). */ static void readtok () { register char *cp; register unsigned char c, c1; register int e; /* Skip leading whitespace. */ cp = tp; c = e = 0; while (cp && (c = *cp) && (cr_whitespace (c))) cp++; if (c) cp++; lasttp = tp = cp - 1; if (c == '\0') { lasttok = curtok; curtok = 0; tp = cp; return; } if (legal_variable_starter (c)) { /* variable names not preceded with a dollar sign are shell variables. */ char *value, *savecp; EXPR_CONTEXT ec; int peektok; while (legal_variable_char (c)) c = *cp++; c = *--cp; #if defined (ARRAY_VARS) if (c == '[') { e = skipsubscript (cp, 0); if (cp[e] == ']') { cp += e + 1; c = *cp; e = ']'; } else evalerror ("bad array subscript"); } #endif /* ARRAY_VARS */ *cp = '\0'; FREE (tokstr); tokstr = savestring (tp); *cp = c; SAVETOK (&ec); tokstr = (char *)NULL; /* keep it from being freed */ tp = savecp = cp; noeval = 1; readtok (); peektok = curtok; if (peektok == STR) /* free new tokstr before old one is restored */ FREE (tokstr); RESTORETOK (&ec); cp = savecp; /* The tests for PREINC and PREDEC aren't strictly correct, but they preserve old behavior if a construct like --x=9 is given. */ if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ) { #if defined (ARRAY_VARS) value = (e == ']') ? get_array_value (tokstr, 0) : get_string_value (tokstr); #else value = get_string_value (tokstr); #endif tokval = (value && *value) ? subexpr (value) : 0; #if defined (ARRAY_VARS) if (e == ']') FREE (value); /* get_array_value returns newly-allocated memory */ #endif } else tokval = 0; lasttok = curtok; curtok = STR; } else if (DIGIT(c)) { while (ISALNUM (c) || c == '#' || c == '@' || c == '_') c = *cp++; c = *--cp; *cp = '\0'; tokval = strlong (tp); *cp = c; lasttok = curtok; curtok = NUM; } else { c1 = *cp++; if ((c == EQ) && (c1 == EQ)) c = EQEQ; else if ((c == NOT) && (c1 == EQ)) c = NEQ; else if ((c == GT) && (c1 == EQ)) c = GEQ; else if ((c == LT) && (c1 == EQ)) c = LEQ; else if ((c == LT) && (c1 == LT)) { if (*cp == '=') /* a <<= b */ { assigntok = LSH; c = OP_ASSIGN; cp++; } else c = LSH; } else if ((c == GT) && (c1 == GT)) { if (*cp == '=') { assigntok = RSH; /* a >>= b */ c = OP_ASSIGN; cp++; } else c = RSH; } else if ((c == BAND) && (c1 == BAND)) c = LAND; else if ((c == BOR) && (c1 == BOR)) c = LOR; else if ((c == '*') && (c1 == '*')) c = POWER; else if ((c == '-') && (c1 == '-') && legal_variable_starter ((unsigned char)*cp)) c = PREDEC; else if ((c == '+') && (c1 == '+') && legal_variable_starter ((unsigned char)*cp)) c = PREINC; else if (c1 == EQ && member (c, "*/%+-&^|")) { assigntok = c; /* a OP= b */ c = OP_ASSIGN; } else cp--; /* `unget' the character */ lasttok = curtok; curtok = c; } tp = cp; } static void evalerror (msg) char *msg; { char *name, *t; name = this_command_name; for (t = expression; whitespace (*t); t++) ; internal_error ("%s%s%s: %s (error token is \"%s\")", name ? name : "", name ? ": " : "", t, msg, (lasttp && *lasttp) ? lasttp : ""); longjmp (evalbuf, 1); } /* Convert a string to a long integer, with an arbitrary base. 0nnn -> base 8 0[Xx]nn -> base 16 Anything else: [base#]number (this is implemented to match ksh93) Base may be >=2 and <=64. If base is <= 36, the numbers are drawn from [0-9][a-zA-Z], and lowercase and uppercase letters may be used interchangably. If base is > 36 and <= 64, the numbers are drawn from [0-9][a-z][A-Z]_@ (a = 10, z = 35, A = 36, Z = 61, _ = 62, @ = 63 -- you get the picture). */ static long strlong (num) char *num; { register char *s; register unsigned char c; int base, foundbase; long val; s = num; base = 10; foundbase = 0; if (*s == '0') { s++; if (*s == '\0') return 0; /* Base 16? */ if (*s == 'x' || *s == 'X') { base = 16; s++; } else base = 8; foundbase++; } val = 0; for (c = *s++; c; c = *s++) { if (c == '#') { if (foundbase) evalerror ("bad number"); /* Illegal base specifications raise an evaluation error. */ if (val < 2 || val > 64) evalerror ("illegal arithmetic base"); base = val; val = 0; foundbase++; } else if (ISALNUM(c) || (c == '_') || (c == '@')) { if (DIGIT(c)) c = TODIGIT(c); else if (c >= 'a' && c <= 'z') c -= 'a' - 10; else if (c >= 'A' && c <= 'Z') c -= 'A' - ((base <= 36) ? 10 : 36); else if (c == '@') c = 62; else if (c == '_') c = 63; if (c >= base) evalerror ("value too great for base"); val = (val * base) + c; } else break; } return (val); } #if defined (EXPR_TEST) void * xmalloc (n) int n; { return (malloc (n)); } void * xrealloc (s, n) char *s; int n; { return (realloc (s, n)); } SHELL_VAR *find_variable () { return 0;} SHELL_VAR *bind_variable () { return 0; } char *get_string_value () { return 0; } procenv_t top_level; main (argc, argv) int argc; char **argv; { register int i; long v; int expok; if (setjmp (top_level)) exit (0); for (i = 1; i < argc; i++) { v = evalexp (argv[i], &expok); if (expok == 0) fprintf (stderr, "%s: expression error\n", argv[i]); else printf ("'%s' -> %ld\n", argv[i], v); } exit (0); } int builtin_error (format, arg1, arg2, arg3, arg4, arg5) char *format; { fprintf (stderr, "expr: "); fprintf (stderr, format, arg1, arg2, arg3, arg4, arg5); fprintf (stderr, "\n"); return 0; } char * itos (n) long n; { return ("42"); } #endif /* EXPR_TEST */ /* findcmd.c -- Functions to search for commands by name. */ /* Copyright (C) 1997 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include #include "chartypes.h" #include "bashtypes.h" #ifndef _MINIX # include #endif #include "filecntl.h" #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include #endif #include "bashansi.h" #include "memalloc.h" #include "shell.h" #include "flags.h" #include "hashlib.h" #include "pathexp.h" #include "hashcmd.h" #include "findcmd.h" /* matching prototypes and declarations */ extern int posixly_correct; /* Static functions defined and used in this file. */ static char *_find_user_command_internal __P((const char *, int)); static char *find_user_command_internal __P((const char *, int)); static char *find_user_command_in_path __P((const char *, char *, int)); static char *find_in_path_element __P((const char *, char *, int, int, struct stat *)); static char *find_absolute_program __P((const char *, int)); static char *get_next_path_element __P((char *, int *)); /* The file name which we would try to execute, except that it isn't possible to execute it. This is the first file that matches the name that we are looking for while we are searching $PATH for a suitable one to execute. If we cannot find a suitable executable file, then we use this one. */ static char *file_to_lose_on; /* Non-zero if we should stat every command found in the hash table to make sure it still exists. */ int check_hashed_filenames; /* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command () encounters a `.' as the directory pathname while scanning the list of possible pathnames; i.e., if `.' comes before the directory containing the file of interest. */ int dot_found_in_search = 0; #define u_mode_bits(x) (((x) & 0000700) >> 6) #define g_mode_bits(x) (((x) & 0000070) >> 3) #define o_mode_bits(x) (((x) & 0000007) >> 0) #define X_BIT(x) ((x) & 1) /* Return some flags based on information about this file. The EXISTS bit is non-zero if the file is found. The EXECABLE bit is non-zero the file is executble. Zero is returned if the file is not found. */ int file_status (name) const char *name; { struct stat finfo; /* Determine whether this file exists or not. */ if (stat (name, &finfo) < 0) return (0); /* If the file is a directory, then it is not "executable" in the sense of the shell. */ if (S_ISDIR (finfo.st_mode)) return (FS_EXISTS|FS_DIRECTORY); #if defined (AFS) /* We have to use access(2) to determine access because AFS does not support Unix file system semantics. This may produce wrong answers for non-AFS files when ruid != euid. I hate AFS. */ if (access (name, X_OK) == 0) return (FS_EXISTS | FS_EXECABLE); else return (FS_EXISTS); #else /* !AFS */ /* Find out if the file is actually executable. By definition, the only other criteria is that the file has an execute bit set that we can use. */ /* Root only requires execute permission for any of owner, group or others to be able to exec a file. */ if (current_user.euid == (uid_t)0) { int bits; bits = (u_mode_bits (finfo.st_mode) | g_mode_bits (finfo.st_mode) | o_mode_bits (finfo.st_mode)); if (X_BIT (bits)) return (FS_EXISTS | FS_EXECABLE); } /* If we are the owner of the file, the owner execute bit applies. */ if (current_user.euid == finfo.st_uid && X_BIT (u_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); /* If we are in the owning group, the group permissions apply. */ if (group_member (finfo.st_gid) && X_BIT (g_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); /* If `others' have execute permission to the file, then so do we, since we are also `others'. */ if (X_BIT (o_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); return (FS_EXISTS); #endif /* !AFS */ } /* Return non-zero if FILE exists and is executable. Note that this function is the definition of what an executable file is; do not change this unless YOU know what an executable file is. */ int executable_file (file) const char *file; { int s; s = file_status (file); return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0)); } int is_directory (file) const char *file; { return (file_status (file) & FS_DIRECTORY); } int executable_or_directory (file) const char *file; { int s; s = file_status (file); return ((s & FS_EXECABLE) || (s & FS_DIRECTORY)); } /* Locate the executable file referenced by NAME, searching along the contents of the shell PATH variable. Return a new string which is the full pathname to the file, or NULL if the file couldn't be found. If a file is found that isn't executable, and that is the only match, then return that. */ char * find_user_command (name) const char *name; { return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS)); } /* Locate the file referenced by NAME, searching along the contents of the shell PATH variable. Return a new string which is the full pathname to the file, or NULL if the file couldn't be found. This returns the first file found. */ char * find_path_file (name) const char *name; { return (find_user_command_internal (name, FS_EXISTS)); } static char * _find_user_command_internal (name, flags) const char *name; int flags; { char *path_list, *cmd; SHELL_VAR *var; /* Search for the value of PATH in both the temporary environment, and in the regular list of variables. */ if (var = find_variable_internal ("PATH", 1)) /* XXX could be array? */ path_list = value_cell (var); else path_list = (char *)NULL; if (path_list == 0 || *path_list == '\0') return (savestring (name)); cmd = find_user_command_in_path (name, path_list, flags); if (var && tempvar_p (var)) dispose_variable (var); return (cmd); } static char * find_user_command_internal (name, flags) const char *name; int flags; { #ifdef __WIN32__ char *res, *dotexe; dotexe = (char *)xmalloc (strlen (name) + 5); strcpy (dotexe, name); strcat (dotexe, ".exe"); res = _find_user_command_internal (dotexe, flags); free (dotexe); if (res == 0) res = _find_user_command_internal (name, flags); return res; #else return (_find_user_command_internal (name, flags)); #endif } /* Return the next element from PATH_LIST, a colon separated list of paths. PATH_INDEX_POINTER is the address of an index into PATH_LIST; the index is modified by this function. Return the next element of PATH_LIST or NULL if there are no more. */ static char * get_next_path_element (path_list, path_index_pointer) char *path_list; int *path_index_pointer; { char *path; path = extract_colon_unit (path_list, path_index_pointer); if (path == 0) return (path); if (*path == '\0') { free (path); path = savestring ("."); } return (path); } /* Look for PATHNAME in $PATH. Returns either the hashed command corresponding to PATHNAME or the first instance of PATHNAME found in $PATH. Returns a newly-allocated string. */ char * search_for_command (pathname) const char *pathname; { char *hashed_file, *command; int temp_path, st; SHELL_VAR *path; hashed_file = command = (char *)NULL; /* If PATH is in the temporary environment for this command, don't use the hash table to search for the full pathname. */ path = find_tempenv_variable ("PATH"); temp_path = path != 0; /* Don't waste time trying to find hashed data for a pathname that is already completely specified or if we're using a command- specific value for PATH. */ if (path == 0 && absolute_program (pathname) == 0) hashed_file = find_hashed_filename (pathname); /* If a command found in the hash table no longer exists, we need to look for it in $PATH. Thank you Posix.2. This forces us to stat every command found in the hash table. */ if (hashed_file && (posixly_correct || check_hashed_filenames)) { st = file_status (hashed_file); if ((st ^ (FS_EXISTS | FS_EXECABLE)) != 0) { remove_hashed_filename (pathname); free (hashed_file); hashed_file = (char *)NULL; } } if (hashed_file) command = hashed_file; else if (absolute_program (pathname)) /* A command containing a slash is not looked up in PATH or saved in the hash table. */ command = savestring (pathname); else { /* If $PATH is in the temporary environment, we've already retrieved it, so don't bother trying again. */ if (temp_path) { command = find_user_command_in_path (pathname, value_cell (path), FS_EXEC_PREFERRED|FS_NODIRS); if (tempvar_p (path)) dispose_variable (path); } else command = find_user_command (pathname); if (command && hashing_enabled && temp_path == 0) remember_filename ((char *)pathname, command, dot_found_in_search, 1); /* XXX fix const later */ } return (command); } char * user_command_matches (name, flags, state) const char *name; int flags, state; { register int i; int path_index, name_len; char *path_list, *path_element, *match; struct stat dotinfo; static char **match_list = NULL; static int match_list_size = 0; static int match_index = 0; if (state == 0) { /* Create the list of matches. */ if (match_list == 0) { match_list_size = 5; match_list = alloc_array (match_list_size); } /* Clear out the old match list. */ for (i = 0; i < match_list_size; i++) match_list[i] = 0; /* We haven't found any files yet. */ match_index = 0; if (absolute_program (name)) { match_list[0] = find_absolute_program (name, flags); match_list[1] = (char *)NULL; path_list = (char *)NULL; } else { name_len = strlen (name); file_to_lose_on = (char *)NULL; dot_found_in_search = 0; stat (".", &dotinfo); path_list = get_string_value ("PATH"); path_index = 0; } while (path_list && path_list[path_index]) { path_element = get_next_path_element (path_list, &path_index); if (path_element == 0) break; match = find_in_path_element (name, path_element, flags, name_len, &dotinfo); free (path_element); if (match == 0) continue; if (match_index + 1 == match_list_size) { match_list_size += 10; match_list = (char **)xrealloc (match_list, (match_list_size + 1) * sizeof (char *)); } match_list[match_index++] = match; match_list[match_index] = (char *)NULL; FREE (file_to_lose_on); file_to_lose_on = (char *)NULL; } /* We haven't returned any strings yet. */ match_index = 0; } match = match_list[match_index]; if (match) match_index++; return (match); } static char * find_absolute_program (name, flags) const char *name; int flags; { int st; st = file_status (name); /* If the file doesn't exist, quit now. */ if ((st & FS_EXISTS) == 0) return ((char *)NULL); /* If we only care about whether the file exists or not, return this filename. Otherwise, maybe we care about whether this file is executable. If it is, and that is what we want, return it. */ if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE))) return (savestring (name)); return (NULL); } static char * find_in_path_element (name, path, flags, name_len, dotinfop) const char *name; char *path; int flags, name_len; struct stat *dotinfop; { int status; char *full_path, *xpath; xpath = (*path == '~') ? bash_tilde_expand (path) : path; /* Remember the location of "." in the path, in all its forms (as long as they begin with a `.', e.g. `./.') */ if (dot_found_in_search == 0 && *xpath == '.') dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL); full_path = sh_makepath (xpath, name, 0); status = file_status (full_path); if (xpath != path) free (xpath); if ((status & FS_EXISTS) == 0) { free (full_path); return ((char *)NULL); } /* The file exists. If the caller simply wants the first file, here it is. */ if (flags & FS_EXISTS) return (full_path); /* If the file is executable, then it satisfies the cases of EXEC_ONLY and EXEC_PREFERRED. Return this file unconditionally. */ if ((status & FS_EXECABLE) && (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0))) { FREE (file_to_lose_on); file_to_lose_on = (char *)NULL; return (full_path); } /* The file is not executable, but it does exist. If we prefer an executable, then remember this one if it is the first one we have found. */ if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0) file_to_lose_on = savestring (full_path); /* If we want only executable files, or we don't want directories and this file is a directory, fail. */ if ((flags & FS_EXEC_ONLY) || (flags & FS_EXEC_PREFERRED) || ((flags & FS_NODIRS) && (status & FS_DIRECTORY))) { free (full_path); return ((char *)NULL); } else return (full_path); } /* This does the dirty work for find_user_command_internal () and user_command_matches (). NAME is the name of the file to search for. PATH_LIST is a colon separated list of directories to search. FLAGS contains bit fields which control the files which are eligible. Some values are: FS_EXEC_ONLY: The file must be an executable to be found. FS_EXEC_PREFERRED: If we can't find an executable, then the the first file matching NAME will do. FS_EXISTS: The first file found will do. FS_NODIRS: Don't find any directories. */ static char * find_user_command_in_path (name, path_list, flags) const char *name; char *path_list; int flags; { char *full_path, *path; int path_index, name_len; struct stat dotinfo; /* We haven't started looking, so we certainly haven't seen a `.' as the directory path yet. */ dot_found_in_search = 0; if (absolute_program (name)) { full_path = find_absolute_program (name, flags); return (full_path); } if (path_list == 0 || *path_list == '\0') return (savestring (name)); /* XXX */ file_to_lose_on = (char *)NULL; name_len = strlen (name); stat (".", &dotinfo); path_index = 0; while (path_list[path_index]) { /* Allow the user to interrupt out of a lengthy path search. */ QUIT; path = get_next_path_element (path_list, &path_index); if (path == 0) break; /* Side effects: sets dot_found_in_search, possibly sets file_to_lose_on. */ full_path = find_in_path_element (name, path, flags, name_len, &dotinfo); free (path); /* This should really be in find_in_path_element, but there isn't the right combination of flags. */ if (full_path && is_directory (full_path)) { free (full_path); continue; } if (full_path) { FREE (file_to_lose_on); return (full_path); } } /* We didn't find exactly what the user was looking for. Return the contents of FILE_TO_LOSE_ON which is NULL when the search required an executable, or non-NULL if a file was found and the search would accept a non-executable as a last resort. If the caller specified FS_NODIRS, and file_to_lose_on is a directory, return NULL. */ if (file_to_lose_on && (flags & FS_NODIRS) && is_directory (file_to_lose_on)) { free (file_to_lose_on); file_to_lose_on = (char *)NULL; } return (file_to_lose_on); } /* flags.c -- Everything about flags except the `set' command. That is in builtins.c */ /* Copyright (C) 1987,1989 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ /* Flags hacking. */ #include "config.h" #if defined (HAVE_UNISTD_H) # include #endif #include "shell.h" #include "flags.h" #if defined (BANG_HISTORY) # include "bashhist.h" #endif #if defined (JOB_CONTROL) extern int set_job_control __P((int)); #endif #if defined (RESTRICTED_SHELL) extern char *shell_name; #endif /* -c, -s invocation options -- not really flags, but they show up in $- */ extern int want_pending_command, read_from_stdin; /* **************************************************************** */ /* */ /* The Standard Sh Flags. */ /* */ /* **************************************************************** */ /* Non-zero means automatically mark variables which are modified or created as auto export variables. */ int mark_modified_vars = 0; /* Non-zero causes asynchronous job notification. Otherwise, job state notification only takes place just before a primary prompt is printed. */ int asynchronous_notification = 0; /* Non-zero means exit immediately if a command exits with a non-zero exit status. */ int exit_immediately_on_error = 0; /* Non-zero means disable filename globbing. */ int disallow_filename_globbing = 0; /* Non-zero means that all keyword arguments are placed into the environment for a command, not just those that appear on the line before the command name. */ int place_keywords_in_env = 0; /* Non-zero means read commands, but don't execute them. This is useful for debugging shell scripts that should do something hairy and possibly destructive. */ int read_but_dont_execute = 0; /* Non-zero means end of file is after one command. */ int just_one_command = 0; /* Non-zero means don't overwrite existing files while doing redirections. */ int noclobber = 0; /* Non-zero means trying to get the value of $i where $i is undefined causes an error, instead of a null substitution. */ int unbound_vars_is_error = 0; /* Non-zero means type out input lines after you read them. */ int echo_input_at_read = 0; /* Non-zero means type out the command definition after reading, but before executing. */ int echo_command_at_execute = 0; /* Non-zero means turn on the job control features. */ int jobs_m_flag = 0; /* Non-zero means this shell is interactive, even if running under a pipe. */ int forced_interactive = 0; /* By default, follow the symbolic links as if they were real directories while hacking the `cd' command. This means that `cd ..' moves up in the string of symbolic links that make up the current directory, instead of the absolute directory. The shell variable `nolinks' also controls this flag. */ int no_symbolic_links = 0; /* **************************************************************** */ /* */ /* Non-Standard Flags Follow Here. */ /* */ /* **************************************************************** */ #if 0 /* Non-zero means do lexical scoping in the body of a FOR command. */ int lexical_scoping = 0; #endif /* Non-zero means no such thing as invisible variables. */ int no_invisible_vars = 0; /* Non-zero means look up and remember command names in a hash table, */ int hashing_enabled = 1; #if defined (BANG_HISTORY) /* Non-zero means that we are doing history expansion. The default. This means !22 gets the 22nd line of history. */ int history_expansion = 1; #endif /* BANG_HISTORY */ /* Non-zero means that we allow comments to appear in interactive commands. */ int interactive_comments = 1; #if defined (RESTRICTED_SHELL) /* Non-zero means that this shell is `restricted'. A restricted shell disallows: changing directories, command or path names containing `/', unsetting or resetting the values of $PATH and $SHELL, and any type of output redirection. */ int restricted = 0; /* currently restricted */ int restricted_shell = 0; /* shell was started in restricted mode. */ #endif /* RESTRICTED_SHELL */ /* Non-zero means that this shell is running in `privileged' mode. This is required if the shell is to run setuid. If the `-p' option is not supplied at startup, and the real and effective uids or gids differ, disable_priv_mode is called to relinquish setuid status. */ int privileged_mode = 0; #if defined (BRACE_EXPANSION) /* Zero means to disable brace expansion: foo{a,b} -> fooa foob */ int brace_expansion = 1; #endif /* **************************************************************** */ /* */ /* The Flags ALIST. */ /* */ /* **************************************************************** */ struct flags_alist shell_flags[] = { /* Standard sh flags. */ { 'a', &mark_modified_vars }, #if defined (JOB_CONTROL) { 'b', &asynchronous_notification }, #endif /* JOB_CONTROL */ { 'e', &exit_immediately_on_error }, { 'f', &disallow_filename_globbing }, { 'h', &hashing_enabled }, { 'i', &forced_interactive }, { 'k', &place_keywords_in_env }, #if defined (JOB_CONTROL) { 'm', &jobs_m_flag }, #endif /* JOB_CONTROL */ { 'n', &read_but_dont_execute }, { 'p', &privileged_mode }, #if defined (RESTRICTED_SHELL) { 'r', &restricted }, #endif /* RESTRICTED_SHELL */ { 't', &just_one_command }, { 'u', &unbound_vars_is_error }, { 'v', &echo_input_at_read }, { 'x', &echo_command_at_execute }, { 'C', &noclobber }, /* New flags that control non-standard things. */ #if 0 { 'l', &lexical_scoping }, #endif { 'I', &no_invisible_vars }, { 'P', &no_symbolic_links }, #if defined (BRACE_EXPANSION) { 'B', &brace_expansion }, #endif #if defined (BANG_HISTORY) { 'H', &history_expansion }, #endif /* BANG_HISTORY */ {0, (int *)NULL} }; #define NUM_SHELL_FLAGS (sizeof (shell_flags) / sizeof (struct flags_alist)) int * find_flag (name) int name; { int i; for (i = 0; shell_flags[i].name; i++) { if (shell_flags[i].name == name) return (shell_flags[i].value); } return (FLAG_UNKNOWN); } /* Change the state of a flag, and return it's original value, or return FLAG_ERROR if there is no flag FLAG. ON_OR_OFF must be either FLAG_ON or FLAG_OFF. */ int change_flag (flag, on_or_off) int flag; int on_or_off; { int *value, old_value; #if defined (RESTRICTED_SHELL) /* Don't allow "set +r" in a shell which is `restricted'. */ if (restricted && flag == 'r' && on_or_off == FLAG_OFF) return (FLAG_ERROR); #endif /* RESTRICTED_SHELL */ value = find_flag (flag); if ((value == (int *)FLAG_UNKNOWN) || (on_or_off != FLAG_ON && on_or_off != FLAG_OFF)) return (FLAG_ERROR); old_value = *value; *value = (on_or_off == FLAG_ON) ? 1 : 0; /* Special cases for a few flags. */ switch (flag) { #if defined (BANG_HISTORY) case 'H': if (on_or_off == FLAG_ON) bash_initialize_history (); break; #endif #if defined (JOB_CONTROL) case 'm': set_job_control (on_or_off == FLAG_ON); break; #endif /* JOB_CONTROL */ case 'n': if (interactive_shell) read_but_dont_execute = 0; break; case 'p': if (on_or_off == FLAG_OFF) disable_priv_mode (); break; #if defined (RESTRICTED_SHELL) case 'r': if (on_or_off == FLAG_ON) maybe_make_restricted (shell_name); break; #endif } return (old_value); } /* Return a string which is the names of all the currently set shell flags. */ char * which_set_flags () { char *temp; int i, string_index; temp = (char *)xmalloc (1 + NUM_SHELL_FLAGS + read_from_stdin + want_pending_command); for (i = string_index = 0; shell_flags[i].name; i++) if (*(shell_flags[i].value)) temp[string_index++] = shell_flags[i].name; if (want_pending_command) temp[string_index++] = 'c'; if (read_from_stdin) temp[string_index++] = 's'; temp[string_index] = '\0'; return (temp); } void reset_shell_flags () { mark_modified_vars = exit_immediately_on_error = disallow_filename_globbing = 0; place_keywords_in_env = read_but_dont_execute = just_one_command = 0; noclobber = unbound_vars_is_error = echo_input_at_read = 0; echo_command_at_execute = jobs_m_flag = forced_interactive = 0; no_symbolic_links = no_invisible_vars = privileged_mode = 0; hashing_enabled = interactive_comments = 1; #if defined (JOB_CONTROL) asynchronous_notification = 0; #endif #if defined (BANG_HISTORY) history_expansion = 1; #endif #if defined (BRACE_EXPANSION) brace_expansion = 1; #endif #if defined (RESTRICTED_SHELL) restricted = 0; #endif } /* general.c -- Stuff that is used by all files. */ /* Copyright (C) 1987-1999 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. Bash is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Bash is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Bash; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */ #include "config.h" #include "bashtypes.h" #ifndef _MINIX # include #endif #include "posixstat.h" #if defined (HAVE_UNISTD_H) # include #endif #include "filecntl.h" #include "bashansi.h" #include #include "chartypes.h" #include #include "shell.h" #include #if !defined (errno) extern int errno; #endif /* !errno */ extern int expand_aliases; extern int interrupt_immediately; extern int interactive_comments; extern int check_hashed_filenames; extern int source_uses_path; extern int source_searches_cwd; /* A standard error message to use when getcwd() returns NULL. */ char *bash_getcwd_errstr = "getcwd: cannot access parent directories"; /* Do whatever is necessary to initialize `Posix mode'. */ void posix_initialize (on) int on; { /* Things that should be turned on when posix mode is enabled. */ if (on != 0) { interactive_comments = source_uses_path = expand_aliases = 1; } /* Things that should be turned on when posix mode is disabled. */ if (on == 0) { source_searches_cwd = 1; expand_aliases = interactive_shell; } } /* **************************************************************** */ /* */ /* Functions to convert to and from and display non-standard types */ /* */ /* **************************************************************** */ #if defined (RLIMTYPE) RLIMTYPE string_to_rlimtype (s) char *s; { RLIMTYPE ret; int neg; ret = 0; neg = 0; while (s && *s && whitespace (*s)) s++; if (*s == '-' || *s == '+') { neg = *s == '-'; s++; } for ( ; s && *s && DIGIT (*s); s++) ret = (ret * 10) + TODIGIT (*s); return (neg ? -ret : ret); } void print_rlimtype (n, addnl) RLIMTYPE n; int addnl; { char s[INT_STRLEN_BOUND (RLIMTYPE) + 1], *p; p = s + sizeof(s); *--p = '\0'; if (n < 0) { do *--p = '0' - n % 10; while ((n /= 10) != 0); *--p = '-'; } else { do *--p = '0' + n % 10; while ((n /= 10) != 0); } printf ("%s%s", p, addnl ? "\n" : ""); } #endif /* RLIMTYPE */ /* **************************************************************** */ /* */ /* Input Validation Functions */ /* */ /* **************************************************************** */ /* Return non-zero if all of the characters in STRING are digits. */ int all_digits (string) char *string; { register char *s; for (s = string; *s; s++) if (DIGIT (*s) == 0) return (0); return (1); } /* Return non-zero if the characters pointed to by STRING constitute a valid number. Stuff the converted number into RESULT if RESULT is not null. */ int legal_number (string, result) char *string; long *result; { long value; char *ep; if (result) *result = 0; errno = 0; value = strtol (string, &ep, 10); if (errno) return 0; /* errno is set on overflow or underflow */ /* Skip any trailing whitespace, since strtol does not. */ while (whitespace (*ep)) ep++; /* If *string is not '\0' but *ep is '\0' on return, the entire string is valid. */ if (string && *string && *ep == '\0') { if (result) *result = value; /* The SunOS4 implementation of strtol() will happily ignore overflow conditions, so this cannot do overflow correctly on those systems. */ return 1; } return (0); } /* Return 1 if this token is a legal shell `identifier'; that is, it consists solely of letters, digits, and underscores, and does not begin with a digit. */ int legal_identifier (name) char *name; { register char *s; unsigned char c; if (!name || !(c = *name) || (legal_variable_starter (c) == 0)) return (0); for (s = name + 1; (c = *s) != 0; s++) { if (legal_variable_char (c) == 0) return (0); } return (1); } /* Make sure that WORD is a valid shell identifier, i.e. does not contain a dollar sign, nor is quoted in any way. Nor does it consist of all digits. If CHECK_WORD is non-zero, the word is checked to ensure that it consists of only letters, digits, and underscores. */ int check_identifier (word, check_word) WORD_DESC *word; int check_word; { if ((word->flags & (W_HASDOLLAR|W_QUOTED)) || all_digits (word->word)) { internal_error ("`%s': not a valid identifier", word->word); return (0); } else if (check_word && legal_identifier (word->word) == 0) { internal_error ("`%s': not a valid identifier", word->word); return (0); } else return (1); } /* **************************************************************** */ /* */ /* Functions to manage files and file descriptors */ /* */ /* ************