diff options
author | Jari Aalto <jari.aalto@cante.net> | 1996-12-23 17:02:34 +0000 |
---|---|---|
committer | Jari Aalto <jari.aalto@cante.net> | 2009-09-12 16:46:49 +0000 |
commit | ccc6cda312fea9f0468ee65b8f368e9653e1380b (patch) | |
tree | b059878adcfd876c4acb8030deda1eeb918c7e75 /execute_cmd.c | |
parent | 726f63884db0132f01745f1fb4465e6621088ccf (diff) | |
download | android_external_bash-ccc6cda312fea9f0468ee65b8f368e9653e1380b.tar.gz android_external_bash-ccc6cda312fea9f0468ee65b8f368e9653e1380b.tar.bz2 android_external_bash-ccc6cda312fea9f0468ee65b8f368e9653e1380b.zip |
Imported from ../bash-2.0.tar.gz.
Diffstat (limited to 'execute_cmd.c')
-rw-r--r-- | execute_cmd.c | 3051 |
1 files changed, 1780 insertions, 1271 deletions
diff --git a/execute_cmd.c b/execute_cmd.c index 55274ea..ce154c3 100644 --- a/execute_cmd.c +++ b/execute_cmd.c @@ -17,9 +17,11 @@ 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, 675 Mass Ave, Cambridge, MA 02139, USA. */ -#if defined (AIX) && defined (RISC6000) && !defined (__GNUC__) +#include "config.h" + +#if !defined (__GNUC__) && !defined (HAVE_ALLOCA_H) && defined (_AIX) #pragma alloca -#endif /* AIX && RISC6000 && !__GNUC__ */ +#endif /* _AIX && RISC6000 && !__GNUC__ */ #include <stdio.h> #include <ctype.h> @@ -28,32 +30,47 @@ #include "filecntl.h" #include "posixstat.h" #include <signal.h> +#include <sys/param.h> -#if !defined (SIGABRT) -#define SIGABRT SIGIOT +#if defined (HAVE_UNISTD_H) +# include <unistd.h> +#endif + +#if defined (HAVE_LIMITS_H) +# include <limits.h> +#endif + +#if defined (HAVE_SYS_TIME_H) +# include <sys/time.h> +#endif + +#if defined (HAVE_SYS_RESOURCE_H) +# include <sys/resource.h> +#endif + +#if defined (HAVE_SYS_TIMES_H) && defined (HAVE_TIMES) +# include <sys/times.h> #endif -#include <sys/param.h> #include <errno.h> #if !defined (errno) extern int errno; #endif -#if defined (HAVE_STRING_H) -# include <string.h> -#else /* !HAVE_STRING_H */ -# include <strings.h> -#endif /* !HAVE_STRING_H */ +#include "bashansi.h" +#include "memalloc.h" #include "shell.h" #include "y.tab.h" #include "flags.h" -#include "hash.h" +#include "builtins.h" +#include "hashlib.h" #include "jobs.h" #include "execute_cmd.h" +#include "trap.h" +#include "pathexp.h" -#include "sysdefs.h" #include "builtins/common.h" #include "builtins/builtext.h" /* list of builtins */ @@ -64,55 +81,91 @@ extern int errno; # 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 interactive, interactive_shell, login_shell; -extern int parse_and_execute_level; +extern int executing, breaking, continuing, loop_level; +extern int interactive, interactive_shell, login_shell, expand_aliases; +extern int parse_and_execute_level, running_trap; extern int command_string_index, variable_context, 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 Function *last_shell_builtin, *this_shell_builtin; -extern jmp_buf top_level, subshell_top_level; -extern int subshell_argc; extern char **subshell_argv, **subshell_envp; -extern int already_making_children; +extern int subshell_argc; +extern char *glob_argv_flags; extern int getdtablesize (); extern int close (); /* Static functions defined and used in this file. */ -static void close_pipes (), do_piping (), execute_disk_command (); -static void execute_subshell_builtin_or_function (); -static void cleanup_redirects (), cleanup_func_redirects (), bind_lastarg (); +static void close_pipes (), do_piping (), bind_lastarg (); +static void cleanup_redirects (); static void add_undo_close_redirect (), add_exec_redirect (); +static int add_undo_redirect (); static int do_redirection_internal (), do_redirections (); -static int expandable_redirection_filename (), execute_shell_script (); -static int execute_builtin_or_function (), add_undo_redirect (); +static int expandable_redirection_filename (); static char *find_user_command_internal (), *find_user_command_in_path (); +static char *find_in_path_element (), *find_absolute_program (); + +static int execute_for_command (); +#if defined (SELECT_COMMAND) +static int execute_select_command (); +#endif +static int time_command (); +static int execute_case_command (); +static int execute_while_command (), execute_until_command (); +static int execute_while_or_until (); +static int execute_if_command (); +static int execute_simple_command (); +static int execute_builtin (), execute_function (); +static int execute_builtin_or_function (); +static int builtin_status (); +static void execute_subshell_builtin_or_function (); +static void execute_disk_command (); +static int execute_connection (); +static int execute_intern_function (); /* The line number that the currently executing function starts on. */ -static int function_line_number = 0; +static int function_line_number; /* Set to 1 if fd 0 was the subject of redirection to a subshell. */ -static int stdin_redir = 0; +static 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; static REDIRECTEE rd; +/* 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; + /* For catching RETURN in a function. */ -int return_catch_flag = 0; +int return_catch_flag; int return_catch_value; -jmp_buf return_catch; +procenv_t return_catch; /* The value returned by the last synchronous command. */ -int last_command_exit_value = 0; +int last_command_exit_value; /* The list of redirections to perform which will undo the redirections that I made in the shell. */ @@ -125,7 +178,11 @@ 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 = 0; +int subshell_environment; + +/* Non-zero if we should stat every command found in the hash table to + make sure it still exists. */ +int check_hashed_filenames; struct fd_bitmap *current_fds_to_close = (struct fd_bitmap *)NULL; @@ -178,6 +235,16 @@ close_fd_bitmap (fdbp) } } +/* 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; + 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. @@ -185,6 +252,7 @@ close_fd_bitmap (fdbp) 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; { @@ -242,6 +310,7 @@ cleanup_redirects (list) dispose_redirects (list); } +#if 0 /* Function to unwind_protect the redirections for functions and builtins. */ static void cleanup_func_redirects (list) @@ -249,6 +318,7 @@ cleanup_func_redirects (list) { do_redirections (list, 1, 0, 0); } +#endif static void dispose_exec_redirects () @@ -280,7 +350,7 @@ open_files () fd_table_size = getdtablesize (); - fprintf (stderr, "pid %d open files:", getpid ()); + fprintf (stderr, "pid %d open files:", (int)getpid ()); for (i = 3; i < fd_table_size; i++) { if ((f = fcntl (i, F_GETFD, 0)) != -1) @@ -289,7 +359,7 @@ open_files () fprintf (stderr, "\n"); } -#define DESCRIBE_PID(pid) if (interactive) describe_pid (pid) +#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. @@ -303,30 +373,44 @@ open_files () EXECUTION_SUCCESS or EXECUTION_FAILURE are the only possible return values. Executing a command with nothing in it returns EXECUTION_SUCCESS. */ -execute_command_internal (command, asynchronous, pipe_in, pipe_out, +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 = EXECUTION_SUCCESS; - int invert, ignore_return; - REDIRECT *my_undo_list, *exec_undo_list; + int exec_result, invert, ignore_return, was_debug_trap; + REDIRECT *my_undo_list, *exec_undo_list, *rp; + pid_t last_pid; - if (!command || breaking || continuing) + if (command == 0 || breaking || continuing || read_but_dont_execute) return (EXECUTION_SUCCESS); run_pending_traps (); + if (running_trap == 0) + currently_executing_command = command; + +#if defined (COMMAND_TIMING) + if (command->flags & CMD_TIME_PIPELINE) + { + 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 */ + invert = (command->flags & CMD_INVERT_RETURN) != 0; + 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->flags & CMD_WANT_SUBSHELL) || - (command->flags & CMD_FORCE_SUBSHELL) || + if ((command->flags & (CMD_WANT_SUBSHELL|CMD_FORCE_SUBSHELL)) || (shell_control_structure (command->type) && (pipe_out != NO_PIPE || pipe_in != NO_PIPE || asynchronous))) { @@ -340,26 +424,15 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, { int user_subshell, return_code, function_value; - /* 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 (); - user_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), + 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, + 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 @@ -378,13 +451,26 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, original_pgrp = -1; #endif /* JOB_CONTROL */ interactive_shell = 0; + expand_aliases = 0; asynchronous = 0; } /* Subshells are neither login nor interactive. */ login_shell = interactive = 0; - subshell_environment = 1; + subshell_environment = user_subshell ? SUBSHELL_PAREN : SUBSHELL_ASYNC; + + reset_terminating_signals (); /* in shell.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 @@ -399,10 +485,8 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, sh compatibility, but I'm not sure it's the right thing to do. */ if (user_subshell) { - REDIRECT *r; - - for (r = command->redirects; r; r = r->next) - switch (r->instruction) + for (rp = command->redirects; rp; rp = rp->next) + switch (rp->instruction) { case r_input_direction: case r_inputa_direction: @@ -414,10 +498,11 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, case r_duplicating_input: case r_duplicating_input_word: case r_close_this: - if (r->redirector == 0) - stdin_redir++; + stdin_redir += (rp->redirector == 0); break; } + + restore_default_signal (0); } if (fds_to_close) @@ -446,7 +531,7 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, need to handle a possible `return'. */ function_value = 0; if (return_catch_flag) - function_value = setjmp (return_catch); + function_value = setjmp (return_catch); if (function_value) return_code = return_catch_value; @@ -474,25 +559,22 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, /* 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 + 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) + if (asynchronous == 0) { last_command_exit_value = wait_for (paren_pid); /* If we have to, invert the return value. */ if (invert) - { - if (last_command_exit_value == EXECUTION_SUCCESS) - return (EXECUTION_FAILURE); - else - return (EXECUTION_SUCCESS); - } + return ((last_command_exit_value == EXECUTION_SUCCESS) + ? EXECUTION_FAILURE + : EXECUTION_SUCCESS); else return (last_command_exit_value); } @@ -550,6 +632,71 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, 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; + + if (ignore_return && command->value.Simple) + command->value.Simple->flags |= CMD_IGNORE_RETURN; + 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 (was_debug_trap) + run_debug_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; @@ -632,360 +779,620 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, } break; - 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. */ - pid_t last_pid = last_made_pid; + case cm_connection: + exec_result = execute_connection (command, asynchronous, + pipe_in, pipe_out, fds_to_close); + break; - if (ignore_return && command->value.Simple) - command->value.Simple->flags |= CMD_IGNORE_RETURN; - exec_result = - execute_simple_command (command->value.Simple, pipe_in, pipe_out, - asynchronous, fds_to_close); + case cm_function_def: + exec_result = execute_intern_function (command->value.Function_def->name, + command->value.Function_def->command); + break; - /* The temporary environment should be used for only the simple - command immediately following its definition. */ - dispose_used_env_vars (); + default: + programming_error + ("execute_command: bad command type `%d'", command->type); + } -#if (defined (Ultrix) && defined (mips)) || !defined (HAVE_ALLOCA) - /* Reclaim memory allocated with alloca () on machines which - may be using the alloca emulation code. */ - (void) alloca (0); -#endif /* (Ultrix && mips) || !HAVE_ALLOCA */ + if (my_undo_list) + { + do_redirections (my_undo_list, 1, 0, 0); + dispose_redirects (my_undo_list); + } - /* If we forked to do the command, then we must wait_for () - the child. */ + if (exec_undo_list) + dispose_redirects (exec_undo_list); - /* 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 (my_undo_list || exec_undo_list) + discard_unwind_frame ("loop_redirections"); - 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); - } - } + /* 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) +static struct timeval * +difftimeval (d, t1, t2) + struct timeval *d, *t1, *t2; +{ + d->tv_sec = t2->tv_sec - t1->tv_sec; + d->tv_usec = t2->tv_usec - t1->tv_usec; + if (d->tv_usec < 0) + { + d->tv_usec += 1000000; + d->tv_sec -= 1; + if (d->tv_sec < 0) /* ??? -- BSD/OS does this */ + d->tv_sec = 0; + } + return d; +} - if (!ignore_return && exit_immediately_on_error && !invert && - (exec_result != EXECUTION_SUCCESS)) +static struct timeval * +addtimeval (d, t1, t2) + struct timeval *d, *t1, *t2; +{ + d->tv_sec = t1->tv_sec + t2->tv_sec; + d->tv_usec = t1->tv_usec + t2->tv_usec; + if (d->tv_usec > 1000000) + { + d->tv_usec -= 1000000; + d->tv_sec += 1; + } + return d; +} + +/* Do "cpu = ((user + sys) * 10000) / real;" with timevals. + Barely-tested code from Deven T. Corzine <deven@ties.org>. */ +static int +timeval_to_cpu (rt, ut, st) + struct timeval *rt, *ut, *st; /* real, user, sys */ +{ + struct timeval t1, t2; + register int i; + + addtimeval (&t1, ut, st); + t2.tv_sec = rt->tv_sec; + t2.tv_usec = rt->tv_usec; + + for (i = 0; i < 6; i++) + { + if ((t1.tv_sec > 99999999) || (t2.tv_sec > 99999999)) + break; + t1.tv_sec *= 10; + t1.tv_sec += t1.tv_usec / 100000; + t1.tv_usec *= 10; + t1.tv_usec %= 1000000; + t2.tv_sec *= 10; + t2.tv_sec += t2.tv_usec / 100000; + t2.tv_usec *= 10; + t2.tv_usec %= 1000000; + } + for (i = 0; i < 4; i++) + { + if (t1.tv_sec < 100000000) + t1.tv_sec *= 10; + else + t2.tv_sec /= 10; + } + + return (t1.tv_sec / t2.tv_sec); +} +#endif /* HAVE_GETRUSAGE && HAVE_GETTIMEOFDAY */ + +#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; + long sec; + int sec_fraction; +{ + long min; + char abuf[16]; + int ind, aind; + + ind = 0; + abuf[15] = '\0'; + + /* If LNG is non-zero, we want to decompose SEC into minutes and seconds. */ + if (lng) + { + min = sec / 60; + sec %= 60; + aind = 14; + 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 = 14; + 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++) { - last_command_exit_value = exec_result; - run_pending_traps (); - longjmp (top_level, EXITPROG); + buf[ind++] = (sec_fraction / precs[aind]) + '0'; + sec_fraction %= precs[aind]; } + } - break; + if (lng) + buf[ind++] = 's'; + buf[ind] = '\0'; - case cm_connection: - switch (command->value.Connection->connector) + 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; + long rs, us, ss; + int rsf, usf, ssf, cpu; +{ + int prec, lng, len; + char *str, *s, ts[32]; + int sum, sum_frac; + int sindex, ssize; + + len = strlen (format); + ssize = (len + 64) - (len % 64); + str = 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') { - /* Do the first command asynchronously. */ - case '&': - { - COMMAND *tc = command->value.Connection->first; - REDIRECT *rp; + 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 (isdigit (*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; + } + } - if (!tc) - break; + str[sindex] = '\0'; + fprintf (fp, "%s\n", str); + fflush (fp); - rp = tc->redirects; + free (str); +} - if (ignore_return && tc) - tc->flags |= CMD_IGNORE_RETURN; +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; + long 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 this shell was compiled without job control support, if - the shell is not running interactively, 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 ((!interactive_shell || subshell_environment || !job_control) && - !stdin_redir) +#if defined (HAVE_GETRUSAGE) && defined (HAVE_GETTIMEOFDAY) + gettimeofday (&before, &dtz); + getrusage (RUSAGE_SELF, &selfb); + getrusage (RUSAGE_CHILDREN, &kidsb); #else - if (!stdin_redir) -#endif /* JOB_CONTROL */ - { - REDIRECT *tr; +# if defined (HAVE_TIMES) + tbefore = times (&before); +# endif +#endif - rd.filename = make_word ("/dev/null"); - tr = make_redirection (0, r_inputa_direction, rd); - tr->next = tc->redirects; - tc->redirects = tr; - } + posix_time = (command->flags & CMD_TIME_POSIX); - exec_result = execute_command_internal - (tc, 1, pipe_in, pipe_out, fds_to_close); + command->flags &= ~(CMD_TIME_PIPELINE|CMD_TIME_POSIX); + rv = execute_command_internal (command, asynchronous, pipe_in, pipe_out, fds_to_close); -#if defined (JOB_CONTROL) - if ((!interactive_shell || subshell_environment || !job_control) && - !stdin_redir) +#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 (!stdin_redir) -#endif /* JOB_CONTROL */ - { - /* Remove the redirection we added above. It matters, - especially for loops, which call execute_command () - multiple times with the same command. */ - REDIRECT *tr, *tl; +# if defined (HAVE_TIMES) + tafter = times (&after); - tr = tc->redirects; - do - { - tl = tc->redirects; - tc->redirects = tc->redirects->next; - } - while (tc->redirects && tc->redirects != rp); + real = tafter - tbefore; + clock_t_to_secs (real, &rs, &rsf); - tl->next = (REDIRECT *)NULL; - dispose_redirects (tr); - } + user = (after.tms_utime - before.tms_utime) + (after.tms_cutime - before.tms_cutime); + clock_t_to_secs (user, &us, &usf); - { - register COMMAND *second; + sys = (after.tms_stime - before.tms_stime) + (after.tms_cstime - before.tms_cstime); + clock_t_to_secs (sys, &ss, &ssf); - second = command->value.Connection->second; + cpu = ((user + sys) * 10000) / real; - if (second) - { - if (ignore_return) - second->flags |= CMD_IGNORE_RETURN; +# else + rs = us = ss = 0L; + rsf = usf = ssf = cpu = 0; +# endif +#endif - exec_result = execute_command_internal - (second, asynchronous, pipe_in, pipe_out, fds_to_close); - } - } - } - break; + if (posix_time) + time_format = POSIX_TIMEFORMAT; + else if ((time_format = get_string_value ("TIMEFORMAT")) == 0) + time_format = BASH_TIMEFORMAT; - case ';': - /* Just call execute command on both of them. */ - 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; + if (time_format && *time_format) + print_formatted_time (stderr, time_format, rs, rsf, us, usf, ss, ssf, cpu); - case '|': - { - int prev, fildes[2], new_bitmap_size, dummyfd; - COMMAND *cmd; - struct fd_bitmap *fd_bitmap; + return rv; +} +#endif /* COMMAND_TIMING */ + +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); + sigset_t set, oset; + BLOCK_CHILD (set, oset); #endif /* JOB_CONTROL */ - prev = pipe_in; - cmd = command; + ignore_return = (command->flags & CMD_IGNORE_RETURN) != 0; - while (cmd && - cmd->type == cm_connection && - cmd->value.Connection && - cmd->value.Connection->connector == '|') - { - /* Make a pipeline between the two commands. */ - if (pipe (fildes) < 0) - { - report_error ("pipe error: %s", strerror (errno)); + 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 (); + 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 (); - } - else - { - /* 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. */ - if (fildes[0] < fds_to_close->size) - new_bitmap_size = fds_to_close->size; - else - new_bitmap_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); + 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); + 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; - } + 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); - /* 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); + prev = fildes[0]; + close (fildes[1]); - if (prev >= 0) - close (prev); + 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); + UNBLOCK_CHILD (oset); #endif - } - break; - case AND_AND: - case OR_OR: - if (asynchronous) + return (exec_result); +} + +static int +execute_connection (command, asynchronous, pipe_in, pipe_out, fds_to_close) + COMMAND *command; + int asynchronous, pipe_in, pipe_out; + struct fd_bitmap *fds_to_close; +{ + REDIRECT *tr, *tl, *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) + tc->flags |= CMD_IGNORE_RETURN; + + /* If this shell was compiled without job control support, if + the shell is not running interactively, 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 ((!interactive_shell || subshell_environment || !job_control) && !stdin_redir) +#else + if (!stdin_redir) +#endif /* JOB_CONTROL */ + { + rd.filename = make_bare_word ("/dev/null"); + tr = make_redirection (0, r_inputa_direction, rd); + tr->next = tc->redirects; + tc->redirects = tr; + } + + exec_result = execute_command_internal (tc, 1, pipe_in, pipe_out, fds_to_close); + +#if defined (JOB_CONTROL) + if ((!interactive_shell || subshell_environment || !job_control) && !stdin_redir) +#else + if (!stdin_redir) +#endif /* JOB_CONTROL */ + { + /* 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 { - /* 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; + tl = tc->redirects; + tc->redirects = tc->redirects->next; } + while (tc->redirects && tc->redirects != rp); - /* 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. */ + tl->next = (REDIRECT *)NULL; + dispose_redirects (tr); + } - if (command->value.Connection->first) - command->value.Connection->first->flags |= CMD_IGNORE_RETURN; + second = command->value.Connection->second; + if (second) + { + if (ignore_return) + second->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_internal (second, asynchronous, pipe_in, pipe_out, fds_to_close); + } - exec_result = - execute_command (command->value.Connection->second); - } - break; + break; - default: - programming_error ("Bad connector `%d'!", - command->value.Connection->connector); - longjmp (top_level, DISCARD); - 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 cm_function_def: - exec_result = intern_function (command->value.Function_def->name, - command->value.Function_def->command); + case '|': + exec_result = execute_pipeline (command, asynchronous, pipe_in, pipe_out, fds_to_close); break; - default: - programming_error - ("execute_command: Bad command type `%d'!", command->type); - } + 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; + } - if (my_undo_list) - { - do_redirections (my_undo_list, 1, 0, 0); - dispose_redirects (my_undo_list); - } + /* 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 (exec_undo_list) - dispose_redirects (exec_undo_list); + if (command->value.Connection->first) + command->value.Connection->first->flags |= CMD_IGNORE_RETURN; - if (my_undo_list || exec_undo_list) - discard_unwind_frame ("loop_redirections"); + 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; - /* Invert the return value if we have to */ - if (invert) - { - if (exec_result == EXECUTION_SUCCESS) - exec_result = EXECUTION_FAILURE; - else - exec_result = EXECUTION_SUCCESS; + exec_result = execute_command (command->value.Connection->second); + } + break; + + default: + programming_error ("execute_connection: bad connector `%d'", command->value.Connection->connector); + jump_to_top_level (DISCARD); + exec_result = EXECUTION_FAILURE; } - last_command_exit_value = exec_result; - run_pending_traps (); - return (last_command_exit_value); + return exec_result; } #if defined (JOB_CONTROL) @@ -1009,22 +1416,27 @@ execute_command_internal (command, asynchronous, pipe_in, pipe_out, /* 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; { - /* I just noticed that the Bourne shell leaves word_desc bound to the - last name in word_list after the FOR statement is done. This seems - wrong to me; I thought that the variable binding should be lexically - scoped, i.e., only would last the duration of the FOR command. This - behaviour can be gotten by turning on the lexical_scoping switch. */ - 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. */ - int retval = EXECUTION_SUCCESS; +#endif if (check_identifier (for_command->name, 1) == 0) - return (EXECUTION_FAILURE); + { + 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; @@ -1034,28 +1446,43 @@ execute_for_command (for_command) 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; - while (list) + for (retval = EXECUTION_SUCCESS; list; list = list->next) { QUIT; - bind_variable (identifier, list->word->word); - execute_command (for_command->action); - retval = last_command_exit_value; + this_command_name = (char *)NULL; + v = bind_variable (identifier, list->word->word); + if (readonly_p (v)) + { + if (interactive_shell == 0 && posixly_correct) + { + last_command_exit_value = EXECUTION_FAILURE; + jump_to_top_level (FORCE_EOF); + } + else + { + run_unwind_frame ("for"); + return (EXECUTION_FAILURE); + } + } + retval = execute_command (for_command->action); REAP (); QUIT; if (breaking) { - breaking--; + breaking--; break; } @@ -1065,12 +1492,11 @@ execute_for_command (for_command) if (continuing) break; } - - list = list->next; } loop_level--; +#if 0 if (lexical_scoping) { if (!old_value) @@ -1084,6 +1510,7 @@ execute_for_command (for_command) dispose_variable (old_value); } } +#endif dispose_words (releaser); discard_unwind_frame ("for"); @@ -1115,10 +1542,8 @@ print_index_and_element (len, ind, list) if (list == 0) return (0); - i = ind; - l = list; - while (l && --i) - l = l->next; + 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)); } @@ -1156,7 +1581,7 @@ print_select_list (list, list_len, max_elem_len, indices_len) return; } - cols = COLS / max_elem_len; + 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; @@ -1232,8 +1657,8 @@ select_query (list, list_len, prompt) while (1) { print_select_list (list, list_len, max_elem_len, indices_len); - printf ("%s", prompt); - fflush (stdout); + fprintf (stderr, "%s", prompt); + fflush (stderr); QUIT; if (read_builtin ((WORD_LIST *)NULL) == EXECUTION_FAILURE) @@ -1248,9 +1673,8 @@ select_query (list, list_len, prompt) if (reply < 1 || reply > list_len) return ""; - l = list; - while (l && --reply) - l = l->next; + for (l = list; l && --reply; l = l->next) + ; return (l->word->word); } } @@ -1259,18 +1683,14 @@ select_query (list, list_len, prompt) 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, return_val; -#if 0 - SHELL_VAR *old_value = (SHELL_VAR *)0; -#endif - - - retval = EXECUTION_SUCCESS; if (check_identifier (select_command->name, 1) == 0) return (EXECUTION_FAILURE); @@ -1292,18 +1712,11 @@ execute_select_command (select_command) begin_unwind_frame ("select"); 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 (select_command->flags & CMD_IGNORE_RETURN) select_command->action->flags |= CMD_IGNORE_RETURN; + retval = EXECUTION_SUCCESS; + unwind_protect_int (return_catch_flag); unwind_protect_jmp_buf (return_catch); return_catch_flag++; @@ -1311,7 +1724,7 @@ execute_select_command (select_command) while (1) { ps3_prompt = get_string_value ("PS3"); - if (!ps3_prompt) + if (ps3_prompt == 0) ps3_prompt = "#? "; QUIT; @@ -1319,8 +1732,21 @@ execute_select_command (select_command) QUIT; if (selection == 0) break; - else - bind_variable (identifier, selection); + + v = bind_variable (identifier, selection); + if (readonly_p (v)) + { + if (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); + } + } return_val = setjmp (return_catch); @@ -1344,22 +1770,6 @@ execute_select_command (select_command) 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 - run_unwind_frame ("select"); return (retval); } @@ -1369,49 +1779,48 @@ execute_select_command (select_command) 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; + WORD_LIST *wlist, *es; PATTERN_LIST *clauses; - char *word; - int retval; + char *word, *pattern; + int retval, match, ignore_return; /* Posix.2 specifies that the WORD is tilde expanded. */ if (member ('~', case_command->word->word)) { - word = tilde_expand (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_no_split (case_command->word, 0); - clauses = case_command->clauses; - word = (wlist) ? string_list (wlist) : savestring (""); + 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 (dispose_words, wlist); add_unwind_protect ((Function *)xfree, word); - while (clauses) +#define EXIT_CASE() goto exit_case_command + + for (clauses = case_command->clauses; clauses; clauses = clauses->next) { QUIT; - list = clauses->patterns; - while (list) + for (list = clauses->patterns; list; list = list->next) { - char *pattern; - WORD_LIST *es; - int match; - /* Posix.2 specifies to tilde expand each member of the pattern list. */ if (member ('~', list->word->word)) { - char *expansion = tilde_expand (list->word->word); + pattern = bash_tilde_expand (list->word->word); free (list->word->word); - list->word->word = expansion; + list->word->word = pattern; } es = expand_word_leave_quoted (list->word, 0); @@ -1419,38 +1828,34 @@ execute_case_command (case_command) if (es && es->word && es->word->word && *(es->word->word)) pattern = quote_string_for_globbing (es->word->word, 1); else - pattern = savestring (""); + { + pattern = xmalloc (1); + pattern[0] = '\0'; + } /* Since the pattern does not undergo quote removal (as per Posix.2, section 3.9.4.3), the fnmatch () call must be able to recognize backslashes as escape characters. */ - match = (fnmatch (pattern, word, 0) != FNM_NOMATCH); + match = fnmatch (pattern, word, 0) != FNM_NOMATCH; free (pattern); dispose_words (es); if (match) { - if (clauses->action && - (case_command->flags & CMD_IGNORE_RETURN)) + if (clauses->action && ignore_return) clauses->action->flags |= CMD_IGNORE_RETURN; - execute_command (clauses->action); - retval = last_command_exit_value; - goto exit_command; + retval = execute_command (clauses->action); + EXIT_CASE (); } - list = list->next; QUIT; } - - clauses = clauses->next; } - exit_command: - dispose_words (wlist); - free (word); +exit_case_command: + free (word); discard_unwind_frame ("case"); - return (retval); } @@ -1460,6 +1865,7 @@ execute_case_command (case_command) /* 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; { @@ -1467,6 +1873,7 @@ execute_while_command (while_command) } /* UNTIL is just like WHILE except that the test result is negated. */ +static int execute_until_command (while_command) WHILE_COM *while_command; { @@ -1478,6 +1885,7 @@ execute_until_command (while_command) 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; @@ -1526,6 +1934,7 @@ execute_while_or_until (while_command, type) /* 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; { @@ -1537,8 +1946,10 @@ execute_if_command (if_command) 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; + if_command->true_case->flags |= CMD_IGNORE_RETURN; + return (execute_command (if_command->true_case)); } else @@ -1558,39 +1969,117 @@ bind_lastarg (arg) { SHELL_VAR *var; - if (!arg) + if (arg == 0) arg = ""; var = bind_variable ("_", arg); var->attributes &= ~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, 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); + 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; +} + /* 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; - int first_word_quoted, result; + char *command_line, *lastarg, *temp; + int first_word_quoted, result, builtin_is_special; pid_t old_last_command_subst_pid; + Function *builtin; + SHELL_VAR *func; result = EXECUTION_SUCCESS; + special_builtin_failed = builtin_is_special = 0; - /* If we're in a function, update the pseudo-line-number information. */ + /* 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); - command_line = (char *)alloca (1 + strlen (the_printed_command)); + command_line = xmalloc (1 + strlen (the_printed_command)); strcpy (command_line, the_printed_command); first_word_quoted = - simple_command->words ? simple_command->words->word->quoted : 0; + simple_command->words ? (simple_command->words->word->flags & W_QUOTED): 0; old_last_command_subst_pid = last_command_subst_pid; @@ -1599,249 +2088,251 @@ execute_simple_command (simple_command, pipe_in, pipe_out, async, fds_to_close) 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); - lastarg = (char *)NULL; - /* 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) + if (words == 0) { - Function *builtin; - SHELL_VAR *func; + result = execute_null_command (simple_command->redirects, + pipe_in, pipe_out, async, + old_last_command_subst_pid); + FREE (command_line); + bind_lastarg ((char *)NULL); + return (result); + } - begin_unwind_frame ("simple-command"); + lastarg = (char *)NULL; - if (echo_command_at_execute) - { - char *line = string_list (words); + begin_unwind_frame ("simple-command"); - if (line && *line) - fprintf (stderr, "%s%s\n", indirection_level_string (), line); + if (echo_command_at_execute) + xtrace_print_word_list (words); - FREE (line); + builtin = (Function *)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 (simple_command->flags & CMD_NO_FUNCTIONS) - func = (SHELL_VAR *)NULL; - else + if (builtin == 0) func = find_function (words->word->word); + } - add_unwind_protect (dispose_words, words); - - QUIT; + 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; + /* 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] == '%') - { - int result; + /* Is this command a job control related thing? */ + if (words->word->word[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; + } - if (async) - this_command_name = "bg"; - else - this_command_name = "fg"; + /* 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 && 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, substring, match, started_status; + register PROCESS *p; + + word = words->word->word; + exact = STREQ (temp, "exact"); + substring = STREQ (temp, "substring"); + wl = strlen (word); + for (i = job_slots - 1; i > -1; i--) + { + if (jobs[i] == 0 || (JOBSTATE (i) != JSTOPPED)) + continue; - last_shell_builtin = this_shell_builtin; - this_shell_builtin = builtin_address (this_command_name); - result = (*this_shell_builtin) (words); - goto return_result; - } + p = jobs[i]->pipe; + do + { + if (exact) + { + cl = strlen (p->command); + match = STREQN (p->command, word, cl); + } + else if (substring) + match = strindex (p->command, word) != (char *)0; + else + match = STREQN (p->command, word, wl); - /* 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. */ - { - char *auto_resume_value = get_string_value ("auto_resume"); - - if (auto_resume_value && - !first_word_quoted && - !words->next && - words->word->word[0] && - !simple_command->redirects && - pipe_in == NO_PIPE && - pipe_out == NO_PIPE && - !async) - { - char *word = words->word->word; - register int i; - int wl, cl, exact, substring, match, started_status; - register PROCESS *p; - - exact = STREQ (auto_resume_value, "exact"); - substring = STREQ (auto_resume_value, "substring"); - wl = strlen (word); - for (i = job_slots - 1; i > -1; i--) - { - if (!jobs[i] || (JOBSTATE (i) != JSTOPPED)) + if (match == 0) + { + p = p->next; continue; + } - p = jobs[i]->pipe; - do - { - if (exact) - { - cl = strlen (p->command); - match = STREQN (p->command, word, cl); - } - else if (substring) - 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"); - last_shell_builtin = this_shell_builtin; - this_shell_builtin = builtin_address ("fg"); - - started_status = start_job (i, 1); - - if (started_status < 0) - return (EXECUTION_FAILURE); - else - return (started_status); - } - while (p != jobs[i]->pipe); - } - } - } -#endif /* JOB_CONTROL */ + run_unwind_frame ("simple-command"); + this_command_name = "fg"; + last_shell_builtin = this_shell_builtin; + this_shell_builtin = builtin_address ("fg"); - /* Remember the name of this command globally. */ - this_command_name = words->word->word; + started_status = start_job (i, 1); + return ((started_status < 0) ? EXECUTION_FAILURE : started_status); + } + while (p != jobs[i]->pipe); + } + } +#endif /* JOB_CONTROL */ - QUIT; + /* Remember the name of this command globally. */ + this_command_name = words->word->word; - /* This command could be a shell builtin or a user-defined function. - If so, and we have pipes, then fork a subshell in here. Else, just - do the command. */ + QUIT; - if (func) - builtin = (Function *)NULL; - else - builtin = find_shell_builtin (this_command_name); + /* 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; + last_shell_builtin = this_shell_builtin; + this_shell_builtin = builtin; - if (builtin || func) + if (builtin || func) + { + if ((pipe_in != NO_PIPE) || (pipe_out != NO_PIPE) || async) { - if ((pipe_in != NO_PIPE) || (pipe_out != NO_PIPE) || async) + if (make_child (command_line, async) == 0) { - if (make_child (savestring (command_line), async) == 0) - { - /* Cancel traps, in trap.c. */ - restore_original_signals (); + /* reset_terminating_signals (); */ /* XXX */ + /* Cancel traps, in trap.c. */ + restore_original_signals (); - if (async) - setup_async_signals (); + if (async) + 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 - { - close_pipes (pipe_in, pipe_out); -#if defined (PROCESS_SUBSTITUTION) && defined (HAVE_DEV_FD) - unlink_fifo_list (); -#endif - goto return_result; - } + 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); - + 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. */ goto return_result; } } - - execute_disk_command (words, simple_command->redirects, command_line, - pipe_in, pipe_out, async, fds_to_close, - (simple_command->flags & CMD_NO_FORK)); - - goto return_result; - } - else 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 (savestring (""), async) == 0) - { - /* Cancel traps, in trap.c. */ - restore_original_signals (); - - do_piping (pipe_in, pipe_out); - - subshell_environment = 1; - - if (do_redirections (simple_command->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 - result = EXECUTION_SUCCESS; + 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; + } + goto return_result; } } - 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 (simple_command->redirects, 0, 0, 0) != 0) - result = EXECUTION_FAILURE; - else if (old_last_command_subst_pid != last_command_subst_pid) - result = last_command_exit_value; - else - result = EXECUTION_SUCCESS; - } + execute_disk_command (words, simple_command->redirects, command_line, + pipe_in, pipe_out, async, fds_to_close, + (simple_command->flags & CMD_NO_FORK)); return_result: bind_lastarg (lastarg); - /* The unwind-protect frame is set up only if WORDS is not empty. */ - if (words) - run_unwind_frame ("simple-command"); + 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) Function *builtin; WORD_LIST *words; int flags, subshell; { - int old_e_flag = exit_immediately_on_error; - int result; + int old_e_flag, result; + 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 @@ -1880,6 +2371,11 @@ execute_builtin (builtin, words, flags, subshell) if (subshell == 0 && builtin == source_builtin) { + /* In POSIX mode, if any variable assignments precede the `.' builtin, + they persist after the builtin completes, since `.' is a special + builtin. */ + if (posixly_correct && builtin_env) + merge_builtin_env (); dispose_builtin_env (); discard_unwind_frame ("builtin_env"); } @@ -1893,8 +2389,6 @@ execute_builtin (builtin, words, flags, subshell) return (result); } -/* XXX -- why do we need to set up unwind-protects for the case where - subshell == 1 at all? */ static int execute_function (var, words, flags, fds_to_close, async, subshell) SHELL_VAR *var; @@ -1904,36 +2398,44 @@ execute_function (var, words, flags, fds_to_close, async, subshell) { int return_val, result; COMMAND *tc, *fc; + char *debug_trap; tc = (COMMAND *)copy_command (function_cell (var)); if (tc && (flags & CMD_IGNORE_RETURN)) tc->flags |= CMD_IGNORE_RETURN; - if (subshell) - begin_unwind_frame ("subshell_function_calling"); - else - begin_unwind_frame ("function_calling"); - 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_int (loop_level); } - else - unwind_protect_int (variable_context); - unwind_protect_int (loop_level); - unwind_protect_int (return_catch_flag); - unwind_protect_jmp_buf (return_catch); - add_unwind_protect (dispose_command, (char *)tc); + debug_trap = (signal_is_trapped (DEBUG_TRAP) && signal_is_ignored (DEBUG_TRAP) == 0) + ? trap_list[DEBUG_TRAP] + : (char *)NULL; + if (debug_trap) + { + if (subshell == 0) + { + debug_trap = savestring (debug_trap); + add_unwind_protect (set_debug_trap, debug_trap); + } + restore_default_signal (DEBUG_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); - add_unwind_protect (dispose_function_env, (char *)NULL); + if (subshell == 0) + add_unwind_protect (dispose_function_env, (char *)NULL); dispose_used_env_vars (); } #if 0 @@ -1941,10 +2443,9 @@ execute_function (var, words, flags, fds_to_close, async, subshell) function_env = (char **)NULL; #endif - /* Note the second argument of "1", meaning that we discard - the current value of "$*"! This is apparently the right thing. */ remember_args (words->next, 1); + /* Number of the line on which the function body starts. */ line_number = function_line_number = tc->line; if (subshell) @@ -1952,10 +2453,7 @@ execute_function (var, words, flags, fds_to_close, async, subshell) #if defined (JOB_CONTROL) stop_pipeline (async, (COMMAND *)NULL); #endif - if (tc->type == cm_group) - fc = tc->value.Group->command; - else - fc = tc; + fc = (tc->type == cm_group) ? tc->value.Group->command : tc; if (fc && (flags & CMD_IGNORE_RETURN)) fc->flags |= CMD_IGNORE_RETURN; @@ -1973,9 +2471,7 @@ execute_function (var, words, flags, fds_to_close, async, subshell) else result = execute_command_internal (fc, 0, NO_PIPE, NO_PIPE, fds_to_close); - if (subshell) - run_unwind_frame ("subshell_function_calling"); - else + if (subshell == 0) run_unwind_frame ("function_calling"); return (result); @@ -1999,12 +2495,14 @@ execute_subshell_builtin_or_function (words, redirects, builtin, var, struct fd_bitmap *fds_to_close; int flags; { + int result, r; + /* A subshell is neither a login shell nor interactive. */ login_shell = interactive = 0; - subshell_environment = 1; + subshell_environment = SUBSHELL_ASYNC; - maybe_make_export_env (); + maybe_make_export_env (); /* XXX - is this needed? */ #if defined (JOB_CONTROL) /* Eradicate all traces of job control after we fork the subshell, so @@ -2033,8 +2531,6 @@ execute_subshell_builtin_or_function (words, redirects, builtin, var, if (builtin) { - int result; - /* Give builtins a place to jump back to on failure, so we don't go back up to main(). */ result = setjmp (top_level); @@ -2044,12 +2540,15 @@ execute_subshell_builtin_or_function (words, redirects, builtin, var, else if (result) exit (EXECUTION_FAILURE); else - exit (execute_builtin (builtin, words, flags, 1)); + { + 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)); - } + exit (execute_function (var, words, flags, fds_to_close, async, 1)); } /* Execute a builtin or function in the current shell context. If BUILTIN @@ -2070,7 +2569,7 @@ execute_builtin_or_function (words, builtin, var, redirects, struct fd_bitmap *fds_to_close; int flags; { - int result = EXECUTION_FAILURE; + int result; REDIRECT *saved_undo_list; if (do_redirections (redirects, 1, 1, 0) != 0) @@ -2078,7 +2577,7 @@ execute_builtin_or_function (words, builtin, var, redirects, cleanup_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; dispose_exec_redirects (); - return (EXECUTION_FAILURE); + return (EX_REDIRFAIL); /* was EXECUTION_FAILURE */ } saved_undo_list = redirection_undo_list; @@ -2096,8 +2595,7 @@ execute_builtin_or_function (words, builtin, var, redirects, if (saved_undo_list) { begin_unwind_frame ("saved redirects"); - add_unwind_protect (cleanup_func_redirects, (char *)saved_undo_list); - add_unwind_protect (dispose_redirects, (char *)saved_undo_list); + add_unwind_protect (cleanup_redirects, (char *)saved_undo_list); } redirection_undo_list = (REDIRECT *)NULL; @@ -2115,8 +2613,7 @@ execute_builtin_or_function (words, builtin, var, redirects, if (redirection_undo_list) { - do_redirections (redirection_undo_list, 1, 0, 0); - dispose_redirects (redirection_undo_list); + cleanup_redirects (redirection_undo_list); redirection_undo_list = (REDIRECT *)NULL; } @@ -2163,72 +2660,29 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out, struct fd_bitmap *fds_to_close; int nofork; /* Don't fork, just exec, if no pipes */ { - register char *pathname; - char *hashed_file, *command, **args; - int pid, temp_path; - SHELL_VAR *path; + char *pathname, *command, **args; + int pid; pathname = words->word->word; + #if defined (RESTRICTED_SHELL) if (restricted && strchr (pathname, '/')) { - report_error ("%s: restricted: cannot specify `/' in command names", + internal_error ("%s: restricted: cannot specify `/' in command names", pathname); last_command_exit_value = EXECUTION_FAILURE; return; } #endif /* RESTRICTED_SHELL */ - 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. */ - temp_path = 0; - path = find_tempenv_variable ("PATH"); - if (path) - temp_path = 1; - - /* Don't waste time trying to find hashed data for a pathname - that is already completely specified. */ - - if (!path && !absolute_program (pathname)) - 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. It seems pretty stupid to me, - so I am basing it on the presence of POSIXLY_CORRECT. */ - - if (hashed_file && posixly_correct) - { - int st; - - st = file_status (hashed_file); - if ((st ^ (FS_EXISTS | FS_EXECABLE)) != 0) - { - remove_hashed_filename (pathname); - hashed_file = (char *)NULL; - } - } + command = search_for_command (pathname); - if (hashed_file) - command = savestring (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 (command) { - command = find_user_command (pathname); - if (command && !hashing_disabled && !temp_path) - remember_filename (pathname, command, dot_found_in_search, 1); + maybe_make_export_env (); + put_command_name_into_env (command); } - maybe_make_export_env (); - - if (command) - put_command_name_into_env (command); - /* We have to make the child before we check for the non-existance 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, @@ -2242,6 +2696,11 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out, { int old_interactive; +#if !defined (ARG_MAX) || ARG_MAX >= 10240 + if (posixly_correct == 0) + put_gnu_argv_flags_into_env ((int)getpid (), glob_argv_flags); +#endif + /* Cancel traps, in trap.c. */ restore_original_signals (); @@ -2253,23 +2712,18 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out, do_piping (pipe_in, pipe_out); - /* 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 = make_word_array (words); - if (async) { old_interactive = interactive; interactive = 0; } - subshell_environment = 1; + subshell_environment = SUBSHELL_FORK; /* 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 + 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); @@ -2287,12 +2741,16 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out, if (async) interactive = old_interactive; - if (!command) + if (command == 0) { - report_error ("%s: command not found", args[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 @@ -2306,6 +2764,7 @@ execute_disk_command (words, redirects, command_line, pipe_in, pipe_out, } } +#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 @@ -2335,9 +2794,10 @@ execute_shell_script (sample, sample_len, command, args, env) i++) ; - execname = xmalloc (1 + (i - start)); - strncpy (execname, (char *) (sample + start), i - start); - execname[i - start] = '\0'; + larry = i - start; + execname = xmalloc (1 + larry); + strncpy (execname, (char *)(sample + start), larry); + execname[larry] = '\0'; size_increment = 1; /* Now the argument, if any. */ @@ -2355,9 +2815,10 @@ execute_shell_script (sample, sample_len, command, args, env) !whitespace (sample[i]) && sample[i] != '\n' && i < sample_len; i++) ; - firstarg = xmalloc (1 + (i - start)); - strncpy (firstarg, (char *)(sample + start), i - start); - firstarg[i - start] = '\0'; + larry = i - start; + firstarg = xmalloc (1 + larry); + strncpy (firstarg, (char *)(sample + start), larry); + firstarg[larry] = '\0'; size_increment = 2; } @@ -2382,6 +2843,13 @@ execute_shell_script (sample, sample_len, command, args, env) return (shell_execve (execname, args, env)); } +#endif /* !HAVE_HASH_BANG_EXEC */ + +#if defined (HAVE_SETOSTYPE) && defined (_POSIX_SOURCE) +# define SETOSTYPE(x) __setostype(x) +#else +# define SETOSTYPE(x) +#endif /* Call execve (), handling interpreting shell scripts, and handling exec failures. */ @@ -2390,130 +2858,170 @@ shell_execve (command, args, env) char *command; char **args, **env; { -#if defined (isc386) && defined (_POSIX_SOURCE) - __setostype (0); /* Turn on USGr3 semantics. */ - execve (command, args, env); - __setostype (1); /* Turn the POSIX semantics back on. */ -#else + struct stat finfo; + int larray, i, fd; + + SETOSTYPE (0); /* Some systems use for USG/POSIX semantics */ execve (command, args, env); -#endif /* !(isc386 && _POSIX_SOURCE) */ + SETOSTYPE (1); /* If we get to this point, then start checking out the file. Maybe it is something we can hack ourselves. */ - { - struct stat finfo; - - if (errno != ENOEXEC) - { - if ((stat (command, &finfo) == 0) && - (S_ISDIR (finfo.st_mode))) - report_error ("%s: is a directory", args[0]); - else + if (errno != ENOEXEC) + { + i = errno; + if ((stat (command, &finfo) == 0) && (S_ISDIR (finfo.st_mode))) + internal_error ("%s: is a directory", command); + else + { + errno = i; file_error (command); + } + return (EX_NOEXEC); /* XXX Posix.2 says that exit status is 126 */ + } - return (EX_NOEXEC); /* XXX Posix.2 says that exit status is 126 */ - } - else - { - /* 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 first line (or up to 30 characters) are in the ASCII set. - Execute the contents as shell commands. */ - int larray = array_len (args) + 1; - int i, should_exec = 0; - + /* 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 first line (or up to 80 characters) are in the ASCII set. + Execute the contents as shell commands. */ + fd = open (command, O_RDONLY); + if (fd >= 0) + { + unsigned char sample[80]; + int sample_len; + + sample_len = read (fd, (char *)sample, 80); + close (fd); + + 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) { - int fd = open (command, O_RDONLY); - if (fd != -1) +#if !defined (HAVE_HASH_BANG_EXEC) + if (sample[0] == '#' && sample[1] == '!') + return (execute_shell_script (sample, sample_len, command, args, env)); + else +#endif + if (check_binary_file (sample, sample_len)) { - unsigned char sample[80]; - int sample_len = read (fd, &sample[0], 80); - - close (fd); - - 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 && sample[0] == '#' && sample[1] == '!') - return (execute_shell_script - (sample, sample_len, command, args, env)); - else if ((sample_len != -1) && - check_binary_file (sample, sample_len)) - { - report_error ("%s: cannot execute binary file", command); - return (EX_BINARY_FILE); - } + internal_error ("%s: cannot execute binary file", command); + return (EX_BINARY_FILE); } } -#if defined (JOB_CONTROL) - /* Forget about the way that job control was working. We are - in a subshell. */ - without_job_control (); -#endif /* JOB_CONTROL */ + } + + larray = array_len (args) + 1; + #if defined (ALIAS) - /* Forget about any aliases that we knew of. We are in a subshell. */ - delete_all_aliases (); + /* 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) - set_sigchld_handler (); + /* Forget about the way job control was working. We are in a subshell. */ + without_job_control (); + set_sigchld_handler (); #endif /* JOB_CONTROL */ - set_sigint_handler (); - /* Insert the name of this shell into the argument list. */ - args = (char **)xrealloc ((char *)args, (1 + larray) * sizeof (char *)); + /* If we're not interactive, close the file descriptor from which we're + reading the current shell script. */ +#if defined (BUFFERED_INPUT) + if (interactive_shell == 0 && default_buffered_input >= 0) + { + close_buffered_fd (default_buffered_input); + default_buffered_input = bash_input.location.buffered_fd = -1; + } +#else + if (interactive_shell == 0 && default_input) + { + fclose (default_input); + default_input = (FILE *)NULL; + } +#endif - for (i = larray - 1; i; i--) - args[i] = args[i - 1]; + set_sigint_handler (); - args[0] = shell_name; - args[1] = command; - args[larray] = (char *)NULL; + /* Insert the name of this shell into the argument list. */ + args = (char **)xrealloc ((char *)args, (1 + larray) * sizeof (char *)); - if (args[0][0] == '-') - args[0]++; + for (i = larray - 1; i; i--) + args[i] = args[i - 1]; - if (should_exec) - { - struct stat finfo; + args[0] = shell_name; + args[1] = command; + args[larray] = (char *)NULL; -#if defined (isc386) && defined (_POSIX_SOURCE) - __setostype (0); /* Turn on USGr3 semantics. */ - execve (shell_name, args, env); - __setostype (1); /* Turn the POSIX semantics back on. */ -#else - execve (shell_name, args, env); -#endif /* isc386 && _POSIX_SOURCE */ + if (args[0][0] == '-') + args[0]++; - /* Oh, no! We couldn't even exec this! */ - if ((stat (args[0], &finfo) == 0) && (S_ISDIR (finfo.st_mode))) - report_error ("%s: is a directory", args[0]); - else - file_error (args[0]); +#if defined (RESTRICTED_SHELL) + if (restricted) + change_flag ('r', FLAG_OFF); +#endif - return (EXECUTION_FAILURE); - } - else - { - subshell_argc = larray; - subshell_argv = args; - subshell_envp = env; - longjmp (subshell_top_level, 1); - } - } - } + 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); +} + +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)) + { + internal_error ("%s: readonly function", var->name); + return (EXECUTION_FAILURE); + } + + bind_function (name->word, function); + return (EXECUTION_SUCCESS); } #if defined (PROCESS_SUBSTITUTION) -/* Currently unused */ void close_all_files () { @@ -2547,8 +3055,7 @@ do_piping (pipe_in, pipe_out) if (pipe_in != NO_PIPE) { if (dup2 (pipe_in, 0) < 0) - internal_error ("cannot duplicate fd %d to fd 0: %s", - pipe_in, strerror (errno)); + sys_error ("cannot duplicate fd %d to fd 0", pipe_in); if (pipe_in > 0) close (pipe_in); } @@ -2557,19 +3064,64 @@ do_piping (pipe_in, pipe_out) if (pipe_out != REDIRECT_BOTH) { if (dup2 (pipe_out, 1) < 0) - internal_error ("cannot duplicate fd %d to fd 1: %s", - pipe_out, strerror (errno)); + sys_error ("cannot duplicate fd %d to fd 1", pipe_out); if (pipe_out == 0 || pipe_out > 1) close (pipe_out); } else - dup2 (1, 2); + if (dup2 (1, 2) < 0) + sys_error ("cannot duplicate fd 1 to fd 2"); } } -#define AMBIGUOUS_REDIRECT -1 -#define NOCLOBBER_REDIRECT -2 -#define RESTRICTED_REDIRECT -3 /* Only can happen in restricted shells. */ +static void +redirection_error (temp, error) + REDIRECT *temp; + int error; +{ + char *filename; + + if (expandable_redirection_filename (temp)) + { + if (posixly_correct && !interactive_shell) + disallow_filename_globbing++; + filename = redirection_expand (temp->redirectee.filename); + if (posixly_correct && !interactive_shell) + disallow_filename_globbing--; + if (filename == 0) + filename = savestring (temp->redirectee.filename->word); + if (filename == 0) + { + filename = xmalloc (1); + filename[0] = '\0'; + } + } + else + filename = itos (temp->redirectee.dest); + + switch (error) + { + case AMBIGUOUS_REDIRECT: + internal_error ("%s: ambiguous redirect", filename); + break; + + case NOCLOBBER_REDIRECT: + internal_error ("%s: cannot overwrite existing file", filename); + break; + +#if defined (RESTRICTED_SHELL) + case RESTRICTED_REDIRECT: + internal_error ("%s: restricted: cannot redirect output", filename); + break; +#endif /* RESTRICTED_SHELL */ + + default: + internal_error ("%s: %s", filename, strerror (error)); + break; + } + + FREE (filename); +} /* Perform the redirections on LIST. If FOR_REAL, then actually make input and output file descriptors, otherwise just do whatever is @@ -2582,8 +3134,8 @@ do_redirections (list, for_real, internal, set_clexec) REDIRECT *list; int for_real, internal, set_clexec; { - register int error; - register REDIRECT *temp = list; + int error; + REDIRECT *temp; if (internal) { @@ -2596,54 +3148,14 @@ do_redirections (list, for_real, internal, set_clexec) dispose_exec_redirects (); } - while (temp) + for (temp = list; temp; temp = temp->next) { error = do_redirection_internal (temp, for_real, internal, set_clexec); - if (error) { - char *filename; - - if (expandable_redirection_filename (temp)) - { - if (posixly_correct && !interactive_shell) - disallow_filename_globbing++; - filename = redirection_expand (temp->redirectee.filename); - if (posixly_correct && !interactive_shell) - disallow_filename_globbing--; - - if (!filename) - filename = savestring (""); - } - else - filename = itos (temp->redirectee.dest); - - switch (error) - { - case AMBIGUOUS_REDIRECT: - report_error ("%s: Ambiguous redirect", filename); - break; - - case NOCLOBBER_REDIRECT: - report_error ("%s: Cannot clobber existing file", filename); - break; - -#if defined (RESTRICTED_SHELL) - case RESTRICTED_REDIRECT: - report_error ("%s: output redirection restricted", filename); - break; -#endif /* RESTRICTED_SHELL */ - - default: - report_error ("%s: %s", filename, strerror (error)); - break; - } - - free (filename); + redirection_error (temp, error); return (error); } - - temp = temp->next; } return (0); } @@ -2654,8 +3166,6 @@ static int expandable_redirection_filename (redirect) REDIRECT *redirect; { - int result; - switch (redirect->instruction) { case r_output_direction: @@ -2667,15 +3177,13 @@ expandable_redirection_filename (redirect) case r_output_force: case r_duplicating_input_word: case r_duplicating_output_word: - result = 1; - break; + return 1; default: - result = 0; + return 0; } - return (result); } - + /* Expand the word in WORD returning a string. If WORD expands to multiple words (or no words), then return NULL. */ char * @@ -2697,11 +3205,80 @@ redirection_expand (word) dispose_words (tlist2); return ((char *)NULL); } - result = string_list (tlist2); + result = string_list (tlist2); /* XXX savestring (tlist2->word->word)? */ dispose_words (tlist2); return (result); } +static int +write_here_document (fd, redirectee) + int fd; + WORD_DESC *redirectee; +{ + char *document; + int document_len, fd2; + FILE *fp; + register WORD_LIST *t, *tlist; + + /* Expand the text if the word that was specified had + no quoting. The text that we expand is treated + exactly as if it were surrounded by double quotes. */ + + if (redirectee->flags & W_QUOTED) + { + document = redirectee->word; + document_len = strlen (document); + /* Set errno to something reasonable if the write fails. */ + if (write (fd, document, document_len) < document_len) + { + if (errno == 0) + errno = ENOSPC; + return (errno); + } + else + return 0; + } + + tlist = expand_string (redirectee->word, Q_HERE_DOCUMENT); + if (tlist) + { + /* Try using buffered I/O (stdio) and writing a word + at a time, letting stdio do the work of buffering + for us rather than managing our own strings. Most + stdios are not particularly fast, however -- this + may need to be reconsidered later. */ + if ((fd2 = dup (fd)) < 0 || (fp = fdopen (fd2, "w")) == NULL) + { + if (fd2 >= 0) + close (fd2); + return (errno); + } + errno = 0; + for (t = tlist; t; t = t->next) + { + /* This is essentially the body of + string_list_internal expanded inline. */ + document = t->word->word; + document_len = strlen (document); + if (t != tlist) + putc (' ', fp); /* separator */ + fwrite (document, document_len, 1, fp); + if (ferror (fp)) + { + if (errno == 0) + errno = ENOSPC; + fd2 = errno; + fclose(fp); + dispose_words (tlist); + return (fd2); + } + } + fclose (fp); + dispose_words (tlist); + } + return 0; +} + /* Do the specific redirection requested. Returns errno in case of error. If FOR_REAL is zero, then just do whatever is neccessary to produce the appropriate side effects. REMEMBERING, if non-zero, says to remember @@ -2712,12 +3289,17 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) REDIRECT *redirect; int for_real, remembering, set_clexec; { - WORD_DESC *redirectee = redirect->redirectee.filename; - int redir_fd = redirect->redirectee.dest; - int fd, redirector = redirect->redirector; + WORD_DESC *redirectee; + int redir_fd, fd, redirector, r; char *redirectee_word; - enum r_instruction ri = redirect->instruction; + enum r_instruction ri; REDIRECT *new_redirect; + struct stat finfo; + + redirectee = redirect->redirectee.filename; + redir_fd = redirect->redirectee.dest; + redirector = redirect->redirector; + ri = redirect->instruction; if (ri == r_duplicating_input_word || ri == r_duplicating_output_word) { @@ -2725,7 +3307,9 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) the redirection into a new one and continue. */ redirectee_word = redirection_expand (redirectee); - if (redirectee_word[0] == '-' && redirectee_word[1] == '\0') + if (redirectee_word == 0) + return (AMBIGUOUS_REDIRECT); + else if (redirectee_word[0] == '-' && redirectee_word[1] == '\0') { rd.dest = 0L; new_redirect = make_redirection (redirector, r_close_this, rd); @@ -2745,9 +3329,9 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) } else if (ri == r_duplicating_output_word && redirector == 1) { - if (!posixly_correct) + if (posixly_correct == 0) { - rd.filename = make_word (redirectee_word); + rd.filename = make_bare_word (redirectee_word); new_redirect = make_redirection (1, r_err_and_out, rd); } else @@ -2800,22 +3384,17 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) case r_err_and_out: /* command &>filename */ case r_input_output: case r_output_force: - if (posixly_correct && !interactive_shell) disallow_filename_globbing++; redirectee_word = redirection_expand (redirectee); if (posixly_correct && !interactive_shell) disallow_filename_globbing--; - - if (!redirectee_word) + + if (redirectee_word == 0) return (AMBIGUOUS_REDIRECT); #if defined (RESTRICTED_SHELL) - if (restricted && (ri == r_output_direction || - ri == r_input_output || - ri == r_err_and_out || - ri == r_appending_to || - ri == r_output_force)) + if (restricted && (WRITE_REDIRECT (ri))) { free (redirectee_word); return (RESTRICTED_REDIRECT); @@ -2824,16 +3403,11 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) /* If we are in noclobber mode, you are not allowed to overwrite existing files. Check first. */ - if (noclobber && (ri == r_output_direction || - ri == r_input_output || - ri == r_err_and_out)) + if (noclobber && OUTPUT_REDIRECT (ri)) { - struct stat finfo; - int stat_result; - - stat_result = stat (redirectee_word, &finfo); + r = stat (redirectee_word, &finfo); - if ((stat_result == 0) && (S_ISREG (finfo.st_mode))) + if (r == 0 && (S_ISREG (finfo.st_mode))) { free (redirectee_word); return (NOCLOBBER_REDIRECT); @@ -2841,12 +3415,12 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) /* If the file was not present, make sure we open it exclusively so that if it is created before we open it, our open will fail. */ - if (stat_result != 0) + if (r != 0) redirect->flags |= O_EXCL; fd = open (redirectee_word, redirect->flags, 0666); - if ((fd < 0) && (errno == EEXIST)) + if (fd < 0 && errno == EEXIST) { free (redirectee_word); return (NOCLOBBER_REDIRECT); @@ -2855,10 +3429,10 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) else { fd = open (redirectee_word, redirect->flags, 0666); -#if defined (AFS_CREATE_BUG) +#if defined (AFS) if ((fd < 0) && (errno == EACCES)) - fd = open (redirectee_word, (redirect->flags & ~O_CREAT), 0666); -#endif /* AFS_CREATE_BUG */ + fd = open (redirectee_word, redirect->flags & ~O_CREAT, 0666); +#endif /* AFS */ } free (redirectee_word); @@ -2906,8 +3480,7 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) if (fd != redirector) { #if defined (BUFFERED_INPUT) - if (ri == r_input_direction || ri == r_inputa_direction || - ri == r_input_output) + if (INPUT_REDIRECT (ri)) close_buffered_fd (fd); else #endif /* !BUFFERED_INPUT */ @@ -2934,97 +3507,34 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) the new input. Place it in a temporary file. */ if (redirectee) { - char filename[40]; - pid_t pid = getpid (); + char filename[24]; /* Make the filename for the temp file. */ - sprintf (filename, "/tmp/t%d-sh", pid); + sprintf (filename, "/tmp/t%d-sh", (int)getpid ()); fd = open (filename, O_TRUNC | O_WRONLY | O_CREAT, 0666); if (fd < 0) return (errno); - errno = 0; /* XXX */ + errno = r = 0; /* XXX */ if (redirectee->word) - { - char *document; - int document_len; - - /* Expand the text if the word that was specified had - no quoting. The text that we expand is treated - exactly as if it were surrounded by double quotes. */ - - if (redirectee->quoted) - { - document = redirectee->word; - document_len = strlen (document); - /* Set errno to something reasonable if the write fails. */ - if (write (fd, document, document_len) < document_len) - { - if (errno == 0) - errno = ENOSPC; - close (fd); - return (errno); - } - } - else - { - WORD_LIST *tlist; - tlist = expand_string (redirectee->word, Q_HERE_DOCUMENT); - if (tlist) - { - int fd2; - FILE *fp; - register WORD_LIST *t; - - /* Try using buffered I/O (stdio) and writing a word - at a time, letting stdio do the work of buffering - for us rather than managing our own strings. Most - stdios are not particularly fast, however -- this - may need to be reconsidered later. */ - if ((fd2 = dup (fd)) < 0 || - (fp = fdopen (fd2, "w")) == NULL) - { - if (fd2 >= 0) - close (fd2); - close (fd); - return (errno); - } - errno = 0; /* XXX */ - for (t = tlist; t; t = t->next) - { - /* This is essentially the body of - string_list_internal expanded inline. */ - document = t->word->word; - document_len = strlen (document); - if (t != tlist) - putc (' ', fp); /* separator */ - fwrite (document, document_len, 1, fp); - if (ferror (fp)) - { - if (errno == 0) - errno = ENOSPC; - break; - } - } - fclose (fp); - dispose_words (tlist); - } - } - } + r = write_here_document (fd, redirectee); close (fd); - if (errno) - return (errno); + if (r) + return (r); /* Make the document really temporary. Also make it the input. */ fd = open (filename, O_RDONLY, 0666); - if (unlink (filename) < 0 || fd < 0) + if (fd < 0) + return (errno); + + if (unlink (filename) < 0) { - if (fd >= 0) - close (fd); - return (errno); + r = errno; + close (fd); + return (r); } if (for_real) @@ -3039,10 +3549,11 @@ do_redirection_internal (redirect, for_real, remembering, set_clexec) #if defined (BUFFERED_INPUT) check_bash_input (redirector); #endif - if (dup2 (fd, redirector) < 0) + if (fd != redirector && dup2 (fd, redirector) < 0) { + r = errno; close (fd); - return (errno); + return (r); } #if defined (BUFFERED_INPUT) @@ -3127,47 +3638,44 @@ add_undo_redirect (fd) int fd; { int new_fd, clexec_flag; - REDIRECT *new_redirect, *closer; + REDIRECT *new_redirect, *closer, *dummy_redirect; new_fd = fcntl (fd, F_DUPFD, SHELL_FD_BASE); if (new_fd < 0) { - file_error ("redirection error"); + sys_error ("redirection error"); return (-1); } - else - { - REDIRECT *dummy_redirect; - - clexec_flag = fcntl (fd, F_GETFD, 0); - rd.dest = 0L; - closer = make_redirection (new_fd, r_close_this, rd); - dummy_redirect = copy_redirects (closer); + clexec_flag = fcntl (fd, F_GETFD, 0); - rd.dest = (long)new_fd; - new_redirect = make_redirection (fd, r_duplicating_output, rd); - new_redirect->next = closer; + rd.dest = 0L; + closer = make_redirection (new_fd, r_close_this, rd); + dummy_redirect = copy_redirects (closer); - closer->next = redirection_undo_list; - redirection_undo_list = new_redirect; + rd.dest = (long)new_fd; + new_redirect = make_redirection (fd, r_duplicating_output, rd); + new_redirect->next = closer; - /* Save redirections that need to be undone even if the undo list - is thrown away by the `exec' builtin. */ - add_exec_redirect (dummy_redirect); + closer->next = redirection_undo_list; + redirection_undo_list = new_redirect; + + /* Save redirections that need to be undone even if the undo list + is thrown away by the `exec' builtin. */ + add_exec_redirect (dummy_redirect); + + /* File descriptors used only for saving others should always be + marked close-on-exec. Unfortunately, we have to preserve the + close-on-exec state of the file descriptor we are saving, since + fcntl (F_DUPFD) sets the new file descriptor to remain open + across execs. If, however, the file descriptor whose state we + are saving is <= 2, we can just set the close-on-exec flag, + because file descriptors 0-2 should always be open-on-exec, + and the restore above in do_redirection() will take care of it. */ + if (clexec_flag || fd < 3) + SET_CLOSE_ON_EXEC (new_fd); - /* File descriptors used only for saving others should always be - marked close-on-exec. Unfortunately, we have to preserve the - close-on-exec state of the file descriptor we are saving, since - fcntl (F_DUPFD) sets the new file descriptor to remain open - across execs. If, however, the file descriptor whose state we - are saving is <= 2, we can just set the close-on-exec flag, - because file descriptors 0-2 should always be open-on-exec, - and the restore above in do_redirection() will take care of it. */ - if (clexec_flag || fd < 3) - SET_CLOSE_ON_EXEC (new_fd); - } return (0); } @@ -3193,26 +3701,6 @@ add_exec_redirect (dummy_redirect) exec_redirection_undo_list = dummy_redirect; } -intern_function (name, function) - WORD_DESC *name; - COMMAND *function; -{ - SHELL_VAR *var; - - if (!check_identifier (name, posixly_correct)) - return (EXECUTION_FAILURE); - - var = find_function (name->word); - if (var && readonly_p (var)) - { - report_error ("%s: readonly function", var->name); - return (EXECUTION_FAILURE); - } - - bind_function (name->word, function); - return (EXECUTION_SUCCESS); -} - #define u_mode_bits(x) (((x) & 0000700) >> 6) #define g_mode_bits(x) (((x) & 0000070) >> 3) #define o_mode_bits(x) (((x) & 0000007) >> 0) @@ -3236,7 +3724,7 @@ file_status (name) /* 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); + return (FS_EXISTS|FS_DIRECTORY); #if defined (AFS) /* We have to use access(2) to determine access because AFS does not @@ -3280,8 +3768,8 @@ file_status (name) since we are also `others'. */ if (X_BIT (o_mode_bits (finfo.st_mode))) return (FS_EXISTS | FS_EXECABLE); - else - return (FS_EXISTS); + + return (FS_EXISTS); #endif /* !AFS */ } @@ -3293,7 +3781,17 @@ int executable_file (file) char *file; { - return (file_status (file) & FS_EXECABLE); + int s; + + s = file_status (file); + return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0)); +} + +int +is_directory (file) + char *file; +{ + return (file_status (file) & FS_DIRECTORY); } /* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command () @@ -3311,7 +3809,7 @@ char * find_user_command (name) char *name; { - return (find_user_command_internal (name, FS_EXEC_PREFERRED)); + return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS)); } /* Locate the file referenced by NAME, searching along the contents @@ -3326,7 +3824,7 @@ find_path_file (name) } static char * -find_user_command_internal (name, flags) +_find_user_command_internal (name, flags) char *name; int flags; { @@ -3335,7 +3833,7 @@ find_user_command_internal (name, flags) /* 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)) + if (var = find_variable_internal ("PATH", 1)) /* XXX could be array? */ path_list = value_cell (var); else path_list = (char *)NULL; @@ -3346,6 +3844,27 @@ find_user_command_internal (name, flags) return (find_user_command_in_path (name, path_list, flags)); } +static char * +find_user_command_internal (name, flags) + char *name; + int flags; +{ +#ifdef __WIN32__ + char *res, *dotexe; + + dotexe = 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. @@ -3372,60 +3891,130 @@ get_next_path_element (path_list, path_index_pointer) } char * +search_for_command (pathname) + 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); + hashed_file = (char *)NULL; + } + } + + if (hashed_file) + command = savestring (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); + else + command = find_user_command (pathname); + if (command && hashing_enabled && temp_path == 0) + remember_filename (pathname, command, dot_found_in_search, 1); + } + return (command); +} + +char * user_command_matches (name, flags, state) char *name; int flags, state; { register int i; - char *path_list; - int path_index; - char *path_element; - char *match; + 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) + if (state == 0) { /* Create the list of matches. */ - if (!match_list) + if (match_list == 0) { - match_list = - (char **) xmalloc ((match_list_size = 5) * sizeof(char *)); - - for (i = 0; i < match_list_size; i++) - match_list[i] = 0; + match_list_size = 5; + match_list = (char **)xmalloc (match_list_size * sizeof(char *)); } /* Clear out the old match list. */ for (i = 0; i < match_list_size; i++) - match_list[i] = NULL; + match_list[i] = 0; /* We haven't found any files yet. */ match_index = 0; - path_list = get_string_value ("PATH"); - path_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) + if (path_element == 0) break; - match = find_user_command_in_path (name, path_element, flags); + match = find_in_path_element (name, path_element, flags, name_len, &dotinfo); free (path_element); - if (!match) + if (match == 0) continue; if (match_index + 1 == match_list_size) - match_list = (char **)xrealloc - (match_list, ((match_list_size += 10) + 1) * sizeof (char *)); + { + 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. */ @@ -3440,33 +4029,6 @@ user_command_matches (name, flags, state) return (match); } -/* Return 1 if PATH1 and PATH2 are the same file. This is kind of - expensive. If non-NULL STP1 and STP2 point to stat structures - corresponding to PATH1 and PATH2, respectively. */ -int -same_file (path1, path2, stp1, stp2) - char *path1, *path2; - struct stat *stp1, *stp2; -{ - struct stat st1, st2; - - if (stp1 == NULL) - { - if (stat (path1, &st1) != 0) - return (0); - stp1 = &st1; - } - - if (stp2 == NULL) - { - if (stat (path2, &st2) != 0) - return (0); - stp2 = &st2; - } - - return ((stp1->st_dev == stp2->st_dev) && (stp1->st_ino == stp2->st_ino)); -} - /* Turn PATH, a directory, and NAME, a filename, into a full pathname. This allocates new memory and returns it. */ static char * @@ -3484,8 +4046,92 @@ make_full_pathname (path, name, name_len) strcpy (full_path + path_len + 1, name); return (full_path); } - -/* This does the dirty work for find_path_file () and find_user_command (). + +static char * +find_absolute_program (name, flags) + 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 ((char *)NULL); +} + +static char * +find_in_path_element (name, path, flags, name_len, dotinfop) + char *name, *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 = make_full_pathname (xpath, name, name_len); + + 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. @@ -3494,6 +4140,7 @@ make_full_pathname (path, name, name_len) 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) @@ -3501,18 +4148,9 @@ find_user_command_in_path (name, path_list, flags) char *path_list; int flags; { - char *full_path, *path, *file_to_lose_on; - int status, path_index, name_len; - struct stat finfo; - - name_len = strlen (name); - - /* 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. */ - file_to_lose_on = (char *)NULL; + 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. */ @@ -3520,94 +4158,45 @@ find_user_command_in_path (name, path_list, flags) if (absolute_program (name)) { - full_path = xmalloc (1 + name_len); - strcpy (full_path, name); - - status = file_status (full_path); - - /* If the file doesn't exist, quit now. */ - if (!(status & FS_EXISTS)) - { - free (full_path); - return ((char *)NULL); - } - - /* If we only care about whether the file exists or not, return - this filename. */ - if (flags & FS_EXISTS) - return (full_path); - - /* Otherwise, maybe we care about whether this file is executable. - If it is, and that is what we want, return it. */ - if ((flags & FS_EXEC_ONLY) && (status & FS_EXECABLE)) - return (full_path); - else - { - free (full_path); - return ((char *)NULL); - } + full_path = find_absolute_program (name, flags); + return (full_path); } - /* Find out the location of the current working directory. */ - stat (".", &finfo); + 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_list[path_index]) + + 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) + if (path == 0) break; - if (*path == '~') - { - char *t = tilde_expand (path); - free (path); - path = t; - } - - /* 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 && (*path == '.') && - same_file (".", path, &finfo, (struct stat *)NULL)) - dot_found_in_search = 1; - - full_path = make_full_pathname (path, name, name_len); + /* 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); - status = file_status (full_path); - - if (!(status & FS_EXISTS)) - goto next_file; - - /* 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) + /* 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 (file_to_lose_on); - - return (full_path); + free (full_path); + continue; } - /* 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) + if (full_path) { - if (!file_to_lose_on) - file_to_lose_on = savestring (full_path); + FREE (file_to_lose_on); + return (full_path); } - - next_file: - free (full_path); } /* We didn't find exactly what the user was looking for. Return @@ -3616,83 +4205,3 @@ find_user_command_in_path (name, path_list, flags) search would accept a non-executable as a last resort. */ return (file_to_lose_on); } - -/* Given a string containing units of information separated by colons, - return the next one pointed to by (P_INDEX), or NULL if there are no more. - Advance (P_INDEX) to the character after the colon. */ -char * -extract_colon_unit (string, p_index) - char *string; - int *p_index; -{ - int i, start; - - i = *p_index; - - if (!string || (i >= (int)strlen (string))) - return ((char *)NULL); - - /* Each call to this routine leaves the index pointing at a colon if - there is more to the path. If I is > 0, then increment past the - `:'. If I is 0, then the path has a leading colon. Trailing colons - are handled OK by the `else' part of the if statement; an empty - string is returned in that case. */ - if (i && string[i] == ':') - i++; - - start = i; - - while (string[i] && string[i] != ':') i++; - - *p_index = i; - - if (i == start) - { - if (string[i]) - (*p_index)++; - - /* Return "" in the case of a trailing `:'. */ - return (savestring ("")); - } - else - { - char *value; - - value = xmalloc (1 + i - start); - strncpy (value, string + start, i - start); - value [i - start] = '\0'; - - return (value); - } -} - -/* Return non-zero if the characters from SAMPLE are not all valid - characters to be found in the first line of a shell script. We - check up to the first newline, or SAMPLE_LEN, whichever comes first. - All of the characters must be printable or whitespace. */ - -#if !defined (isspace) -#define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\n' || (c) == '\f') -#endif - -#if !defined (isprint) -#define isprint(c) (isletter(c) || digit(c) || ispunct(c)) -#endif - -int -check_binary_file (sample, sample_len) - unsigned char *sample; - int sample_len; -{ - register int i; - - for (i = 0; i < sample_len; i++) - { - if (sample[i] == '\n') - break; - - if (!isspace (sample[i]) && !isprint (sample[i])) - return (1); - } - return (0); -} |